commit 7a83face87368f191240fca81c1709aad88704ba Author: Synthasmagoria Date: Fri Sep 11 13:27:24 2026 +0200 Update diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b622c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build +cscope.out +cscope.in.out +cscope.po.out +transformation_visualizer +src/libraries/raylib/linux/* +src/libraries/raylib/wasm/* diff --git a/NOTES.MD b/NOTES.MD new file mode 100644 index 0000000..2259057 --- /dev/null +++ b/NOTES.MD @@ -0,0 +1,3 @@ +For a release build you can do +upx --best --lzma game_desktop.bin +Though apparently this is somewhat problematic diff --git a/build_desktop.sh b/build_desktop.sh new file mode 100755 index 0000000..80af076 --- /dev/null +++ b/build_desktop.sh @@ -0,0 +1,28 @@ +#!/bin/bash -eu + +EXE_NAME="Transformation Visualizer" +OUT_DIR="build/desktop" +ASSETS_DIR="src/assets/" + +if [[ -v 1 ]]; then + if [ "$1" = "DEBUG" ]; then + ODIN_FLAGS="-debug" + elif [ "$1" = "RELEASE" ]; then + ODIN_FLAGS="-o:speed -extra-linker-flags:\"-Wl,--gc-sections\"" + else + echo "Usage: + + + + +

+ + {{{ SCRIPT }}} + + diff --git a/src/libraries/raylib/raylib/src/platforms/rcore_android.c b/src/libraries/raylib/raylib/src/platforms/rcore_android.c new file mode 100644 index 0000000..2851658 --- /dev/null +++ b/src/libraries/raylib/raylib/src/platforms/rcore_android.c @@ -0,0 +1,1615 @@ +/********************************************************************************************** +* +* rcore_android - Functions to manage window, graphics device and inputs +* +* PLATFORM: ANDROID +* - Android (ARM, ARM64) +* +* LIMITATIONS: +* - Limitation 01 +* - Limitation 02 +* +* POSSIBLE IMPROVEMENTS: +* - Improvement 01 +* - Improvement 02 +* +* CONFIGURATION: +* #define RCORE_PLATFORM_CUSTOM_FLAG +* Custom flag for rcore on target platform -not used- +* +* DEPENDENCIES: +* - Android NDK: Provides C API to access Android functionality +* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include // Required for: android_app struct and activity management +#include // Required for: AWINDOW_FLAG_FULLSCREEN definition and others +#include // Required for: Android log system: __android_log_vprint() +#include // Required for: AAssetManager +//#include // Required for: Android sensors functions (accelerometer, gyroscope, light...) + +#include // Required for: error types +#include // Required for: JNIEnv and JavaVM [Used in OpenURL() and GetCurrentMonitor()] + +#include // Native platform windowing system interface + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct { + // Application data + struct android_app *app; // Android activity + struct android_poll_source *source; // Android events polling source + bool appEnabled; // Flag to detect if app is active ** = true + bool contextRebindRequired; // Used to know context rebind required + + // Display data + EGLDisplay device; // Native display device (physical screen connection) + EGLSurface surface; // Surface to draw on, framebuffers (connected to context) + EGLContext context; // Graphic context, mode in which drawing can be done + EGLConfig config; // Graphic config +} PlatformData; + +typedef struct { + // Store data for both Hover and Touch events + // Used to ignore Hover events which are interpreted as Touch events + int32_t pointCount; // Number of touch points active + int32_t pointId[MAX_TOUCH_POINTS]; // Point identifiers + Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen + + int32_t hoverPoints[MAX_TOUCH_POINTS]; // Hover Points +} TouchRaw; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context +static PlatformData platform = { 0 }; // Platform specific data + +//---------------------------------------------------------------------------------- +// Local Variables Definition +//---------------------------------------------------------------------------------- +#define KEYCODE_MAP_SIZE 162 +static const KeyboardKey mapKeycode[KEYCODE_MAP_SIZE] = { + KEY_NULL, // AKEYCODE_UNKNOWN + 0, // AKEYCODE_SOFT_LEFT + 0, // AKEYCODE_SOFT_RIGHT + 0, // AKEYCODE_HOME + KEY_BACK, // AKEYCODE_BACK + 0, // AKEYCODE_CALL + 0, // AKEYCODE_ENDCALL + KEY_ZERO, // AKEYCODE_0 + KEY_ONE, // AKEYCODE_1 + KEY_TWO, // AKEYCODE_2 + KEY_THREE, // AKEYCODE_3 + KEY_FOUR, // AKEYCODE_4 + KEY_FIVE, // AKEYCODE_5 + KEY_SIX, // AKEYCODE_6 + KEY_SEVEN, // AKEYCODE_7 + KEY_EIGHT, // AKEYCODE_8 + KEY_NINE, // AKEYCODE_9 + 0, // AKEYCODE_STAR + 0, // AKEYCODE_POUND + KEY_UP, // AKEYCODE_DPAD_UP + KEY_DOWN, // AKEYCODE_DPAD_DOWN + KEY_LEFT, // AKEYCODE_DPAD_LEFT + KEY_RIGHT, // AKEYCODE_DPAD_RIGHT + 0, // AKEYCODE_DPAD_CENTER + KEY_VOLUME_UP, // AKEYCODE_VOLUME_UP + KEY_VOLUME_DOWN, // AKEYCODE_VOLUME_DOWN + 0, // AKEYCODE_POWER + 0, // AKEYCODE_CAMERA + 0, // AKEYCODE_CLEAR + KEY_A, // AKEYCODE_A + KEY_B, // AKEYCODE_B + KEY_C, // AKEYCODE_C + KEY_D, // AKEYCODE_D + KEY_E, // AKEYCODE_E + KEY_F, // AKEYCODE_F + KEY_G, // AKEYCODE_G + KEY_H, // AKEYCODE_H + KEY_I, // AKEYCODE_I + KEY_J, // AKEYCODE_J + KEY_K, // AKEYCODE_K + KEY_L, // AKEYCODE_L + KEY_M, // AKEYCODE_M + KEY_N, // AKEYCODE_N + KEY_O, // AKEYCODE_O + KEY_P, // AKEYCODE_P + KEY_Q, // AKEYCODE_Q + KEY_R, // AKEYCODE_R + KEY_S, // AKEYCODE_S + KEY_T, // AKEYCODE_T + KEY_U, // AKEYCODE_U + KEY_V, // AKEYCODE_V + KEY_W, // AKEYCODE_W + KEY_X, // AKEYCODE_X + KEY_Y, // AKEYCODE_Y + KEY_Z, // AKEYCODE_Z + KEY_COMMA, // AKEYCODE_COMMA + KEY_PERIOD, // AKEYCODE_PERIOD + KEY_LEFT_ALT, // AKEYCODE_ALT_LEFT + KEY_RIGHT_ALT, // AKEYCODE_ALT_RIGHT + KEY_LEFT_SHIFT, // AKEYCODE_SHIFT_LEFT + KEY_RIGHT_SHIFT, // AKEYCODE_SHIFT_RIGHT + KEY_TAB, // AKEYCODE_TAB + KEY_SPACE, // AKEYCODE_SPACE + 0, // AKEYCODE_SYM + 0, // AKEYCODE_EXPLORER + 0, // AKEYCODE_ENVELOPE + KEY_ENTER, // AKEYCODE_ENTER + KEY_BACKSPACE, // AKEYCODE_DEL + KEY_GRAVE, // AKEYCODE_GRAVE + KEY_MINUS, // AKEYCODE_MINUS + KEY_EQUAL, // AKEYCODE_EQUALS + KEY_LEFT_BRACKET, // AKEYCODE_LEFT_BRACKET + KEY_RIGHT_BRACKET, // AKEYCODE_RIGHT_BRACKET + KEY_BACKSLASH, // AKEYCODE_BACKSLASH + KEY_SEMICOLON, // AKEYCODE_SEMICOLON + KEY_APOSTROPHE, // AKEYCODE_APOSTROPHE + KEY_SLASH, // AKEYCODE_SLASH + 0, // AKEYCODE_AT + 0, // AKEYCODE_NUM + 0, // AKEYCODE_HEADSETHOOK + 0, // AKEYCODE_FOCUS + 0, // AKEYCODE_PLUS + KEY_MENU, // AKEYCODE_MENU + 0, // AKEYCODE_NOTIFICATION + 0, // AKEYCODE_SEARCH + 0, // AKEYCODE_MEDIA_PLAY_PAUSE + 0, // AKEYCODE_MEDIA_STOP + 0, // AKEYCODE_MEDIA_NEXT + 0, // AKEYCODE_MEDIA_PREVIOUS + 0, // AKEYCODE_MEDIA_REWIND + 0, // AKEYCODE_MEDIA_FAST_FORWARD + 0, // AKEYCODE_MUTE + KEY_PAGE_UP, // AKEYCODE_PAGE_UP + KEY_PAGE_DOWN, // AKEYCODE_PAGE_DOWN + 0, // AKEYCODE_PICTSYMBOLS + 0, // AKEYCODE_SWITCH_CHARSET + 0, // AKEYCODE_BUTTON_A + 0, // AKEYCODE_BUTTON_B + 0, // AKEYCODE_BUTTON_C + 0, // AKEYCODE_BUTTON_X + 0, // AKEYCODE_BUTTON_Y + 0, // AKEYCODE_BUTTON_Z + 0, // AKEYCODE_BUTTON_L1 + 0, // AKEYCODE_BUTTON_R1 + 0, // AKEYCODE_BUTTON_L2 + 0, // AKEYCODE_BUTTON_R2 + 0, // AKEYCODE_BUTTON_THUMBL + 0, // AKEYCODE_BUTTON_THUMBR + 0, // AKEYCODE_BUTTON_START + 0, // AKEYCODE_BUTTON_SELECT + 0, // AKEYCODE_BUTTON_MODE + KEY_ESCAPE, // AKEYCODE_ESCAPE + KEY_DELETE, // AKEYCODE_FORWARD_DELL + KEY_LEFT_CONTROL, // AKEYCODE_CTRL_LEFT + KEY_RIGHT_CONTROL, // AKEYCODE_CTRL_RIGHT + KEY_CAPS_LOCK, // AKEYCODE_CAPS_LOCK + KEY_SCROLL_LOCK, // AKEYCODE_SCROLL_LOCK + KEY_LEFT_SUPER, // AKEYCODE_META_LEFT + KEY_RIGHT_SUPER, // AKEYCODE_META_RIGHT + 0, // AKEYCODE_FUNCTION + KEY_PRINT_SCREEN, // AKEYCODE_SYSRQ + KEY_PAUSE, // AKEYCODE_BREAK + KEY_HOME, // AKEYCODE_MOVE_HOME + KEY_END, // AKEYCODE_MOVE_END + KEY_INSERT, // AKEYCODE_INSERT + 0, // AKEYCODE_FORWARD + 0, // AKEYCODE_MEDIA_PLAY + 0, // AKEYCODE_MEDIA_PAUSE + 0, // AKEYCODE_MEDIA_CLOSE + 0, // AKEYCODE_MEDIA_EJECT + 0, // AKEYCODE_MEDIA_RECORD + KEY_F1, // AKEYCODE_F1 + KEY_F2, // AKEYCODE_F2 + KEY_F3, // AKEYCODE_F3 + KEY_F4, // AKEYCODE_F4 + KEY_F5, // AKEYCODE_F5 + KEY_F6, // AKEYCODE_F6 + KEY_F7, // AKEYCODE_F7 + KEY_F8, // AKEYCODE_F8 + KEY_F9, // AKEYCODE_F9 + KEY_F10, // AKEYCODE_F10 + KEY_F11, // AKEYCODE_F11 + KEY_F12, // AKEYCODE_F12 + KEY_NUM_LOCK, // AKEYCODE_NUM_LOCK + KEY_KP_0, // AKEYCODE_NUMPAD_0 + KEY_KP_1, // AKEYCODE_NUMPAD_1 + KEY_KP_2, // AKEYCODE_NUMPAD_2 + KEY_KP_3, // AKEYCODE_NUMPAD_3 + KEY_KP_4, // AKEYCODE_NUMPAD_4 + KEY_KP_5, // AKEYCODE_NUMPAD_5 + KEY_KP_6, // AKEYCODE_NUMPAD_6 + KEY_KP_7, // AKEYCODE_NUMPAD_7 + KEY_KP_8, // AKEYCODE_NUMPAD_8 + KEY_KP_9, // AKEYCODE_NUMPAD_9 + KEY_KP_DIVIDE, // AKEYCODE_NUMPAD_DIVIDE + KEY_KP_MULTIPLY, // AKEYCODE_NUMPAD_MULTIPLY + KEY_KP_SUBTRACT, // AKEYCODE_NUMPAD_SUBTRACT + KEY_KP_ADD, // AKEYCODE_NUMPAD_ADD + KEY_KP_DECIMAL, // AKEYCODE_NUMPAD_DOT + 0, // AKEYCODE_NUMPAD_COMMA + KEY_KP_ENTER, // AKEYCODE_NUMPAD_ENTER + KEY_KP_EQUAL // AKEYCODE_NUMPAD_EQUALS +}; + +static TouchRaw touchRaw = { 0 }; + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +void ClosePlatform(void); // Close platform + +static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs +static GamepadButton AndroidTranslateGamepadButton(int button); // Map Android gamepad button to raylib gamepad button + +static void SetupFramebuffer(int width, int height); // Setup main framebuffer (required by InitPlatform()) + +static int android_read(void *cookie, char *buf, int size); +static int android_write(void *cookie, const char *buf, int size); +static fpos_t android_seek(void *cookie, fpos_t offset, int whence); +static int android_close(void *cookie); + +// WARNING: fopen() calls are intercepted via linker flag -Wl,--wrap=fopen: the linker renames +// the original fopen -> __real_fopen and redirects all call sites to __wrap_fopen +// The flag MUST be applied at every final link step that needs wrapping; +// it has no effect when only building a static archive (.a) +// +// STATIC library (.a) — wrapping deferred to consumer's final link step: +// both raylib and consumer fopen calls are wrapped together in one link +// CMake: handled automatically — the PUBLIC flag propagates as INTERFACE_LINK_OPTIONS +// to the consumer's final link via target_link_libraries +// Make: pass -Wl,--wrap=fopen to the linker command producing the final artifact +// build.zig: pass -Wl,--wrap=fopen to the linker command producing the final artifact +// custom: pass -Wl,--wrap=fopen to the linker command producing the final artifact +// +// SHARED library (.so) — wrapping is self-contained: +// only fopen calls linked into the .so are wrapped; the consumer's own fopen calls +// are NOT wrapped unless the consumer also links with -Wl,--wrap=fopen independently +// CMake: handled automatically — CMakeLists.txt sets target_link_options(raylib PUBLIC +// -Wl,--wrap=fopen) which applies the flag to the .so link; +// only raylib internals are wrapped, app code requires a separate flag +// Make: handled automatically — src/Makefile sets LDFLAGS += -Wl,--wrap=fopen; +// only raylib internals are wrapped, app code requires a separate flag +// build.zig: NOT supported — std.Build has no dedicated linker wrap helper, the flag +// is not correctly applied at the .so link step +// custom: apply -Wl,--wrap=fopen to the linker command producing the .so +FILE *__real_fopen(const char *fileName, const char *mode); // Real fopen, provided by the linker (--wrap=fopen) +FILE *__wrap_fopen(const char *fileName, const char *mode); // Replacement for fopen() + +FILE *funopen(const void *cookie, int (*readfn)(void *, char *, int), int (*writefn)(void *, const char *, int), + fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *)); + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Application +//---------------------------------------------------------------------------------- + +// To allow easier porting to android, allow the user to define a +// custom main function which is called from android_main +extern int main(int argc, char *argv[]); + +// Android main function +void android_main(struct android_app *app) +{ + char arg0[] = "raylib"; // NOTE: argv[] are mutable + platform.app = app; + + // NOTE: Return from main is ignored + (void)main(1, (char *[]) { arg0, NULL }); + + // Request to end the native activity + ANativeActivity_finish(app->activity); + + // Android ALooper_pollOnce() variables + int pollResult = 0; + int pollEvents = 0; + + // Waiting for application events before complete finishing + while (!app->destroyRequested) + { + // Poll all events until return value TIMEOUT is reached, meaning no events left to process + while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, (void **)&platform.source)) > ALOOPER_POLL_TIMEOUT) + { + if (platform.source != NULL) platform.source->process(app, platform.source); + } + } +} + +// NOTE: Add this to header (if apps really need it) +struct android_app *GetAndroidApp(void) +{ + return platform.app; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +bool WindowShouldClose(void) +{ + if (CORE.Window.ready) return CORE.Window.shouldClose; + else return true; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + TRACELOG(LOG_WARNING, "ToggleFullscreen() not available on target platform"); +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + TRACELOG(LOG_WARNING, "ToggleBorderlessWindowed() not available on target platform"); +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MaximizeWindow() not available on target platform"); +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform"); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + TRACELOG(LOG_WARNING, "RestoreWindow() not available on target platform"); +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + // State change: FLAG_WINDOW_ALWAYS_RUN + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform"); +} + +// Set icon for window +void SetWindowIcons(Image *images, int count) +{ + TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform"); +} + +// Set title for window +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform"); +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform"); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + TRACELOG(LOG_WARNING, "SetWindowSize() not available on target platform"); +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + TRACELOG(LOG_WARNING, "SetWindowOpacity() not available on target platform"); +} + +// Set window focused +void SetWindowFocused(void) +{ + TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform"); +} + +// Get native window handle +void *GetWindowHandle(void) +{ + return (void *)platform.app->window; // Type: ANativeWindow* +} + +// Get number of monitors +int GetMonitorCount(void) +{ + TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform"); + return 1; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + int displayId = -1; + JNIEnv *env = NULL; + JavaVM *vm = platform.app->activity->vm; + (*vm)->AttachCurrentThread(vm, &env, NULL); + + jobject activity = platform.app->activity->clazz; + jclass activityClass = (*env)->GetObjectClass(env, activity); + + jmethodID getDisplayMethod = (*env)->GetMethodID(env, activityClass, "getDisplay", "()Landroid/view/Display;"); + + jobject display = (*env)->CallObjectMethod(env, activity, getDisplayMethod); + + if (display == NULL) + { + TRACELOG(LOG_ERROR, "GetCurrentMonitor() couldn't get the display object"); + } + else + { + jclass displayClass = (*env)->FindClass(env, "android/view/Display"); + jmethodID getDisplayIdMethod = (*env)->GetMethodID(env, displayClass, "getDisplayId", "()I"); + displayId = (int)(*env)->CallIntMethod(env, display, getDisplayIdMethod); + (*env)->DeleteLocalRef(env, displayClass); + } + + (*env)->DeleteLocalRef(env, activityClass); + (*env)->DeleteLocalRef(env, display); + + (*vm)->DetachCurrentThread(vm); + return displayId; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorWidth() not implemented on target platform"); + return 0; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorHeight() not implemented on target platform"); + return 0; +} + +// Get selected monitor physical width in millimetres +// NOTE: It seems to return a slightly underestimated value on some devices +int GetMonitorPhysicalWidth(int monitor) +{ + int widthPixels = ANativeWindow_getWidth(platform.app->window); + float dpi = AConfiguration_getDensity(platform.app->config); + return (widthPixels/dpi)*25.4f; +} + +// Get selected monitor physical height in millimetres +// NOTE: It seems to return a slightly underestimated value on some devices +int GetMonitorPhysicalHeight(int monitor) +{ + int heightPixels = ANativeWindow_getHeight(platform.app->window); + float dpi = AConfiguration_getDensity(platform.app->config); + return (heightPixels/dpi)*25.4f; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorRefreshRate() not implemented on target platform"); + return 0; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorName() not implemented on target platform"); + return ""; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + TRACELOG(LOG_WARNING, "GetWindowPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get window scale DPI factor for current monitor +Vector2 GetWindowScaleDPI(void) +{ + int density = AConfiguration_getDensity(platform.app->config); + float scale = (float)density/160; + return (Vector2){ scale, scale }; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + TRACELOG(LOG_WARNING, "SetClipboardText() not implemented on target platform"); +} + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ + TRACELOG(LOG_WARNING, "GetClipboardText() not implemented on target platform"); + return NULL; +} + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + + TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform"); + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + CORE.Input.Mouse.cursorHidden = false; +} + +// Hide mouse cursor +void HideCursor(void) +{ + CORE.Input.Mouse.cursorHidden = true; +} + +// Enable cursor (unlock cursor) +void EnableCursor(void) +{ + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + CORE.Input.Mouse.cursorLocked = false; +} + +// Disables cursor (lock cursor) +void DisableCursor(void) +{ + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + CORE.Input.Mouse.cursorLocked = true; +} + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ + if (platform.surface != EGL_NO_SURFACE) eglSwapBuffers(platform.device, platform.surface); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds since InitTimer() +double GetTime(void) +{ + double time = 0.0; + struct timespec ts = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long long nanoSeconds = (unsigned long long)ts.tv_sec*1000000000LLU + (unsigned long long)ts.tv_nsec; + + time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() + + return time; +} + +// Open URL with default system browser (if available) +// WARNING: This function is only safe to use if you control the URL given, +// a user could craft a malicious string to perform and undesired action +// NOTE: Some safety checks have been added to mitigate security issues +void OpenURL(const char *url) +{ + // Security check to (partially) avoid malicious code + if ((strchr(url, '\'') != NULL) || (strchr(url, '\"') != NULL)) + { + // Filter characters: ' and " + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'\"] characters"); + } + else if ((strncmp(url, "http://", 7) != 0) && (strncmp(url, "https://", 8) != 0)) + { + // Only allow URL starting with "http://" or "https://" protocols + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL must start with 'http://' or 'https://' protocols"); + } + else + { + JNIEnv *env = NULL; + JavaVM *vm = platform.app->activity->vm; + (*vm)->AttachCurrentThread(vm, &env, NULL); + + jstring urlString = (*env)->NewStringUTF(env, url); + jclass uriClass = (*env)->FindClass(env, "android/net/Uri"); + jmethodID uriParse = (*env)->GetStaticMethodID(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); + jobject uri = (*env)->CallStaticObjectMethod(env, uriClass, uriParse, urlString); + + jclass intentClass = (*env)->FindClass(env, "android/content/Intent"); + jfieldID actionViewId = (*env)->GetStaticFieldID(env, intentClass, "ACTION_VIEW", "Ljava/lang/String;"); + jobject actionView = (*env)->GetStaticObjectField(env, intentClass, actionViewId); + jmethodID newIntent = (*env)->GetMethodID(env, intentClass, "", "(Ljava/lang/String;Landroid/net/Uri;)V"); + jobject intent = (*env)->AllocObject(env, intentClass); + + (*env)->CallVoidMethod(env, intent, newIntent, actionView, uri); + jclass activityClass = (*env)->FindClass(env, "android/app/Activity"); + jmethodID startActivity = (*env)->GetMethodID(env, activityClass, "startActivity", "(Landroid/content/Intent;)V"); + (*env)->CallVoidMethod(env, platform.app->activity->clazz, startActivity, intent); + + (*vm)->DetachCurrentThread(vm); + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + TRACELOG(LOG_WARNING, "SetGamepadMappings() not implemented on target platform"); + return 0; +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + TRACELOG(LOG_WARNING, "SetGamepadVibration() not implemented on target platform"); +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; +} + +// Set mouse cursor +void SetMouseCursor(int cursor) +{ + TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform"); +} + +// Get physical key name +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + +// Register all input events +void PollInputEvents(void) +{ +#if SUPPORT_GESTURES_SYSTEM + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + // Reset key repeats + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN + //CORE.Input.Gamepad.axisCount = 0; + + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available + { + // Register previous gamepad states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) + CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; + } + } + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Reset touch positions + //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; + + // Register previous keys states + // NOTE: Android supports up to 260 keys + for (int i = 0; i < 260; i++) + { + CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // Android ALooper_pollOnce() variables + int pollResult = 0; + int pollEvents = 0; + + // Poll Events (registered events) until TIMEOUT is reached which indicates there are no events left to poll + // NOTE: Activity is paused if not enabled (platform.appEnabled) and always run flag is not set (FLAG_WINDOW_ALWAYS_RUN) + while ((pollResult = ALooper_pollOnce((platform.appEnabled || FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN))? 0 : -1, NULL, &pollEvents, ((void **)&platform.source)) > ALOOPER_POLL_TIMEOUT)) + { + // Process this event + if (platform.source != NULL) platform.source->process(platform.app, platform.source); + + // NOTE: Allow closing the window in case a configuration change happened + // The android_main function should be allowed to return to its caller in order for the + // Android OS to relaunch the activity + if (platform.app->destroyRequested != 0) + { + CORE.Window.shouldClose = true; + break; + } + } +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + // Initialize display basic configuration + //---------------------------------------------------------------------------- + CORE.Window.currentFbo.width = CORE.Window.screen.width; + CORE.Window.currentFbo.height = CORE.Window.screen.height; + + // Set desired windows flags before initializing anything + ANativeActivity_setWindowFlags(platform.app->activity, AWINDOW_FLAG_FULLSCREEN, 0); //AWINDOW_FLAG_SCALED, AWINDOW_FLAG_DITHER + + int orientation = AConfiguration_getOrientation(platform.app->config); + + if (orientation == ACONFIGURATION_ORIENTATION_PORT) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as portrait"); + else if (orientation == ACONFIGURATION_ORIENTATION_LAND) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as landscape"); + + // TODO: Automatic orientation doesn't seem to work + if (CORE.Window.screen.width <= CORE.Window.screen.height) + { + AConfiguration_setOrientation(platform.app->config, ACONFIGURATION_ORIENTATION_PORT); + TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to portrait"); + } + else + { + AConfiguration_setOrientation(platform.app->config, ACONFIGURATION_ORIENTATION_LAND); + TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to landscape"); + } + + //AConfiguration_getDensity(platform.app->config); + //AConfiguration_getKeyboard(platform.app->config); + //AConfiguration_getScreenSize(platform.app->config); + //AConfiguration_getScreenLong(platform.app->config); + + // Set some default window flags + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // false + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // false + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // true + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // false + //---------------------------------------------------------------------------- + + // Initialize App command system + // NOTE: On APP_CMD_INIT_WINDOW -> InitGraphicsDevice(), InitTimer(), LoadFontDefault()... + //---------------------------------------------------------------------------- + platform.app->onAppCmd = AndroidCommandCallback; + //---------------------------------------------------------------------------- + + // Initialize input events system + //---------------------------------------------------------------------------- + platform.app->onInputEvent = AndroidInputCallback; + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = platform.app->activity->internalDataPath; // Define base path for storage + //---------------------------------------------------------------------------- + + TRACELOG(LOG_INFO, "PLATFORM: ANDROID: Initialized successfully"); + + // Android ALooper_pollOnce() variables + int pollResult = 0; + int pollEvents = 0; + + // Wait for window to be initialized (display and context) + while (!CORE.Window.ready) + { + // Process events until TIMEOUT is reached, which indicates no more events queued + while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, ((void **)&platform.source)) > ALOOPER_POLL_TIMEOUT)) + { + // Process this event + if (platform.source != NULL) platform.source->process(platform.app, platform.source); + + // NOTE: It's highly likely destroyRequested will never be non-zero at the start of the activity lifecycle + //if (platform.app->destroyRequested != 0) CORE.Window.shouldClose = true; + } + } + + for (int i = 0; i < MAX_TOUCH_POINTS; i++) touchRaw.hoverPoints[i] = -1; + + return 0; +} + +// Close platform +void ClosePlatform(void) +{ + // Close surface, context and display + if (platform.device != EGL_NO_DISPLAY) + { + eglMakeCurrent(platform.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (platform.surface != EGL_NO_SURFACE) + { + eglDestroySurface(platform.device, platform.surface); + platform.surface = EGL_NO_SURFACE; + } + + if (platform.context != EGL_NO_CONTEXT) + { + eglDestroyContext(platform.device, platform.context); + platform.context = EGL_NO_CONTEXT; + } + + eglTerminate(platform.device); + platform.device = EGL_NO_DISPLAY; + } + + // NOTE: Reset global state in case the activity is being relaunched + if (platform.app->destroyRequested != 0) + { + CORE = (CoreData){ 0 }; + platform = (PlatformData){ 0 }; + } +} + +// Initialize display device and framebuffer +// NOTE: width and height represent the screen (framebuffer) desired size, not actual display size +// If width or height are 0, default display size will be used for framebuffer size +// NOTE: returns false in case graphic device could not be created +static int InitGraphicsDevice(void) +{ + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + + EGLint samples = 0; + EGLint sampleBuffer = 0; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) + { + samples = 4; + sampleBuffer = 1; + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); + } + + const EGLint framebufferAttribs[] = { + EGL_RENDERABLE_TYPE, (rlGetVersion() == RL_OPENGL_ES_30)? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT, // Type of context support + EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5) + EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6) + EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) + //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) + EGL_DEPTH_SIZE, 24, // Depth buffer size (Required to use Depth testing!) + //EGL_STENCIL_SIZE, 8, // Stencil buffer size + EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA + EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) + EGL_NONE + }; + + const EGLint contextAttribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + EGLint numConfigs = 0; + + // Get an EGL device connection + platform.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); + + if (platform.device == EGL_NO_DISPLAY) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return -1; + } + + // Initialize the EGL device connection + if (eglInitialize(platform.device, NULL, NULL) == EGL_FALSE) + { + // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return -1; + } + + // Get an appropriate EGL framebuffer configuration + eglChooseConfig(platform.device, framebufferAttribs, &platform.config, 1, &numConfigs); + + // Set rendering API + eglBindAPI(EGL_OPENGL_ES_API); + + // Create an EGL rendering context + platform.context = eglCreateContext(platform.device, platform.config, EGL_NO_CONTEXT, contextAttribs); + if (platform.context == EGL_NO_CONTEXT) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context"); + return -1; + } + + // Create an EGL window surface + //--------------------------------------------------------------------------------- + EGLint displayFormat = 0; + + // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is guaranteed to be accepted by ANativeWindow_setBuffersGeometry() + // As soon as an EGLConfig is picked, it's safe to reconfigure the ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID + eglGetConfigAttrib(platform.device, platform.config, EGL_NATIVE_VISUAL_ID, &displayFormat); + + // At this point render size vs screen size needs to be managed + // NOTE: This function use and modify global module variables: + // -> CORE.Window.screen.width/CORE.Window.screen.height + // -> CORE.Window.render.width/CORE.Window.render.height + // -> CORE.Window.screenScale + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); + + ANativeWindow_setBuffersGeometry(platform.app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat); + //ANativeWindow_setBuffersGeometry(platform.app->window, 0, 0, displayFormat); // Force use of native display size + + platform.surface = eglCreateWindowSurface(platform.device, platform.config, platform.app->window, NULL); + + // There must be at least one frame displayed before the buffers are swapped + //eglSwapInterval(platform.device, 1); + + if (eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context) == EGL_FALSE) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to attach EGL rendering context to EGL surface"); + return -1; + } + else + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + } + + // Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions + rlLoadExtensions(eglGetProcAddress); + + CORE.Window.ready = true; + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); + + return 0; +} + +// ANDROID: Process activity lifecycle commands +static void AndroidCommandCallback(struct android_app *app, int32_t cmd) +{ + switch (cmd) + { + case APP_CMD_START: + { + //rendering = true; + } break; + case APP_CMD_RESUME: break; + case APP_CMD_INIT_WINDOW: + { + if (app->window != NULL) + { + if (platform.contextRebindRequired) + { + // Reset screen scaling to full display size + EGLint displayFormat = 0; + eglGetConfigAttrib(platform.device, platform.config, EGL_NATIVE_VISUAL_ID, &displayFormat); + + // Adding renderOffset here feels rather hackish, but the viewport scaling is wrong after the + // context rebinding if the screen is scaled unless offsets are added. There's probably a more + // appropriate way to fix this + ANativeWindow_setBuffersGeometry(app->window, + CORE.Window.render.width + CORE.Window.renderOffset.x, + CORE.Window.render.height + CORE.Window.renderOffset.y, + displayFormat); + + // Recreate display surface and re-attach OpenGL context + platform.surface = eglCreateWindowSurface(platform.device, platform.config, app->window, NULL); + eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context); + + platform.contextRebindRequired = false; + } + else + { + CORE.Window.display.width = ANativeWindow_getWidth(platform.app->window); + CORE.Window.display.height = ANativeWindow_getHeight(platform.app->window); + + // Initialize graphics device (display device and OpenGL context) + InitGraphicsDevice(); + + // Initialize OpenGL context (states and resources) + // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, stored as globals in rlgl + rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); + + // Setup default viewport + // NOTE: It updated CORE.Window.render.width and CORE.Window.render.height + SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); + + // Initialize hi-res timer + InitTimer(); + + #if SUPPORT_MODULE_RTEXT + // Load default font + // WARNING: External function: Module required: rtext + LoadFontDefault(); + #if SUPPORT_MODULE_RSHAPES + // Set font white rectangle for shapes drawing, so shapes and text can be batched together + // WARNING: rshapes module is required, if not available, default internal white rectangle is used + Rectangle rec = GetFontDefault().recs[95]; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) + { + // NOTE: Trying to maxime rec padding to avoid pixel bleeding on MSAA filtering + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 }); + } + else + { + // NOTE: Setting up a 1px padding on char rectangle to avoid pixel bleeding + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); + } + #endif + #else + #if SUPPORT_MODULE_RSHAPES + // Set default texture and rectangle to be used for shapes drawing + // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 + Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; + SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); // WARNING: Module required: rshapes + #endif + #endif + + // Initialize random seed + SetRandomSeed((unsigned int)time(NULL)); + } + } + } break; + case APP_CMD_GAINED_FOCUS: + { + platform.appEnabled = true; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); + //ResumeMusicStream(); + } break; + case APP_CMD_PAUSE: break; + case APP_CMD_LOST_FOCUS: + { + platform.appEnabled = false; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); + //PauseMusicStream(); + } break; + case APP_CMD_TERM_WINDOW: + { + // Detach OpenGL context and destroy display surface + // NOTE 1: This case is used when the user exits the app without closing it, context is detached to ensure everything is recoverable upon resuming + // NOTE 2: Detaching context before destroying display surface avoids losing our resources (textures, shaders, VBOs...) + // NOTE 3: In some cases (too many context loaded), OS could unload context automatically... :( + if (platform.device != EGL_NO_DISPLAY) + { + eglMakeCurrent(platform.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (platform.surface != EGL_NO_SURFACE) + { + eglDestroySurface(platform.device, platform.surface); + platform.surface = EGL_NO_SURFACE; + } + + platform.contextRebindRequired = true; + } + // If 'platform.device' is already set to 'EGL_NO_DISPLAY' + // this means that the user has already called 'CloseWindow()' + + } break; + case APP_CMD_SAVE_STATE: break; + case APP_CMD_STOP: break; + case APP_CMD_DESTROY: break; + case APP_CMD_CONFIG_CHANGED: + { + //AConfiguration_fromAssetManager(platform.app->config, platform.app->activity->assetManager); + //print_cur_config(platform.app); + + // Check screen orientation here! + } break; + default: break; + } +} + +// ANDROID: Map Android gamepad button to raylib gamepad button +static GamepadButton AndroidTranslateGamepadButton(int button) +{ + switch (button) + { + case AKEYCODE_BUTTON_A: return GAMEPAD_BUTTON_RIGHT_FACE_DOWN; + case AKEYCODE_BUTTON_B: return GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; + case AKEYCODE_BUTTON_X: return GAMEPAD_BUTTON_RIGHT_FACE_LEFT; + case AKEYCODE_BUTTON_Y: return GAMEPAD_BUTTON_RIGHT_FACE_UP; + case AKEYCODE_BUTTON_L1: return GAMEPAD_BUTTON_LEFT_TRIGGER_1; + case AKEYCODE_BUTTON_R1: return GAMEPAD_BUTTON_RIGHT_TRIGGER_1; + case AKEYCODE_BUTTON_L2: return GAMEPAD_BUTTON_LEFT_TRIGGER_2; + case AKEYCODE_BUTTON_R2: return GAMEPAD_BUTTON_RIGHT_TRIGGER_2; + case AKEYCODE_BUTTON_THUMBL: return GAMEPAD_BUTTON_LEFT_THUMB; + case AKEYCODE_BUTTON_THUMBR: return GAMEPAD_BUTTON_RIGHT_THUMB; + case AKEYCODE_BUTTON_START: return GAMEPAD_BUTTON_MIDDLE_RIGHT; + case AKEYCODE_BUTTON_SELECT: return GAMEPAD_BUTTON_MIDDLE_LEFT; + case AKEYCODE_BUTTON_MODE: return GAMEPAD_BUTTON_MIDDLE; + // On some (most?) gamepads dpad events are reported as axis motion instead + case AKEYCODE_DPAD_DOWN: return GAMEPAD_BUTTON_LEFT_FACE_DOWN; + case AKEYCODE_DPAD_RIGHT: return GAMEPAD_BUTTON_LEFT_FACE_RIGHT; + case AKEYCODE_DPAD_LEFT: return GAMEPAD_BUTTON_LEFT_FACE_LEFT; + case AKEYCODE_DPAD_UP: return GAMEPAD_BUTTON_LEFT_FACE_UP; + default: return GAMEPAD_BUTTON_UNKNOWN; + } +} + +// ANDROID: Get input events +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) +{ + // If additional inputs are required check: + // REF: https://developer.android.com/ndk/reference/group/input + // REF: https://developer.android.com/training/game-controllers/controller-input + + int type = AInputEvent_getType(event); + int source = AInputEvent_getSource(event); + + if (type == AINPUT_EVENT_TYPE_MOTION) + { + if (FLAG_IS_SET(source, AINPUT_SOURCE_JOYSTICK) || + FLAG_IS_SET(source, AINPUT_SOURCE_GAMEPAD)) + { + // Assuming a single gamepad, "detected" on its input event + CORE.Input.Gamepad.ready[0] = true; + + CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_X] = AMotionEvent_getAxisValue( + event, AMOTION_EVENT_AXIS_X, 0); + CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_Y] = AMotionEvent_getAxisValue( + event, AMOTION_EVENT_AXIS_Y, 0); + CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_X] = AMotionEvent_getAxisValue( + event, AMOTION_EVENT_AXIS_Z, 0); + CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_Y] = AMotionEvent_getAxisValue( + event, AMOTION_EVENT_AXIS_RZ, 0); + CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_TRIGGER] = AMotionEvent_getAxisValue( + event, AMOTION_EVENT_AXIS_BRAKE, 0)*2.0f - 1.0f; + CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_TRIGGER] = AMotionEvent_getAxisValue( + event, AMOTION_EVENT_AXIS_GAS, 0)*2.0f - 1.0f; + + // dpad is reported as an axis on android + float dpadX = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_X, 0); + float dpadY = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_Y, 0); + + if (dpadX == 1.0f) + { + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 1; + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0; + } + else if (dpadX == -1.0f) + { + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0; + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 1; + } + else + { + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0; + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0; + } + + if (dpadY == 1.0f) + { + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 1; + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0; + } + else if (dpadY == -1.0f) + { + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0; + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 1; + } + else + { + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0; + CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0; + } + + return 1; // Handled gamepad axis motion + } + } + else if (type == AINPUT_EVENT_TYPE_KEY) + { + int32_t keycode = AKeyEvent_getKeyCode(event); + //int32_t AKeyEvent_getMetaState(event); + + // Handle gamepad button presses and releases. AOSP stamps the + // KEYBOARD source bit on every key event from a gamepad, so + // discriminate on the keycode rather than gating on source bits. + if (FLAG_IS_SET(source, AINPUT_SOURCE_JOYSTICK) || + FLAG_IS_SET(source, AINPUT_SOURCE_GAMEPAD)) + { + GamepadButton button = AndroidTranslateGamepadButton(keycode); + + if (button != GAMEPAD_BUTTON_UNKNOWN) + { + // Assuming a single gamepad, "detected" on its input event + CORE.Input.Gamepad.ready[0] = true; + + if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) + { + CORE.Input.Gamepad.currentButtonState[0][button] = 1; + } + else CORE.Input.Gamepad.currentButtonState[0][button] = 0; // Key up + + return 1; // Handled gamepad button + } + // Unknown keycode: fall through to the keyboard handler below. + } + + KeyboardKey key = ((keycode > 0) && (keycode < KEYCODE_MAP_SIZE))? mapKeycode[keycode] : KEY_NULL; + if (key != KEY_NULL) + { + // Save current key and its state + // NOTE: Android key action is 0 for down and 1 for up + if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) + { + CORE.Input.Keyboard.currentKeyState[key] = 1; // Key down + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_MULTIPLE) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1; + else CORE.Input.Keyboard.currentKeyState[key] = 0; // Key up + } + + if (keycode == AKEYCODE_POWER) + { + // Let the OS handle input to avoid app stuck. Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS + // Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS + // It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected + // NOTE: AndroidManifest.xml must have + // Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour + return 0; + } + else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU)) + { + // Eat BACK_BUTTON and AKEYCODE_MENU, do nothing... and don't let to be handled by OS! + return 1; + } + else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN)) + { + // Set default OS behaviour + return 0; + } + + return 0; + } + + // Register touch points count + touchRaw.pointCount = AMotionEvent_getPointerCount(event); + + for (int i = 0; (i < touchRaw.pointCount) && (i < MAX_TOUCH_POINTS); i++) + { + // Register touch points id + touchRaw.pointId[i] = AMotionEvent_getPointerId(event, i); + + // Register touch points position + touchRaw.position[i] = (Vector2){ AMotionEvent_getX(event, i), AMotionEvent_getY(event, i) }; + + // Normalize CORE.Input.Touch.position[i] for CORE.Window.screen.width and CORE.Window.screen.height + float widthRatio = (float)(CORE.Window.screen.width + CORE.Window.renderOffset.x)/(float)CORE.Window.display.width; + float heightRatio = (float)(CORE.Window.screen.height + CORE.Window.renderOffset.y)/(float)CORE.Window.display.height; + touchRaw.position[i].x = touchRaw.position[i].x*widthRatio - (float)CORE.Window.renderOffset.x/2; + touchRaw.position[i].y = touchRaw.position[i].y*heightRatio - (float)CORE.Window.renderOffset.y/2; + } + + int32_t action = AMotionEvent_getAction(event); + unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; + int32_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + + if (flags == AMOTION_EVENT_ACTION_HOVER_ENTER) + { + // The new pointer is hover + // So add it to hoverPoints + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { + if (touchRaw.hoverPoints[i] == -1) + { + touchRaw.hoverPoints[i] = touchRaw.pointId[pointerIndex]; + break; + } + } + } + +#if SUPPORT_GESTURES_SYSTEM + GestureEvent gestureEvent = { 0 }; + + gestureEvent.pointCount = 0; + + // Register touch actions + if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_ACTION_DOWN; + else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_ACTION_UP; + else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; + else if (flags == AMOTION_EVENT_ACTION_CANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; + + for (int i = 0; (i < touchRaw.pointCount) && (i < MAX_TOUCH_POINTS); i++) + { + // If the touchPoint is hover, Ignore it + bool hover = false; + for (int j = 0; j < MAX_TOUCH_POINTS; j++) + { + // Check if the touchPoint is in hoverPointers + if (touchRaw.hoverPoints[j] == touchRaw.pointId[i]) + { + hover = true; + break; + } + } + if (hover) continue; + + gestureEvent.pointId[gestureEvent.pointCount] = touchRaw.pointId[i]; + gestureEvent.position[gestureEvent.pointCount] = touchRaw.position[i]; + gestureEvent.position[gestureEvent.pointCount].x /= (float)GetScreenWidth(); + gestureEvent.position[gestureEvent.pointCount].y /= (float)GetScreenHeight(); + gestureEvent.pointCount++; + } + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif + + if (flags == AMOTION_EVENT_ACTION_HOVER_EXIT) + { + // Hover exited. So, remove it from hoverPoints + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { + if (touchRaw.hoverPoints[i] == touchRaw.pointId[pointerIndex]) + { + touchRaw.hoverPoints[i] = -1; + break; + } + } + } + + if ((flags == AMOTION_EVENT_ACTION_POINTER_UP) || (flags == AMOTION_EVENT_ACTION_UP)) + { + // One of the touchpoints is released, remove it from touch point arrays + for (int i = pointerIndex; (i < touchRaw.pointCount - 1) && (i < MAX_TOUCH_POINTS - 1); i++) + { + touchRaw.pointId[i] = touchRaw.pointId[i+1]; + touchRaw.position[i] = touchRaw.position[i+1]; + } + touchRaw.pointCount--; + } + + CORE.Input.Touch.pointCount = 0; + for (int i = 0; (i < touchRaw.pointCount) && (i < MAX_TOUCH_POINTS); i++) + { + // If the touchPoint is hover, Ignore it + bool hover = false; + for (int j = 0; j < MAX_TOUCH_POINTS; j++) + { + // Check if the touchPoint is in hoverPointers + if (touchRaw.hoverPoints[j] == touchRaw.pointId[i]) + { + hover = true; + break; + } + } + if (hover) continue; + + CORE.Input.Touch.pointId[CORE.Input.Touch.pointCount] = touchRaw.pointId[i]; + CORE.Input.Touch.position[CORE.Input.Touch.pointCount] = touchRaw.position[i]; + CORE.Input.Touch.pointCount++; + } + + // When all touchpoints are tapped and released really quickly, this event is generated + if (flags == AMOTION_EVENT_ACTION_CANCEL) CORE.Input.Touch.pointCount = 0; + + if (CORE.Input.Touch.pointCount > 0) CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 1; + else CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 0; + + // Stores the previous position of touch[0] only while it's active to calculate the delta + if (flags == AMOTION_EVENT_ACTION_MOVE) CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + else CORE.Input.Mouse.previousPosition = CORE.Input.Touch.position[0]; + + // Map touch[0] as mouse input for convenience + CORE.Input.Mouse.currentPosition = CORE.Input.Touch.position[0]; + CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; + + return 0; +} + +// Compute framebuffer size relative to screen size and display size +// NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified +static void SetupFramebuffer(int width, int height) +{ + // Calculate CORE.Window.render.width and CORE.Window.render.height, having the display size (input params) and the desired screen size (global var) + if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) + { + 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); + + // Downscaling to fit display with border-bars + float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width; + float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height; + + if (widthRatio <= heightRatio) + { + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio); + CORE.Window.render.height = CORE.Window.display.height; + CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width); + CORE.Window.renderOffset.y = 0; + } + + // Screen scaling required + float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width; + CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f); + + // NOTE: Rendering to full display resolution + // Above parameters need to be calculate for downscale matrix and offsets + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = CORE.Window.display.height; + + TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height); + } + else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height)) + { + // Required screen size is smaller than display size + 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); + + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) + { + CORE.Window.screen.width = CORE.Window.display.width; + CORE.Window.screen.height = CORE.Window.display.height; + } + + // Upscaling to fit display with border-bars + float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; + float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; + + if (displayRatio <= screenRatio) + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio); + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width); + CORE.Window.renderOffset.y = 0; + } + } + else + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = 0; + } +} + +// Replacement for fopen(), used as linker wrap entry point (-Wl,--wrap=fopen) +// REF: https://developer.android.com/ndk/reference/group/asset +__attribute__((visibility("default"))) FILE *__wrap_fopen(const char *fileName, const char *mode) +{ + FILE *file = NULL; + + // NOTE: AAsset provides access to read-only asset, write operations use regular fopen + if (mode[0] == 'w') + { + file = __real_fopen(TextFormat("%s/%s", platform.app->activity->internalDataPath, fileName), mode); + if (file == NULL) file = __real_fopen(fileName, mode); + } + else + { + AAsset *asset = AAssetManager_open(platform.app->activity->assetManager, fileName, AASSET_MODE_UNKNOWN); + + if (asset != NULL) + { + // Get pointer to file in the assets + file = funopen(asset, android_read, android_write, android_seek, android_close); + } + else + { + // Do a regular open if file is not found in the assets + file = __real_fopen(TextFormat("%s/%s", platform.app->activity->internalDataPath, fileName), mode); + if (file == NULL) file = __real_fopen(fileName, mode); + } + } + + return file; +} + +static int android_read(void *cookie, char *data, int dataSize) +{ + return AAsset_read((AAsset *)cookie, data, dataSize); +} + +static int android_write(void *cookie, const char *data, int dataSize) +{ + TRACELOG(LOG_WARNING, "ANDROID: Failed to provide write access to APK"); + + return EACCES; +} + +static fpos_t android_seek(void *cookie, fpos_t offset, int whence) +{ + return AAsset_seek((AAsset *)cookie, offset, whence); +} + +static int android_close(void *cookie) +{ + AAsset_close((AAsset *)cookie); + return 0; +} + +// EOF diff --git a/src/libraries/raylib/raylib/src/platforms/rcore_desktop_glfw.c b/src/libraries/raylib/raylib/src/platforms/rcore_desktop_glfw.c new file mode 100644 index 0000000..7958201 --- /dev/null +++ b/src/libraries/raylib/raylib/src/platforms/rcore_desktop_glfw.c @@ -0,0 +1,2237 @@ +/********************************************************************************************** +* +* rcore_desktop_glfw - Functions to manage window, graphics device and inputs +* +* PLATFORM: DESKTOP: GLFW +* - Windows (Win32, Win64) +* - Linux (X11/Wayland desktop mode) +* - FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop) +* - OSX/macOS (x64, arm64) +* +* LIMITATIONS: +* - Limitation 01 +* - Limitation 02 +* +* POSSIBLE IMPROVEMENTS: +* - Improvement 01 +* - Improvement 02 +* +* CONFIGURATION: +* #define RCORE_PLATFORM_CUSTOM_FLAG +* Custom flag for rcore on target platform -not used- +* +* DEPENDENCIES: +* - rglfw: Manage graphic device, OpenGL context and inputs (Windows, Linux, OSX/macOS, FreeBSD...) +* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 + // NOTE: Already provided by rlgl implementation (on glad.h) +#include "GLFW/glfw3.h" // GLFW3 library: Windows, OpenGL context and Input management + // NOTE: GLFW3 already includes gl.h (OpenGL) headers + +// Support retrieving native window handlers +#if defined(_WIN32) + #if !defined(HWND) && !defined(_MSVC_LANG) + #define HWND void* + #elif !defined(HWND) && defined(_MSVC_LANG) + typedef struct HWND__ *HWND; + #endif + + #include "../external/win32_clipboard.h" // Clipboard image copy-paste + + #define GLFW_EXPOSE_NATIVE_WIN32 + #define GLFW_NATIVE_INCLUDE_NONE // To avoid some symbols re-definition in windows.h + #include "GLFW/glfw3native.h" + + #if SUPPORT_WINMM_HIGHRES_TIMER && !SUPPORT_BUSY_WAIT_LOOP + // NOTE: Those functions require linking with winmm library + //#pragma warning(disable: 4273) + __declspec(dllimport) unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); + //#pragma warning(default: 4273) + #endif +#endif +#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) + #include // Required for: timespec, nanosleep(), select() - POSIX + + #if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) + // Set appropriate expose macros based on available backends + #if defined(_GLFW_X11) + #define GLFW_EXPOSE_NATIVE_X11 + #define Font X11Font // Hack to fix 'Font' name collision + // The definition and references to the X11 Font type will be replaced by 'X11Font' + // Works as long as the current file consistently references any X11 Font as X11Font + // Since it is never referenced (as of writing), this does not pose an issue + #endif + + #if defined(_GLFW_WAYLAND) + #define GLFW_EXPOSE_NATIVE_WAYLAND + #endif + + #include "GLFW/glfw3native.h" // Include native header only once, regardless of how many backends are defined + // Required for: glfwGetX11Window() and glfwGetWaylandWindow() + #if defined(_GLFW_X11) // Clean up X11-specific hacks + #undef Font // Revert hack and allow normal raylib Font usage + #endif + #endif +#endif +#if defined(__APPLE__) + #include // Required for: usleep() + + //#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition + void *glfwGetCocoaWindow(GLFWwindow *handle); + #include "GLFW/glfw3native.h" // Required for: glfwGetCocoaWindow() +#endif + +#include // Required for: size_t + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct { + GLFWwindow *handle; // GLFW window handle (graphic device) +#if defined(__linux__) && defined(_GLFW_X11) + // Local storage for the window handle returned by glfwGetX11Window + // This is needed as X11 handles are integers and may not fit inside a pointer depending on platform + // Storing the handle locally and returning a pointer in GetWindowHandle allows the code to work regardless of pointer width + Window windowHandleX11; // Underlying type: unsigned long (XID, Window) +#endif +} PlatformData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context + +static PlatformData platform = { 0 }; // Platform specific data + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +void ClosePlatform(void); // Close platform + +// Error callback event +static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error + +// Window callbacks events +static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized +static void FramebufferSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 FramebufferSize Callback, runs when window is resized +static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley); // GLFW3 Window Content Scale Callback, runs when window changes scale +static void WindowPosCallback(GLFWwindow *window, int x, int y); // GLFW3 WindowPos Callback, runs when window is moved +static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored +static void WindowMaximizeCallback(GLFWwindow *window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized +static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 WindowFocus Callback, runs when window get/lose focus +static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window + +// Input callbacks events +static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed +static void CharCallback(GLFWwindow *window, unsigned int codepoint); // GLFW3 Char Callback, runs on key pressed (get codepoint value) +static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed +static void MouseCursorPosCallback(GLFWwindow *window, double x, double y); // GLFW3 Cursor Position Callback, runs on mouse move +static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Scrolling Callback, runs on mouse wheel +static void CursorEnterCallback(GLFWwindow *window, int entered); // GLFW3 Cursor Enter Callback, cursor enters client area +static void JoystickCallback(int jid, int event); // GLFW3 Joystick Connected/Disconnected Callback + +// Memory allocator wrappers [used by glfwInitAllocator()] +static void *AllocateWrapper(size_t size, void *user); // GLFW3 GLFWallocatefun, wrapps around RL_CALLOC macro +static void *ReallocateWrapper(void *block, size_t size, void *user); // GLFW3 GLFWreallocatefun, wrapps around RL_REALLOC macro +static void DeallocateWrapper(void *block, void *user); // GLFW3 GLFWdeallocatefun, wraps around RL_FREE macro + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +// NOTE: By default, if KEY_ESCAPE pressed or window close icon clicked +bool WindowShouldClose(void) +{ + if (CORE.Window.ready) return CORE.Window.shouldClose; + else return true; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + { + // Store previous screen data (in case exiting fullscreen) + CORE.Window.previousPosition = CORE.Window.position; + CORE.Window.previousScreen = CORE.Window.screen; + + // Use current monitor the window is on to get fullscreen required size + int monitorCount = 0; + int monitorIndex = GetCurrentMonitor(); + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + GLFWmonitor *monitor = (monitorIndex < monitorCount)? monitors[monitorIndex] : NULL; + + if (monitor != NULL) + { + // Get current monitor video mode + const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitorIndex]); + CORE.Window.display.width = mode->width; + CORE.Window.display.height = mode->height; + + CORE.Window.position = (Point){ 0, 0 }; + CORE.Window.screen = CORE.Window.display; + + // Set fullscreen flag to be processed on FramebufferSizeCallback() accordingly + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + +#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) + // NOTE: X11 requires undecorating the window before switching to + // fullscreen to avoid issues with framebuffer scaling + glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_FALSE); + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); +#endif + // WARNING: This function launches FramebufferSizeCallback() + glfwSetWindowMonitor(platform.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor"); + } + else + { + // Restore previous window position and size + CORE.Window.position = CORE.Window.previousPosition; + CORE.Window.screen = CORE.Window.previousScreen; + + // Set fullscreen flag to be processed on FramebufferSizeCallback() accordingly + // and considered by GetWindowScaleDPI() + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + +#if !defined(__APPLE__) && !defined(_GLFW_WAYLAND) + // Make sure to restore render size considering HighDPI scaling + // NOTE: On Wayland, GLFW_SCALE_FRAMEBUFFER handles scaling, skip manual resize + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + Vector2 scaleDpi = GetWindowScaleDPI(); + CORE.Window.screen.width = (unsigned int)(CORE.Window.screen.width*scaleDpi.x); + CORE.Window.screen.height = (unsigned int)(CORE.Window.screen.height*scaleDpi.y); + } +#endif + + // WARNING: This function launches FramebufferSizeCallback() + glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, + CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + +#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) + // NOTE: X11 requires restoring the decorated window after switching from + // fullscreen to avoid issues with framebuffer scaling + glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_TRUE); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); +#endif + } + + // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) + // NOTE: V-Sync can be enabled by graphic driver configuration + if (FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) glfwSwapInterval(1); +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + // Leave fullscreen before attempting to set borderless windowed mode + // NOTE: Fullscreen already saves the previous position so it does not need to be set again later + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) ToggleFullscreen(); + + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + const int monitor = GetCurrentMonitor(); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); + + if (mode != NULL) + { + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + // Store screen position and size + // NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here + CORE.Window.previousPosition = CORE.Window.position; + CORE.Window.previousScreen = CORE.Window.screen; + + // Set undecorated flag + glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_FALSE); + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); + + // Get monitor position and size + glfwGetMonitorPos(monitors[monitor], &CORE.Window.position.x, &CORE.Window.position.y); + CORE.Window.screen.width = mode->width; + CORE.Window.screen.height = mode->height; + + // Set screen position and size + #if defined(_WIN32) + // NOTE: To prevent the fullscreen window from always staying on top, don't pass a monitor at this point. + glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, + CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate); + #else + glfwSetWindowMonitor(platform.handle, monitors[monitor], CORE.Window.position.x, CORE.Window.position.y, + CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate); + #endif + + // Refocus window + glfwFocusWindow(platform.handle); + + FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + else + { + // Restore previous screen values + CORE.Window.position = CORE.Window.previousPosition; + CORE.Window.screen = CORE.Window.previousScreen; + + // Remove undecorated flag + glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_TRUE); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); + + #if !defined(__APPLE__) && !defined(_GLFW_WAYLAND) + // Make sure to restore size considering HighDPI scaling + // NOTE: On Wayland, GLFW_SCALE_FRAMEBUFFER handles scaling, skip manual resize + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + Vector2 scaleDpi = GetWindowScaleDPI(); + CORE.Window.screen.width = (unsigned int)(CORE.Window.screen.width*scaleDpi.x); + CORE.Window.screen.height = (unsigned int)(CORE.Window.screen.height*scaleDpi.y); + } + #endif + + // Return to previous screen size and position + glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, + CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate); + + // Refocus window + glfwFocusWindow(platform.handle); + + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + if (glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) + { + glfwMaximizeWindow(platform.handle); + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + // NOTE: Following function launches callback that sets appropriate flag! + glfwIconifyWindow(platform.handle); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + if (glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) + { + // Restores the specified window if it was previously iconified (minimized) or maximized + glfwRestoreWindow(platform.handle); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); + + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT) != FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) && FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + glfwSwapInterval(1); + FLAG_SET(CORE.Window.flags, FLAG_VSYNC_HINT); + } + + // State change: FLAG_BORDERLESS_WINDOWED_MODE + // NOTE: This must be handled before FLAG_FULLSCREEN_MODE because ToggleBorderlessWindowed() needs to get some fullscreen values if fullscreen is running + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE) != FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) && FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + ToggleBorderlessWindowed(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_FULLSCREEN_MODE + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE) != FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) && FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + ToggleFullscreen(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_RESIZABLE + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) && FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) + { + glfwSetWindowAttrib(platform.handle, GLFW_RESIZABLE, GLFW_TRUE); + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); + } + + // State change: FLAG_WINDOW_UNDECORATED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED) != FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) && FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_FALSE); + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); + } + + // State change: FLAG_WINDOW_HIDDEN + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN) != FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) && FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + glfwHideWindow(platform.handle); + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); + } + + // State change: FLAG_WINDOW_MINIMIZED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED) != FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) && FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + //GLFW_ICONIFIED + MinimizeWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_MAXIMIZED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED) != FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) && FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) + { + //GLFW_MAXIMIZED + MaximizeWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_UNFOCUSED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED) != FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) && FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) + { + glfwSetWindowAttrib(platform.handle, GLFW_FOCUS_ON_SHOW, GLFW_FALSE); + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); + } + + // State change: FLAG_WINDOW_TOPMOST + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST) != FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) && FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) + { + glfwSetWindowAttrib(platform.handle, GLFW_FLOATING, GLFW_TRUE); + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST); + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN) != FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) && FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); + } + + // The following states can not be changed after window creation + + // State change: FLAG_WINDOW_TRANSPARENT + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT) != FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) && FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI) != FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) && FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization"); + } + + // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH) != FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) && FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + glfwSetWindowAttrib(platform.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH); + } + + // State change: FLAG_MSAA_4X_HINT + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT) != FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) && FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + TRACELOG(LOG_WARNING, "WINDOW: MSAA can only be configured before window initialization"); + } + + // State change: FLAG_INTERLACED_HINT + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_INTERLACED_HINT) != FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) && FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "WINDOW: Interlaced mode can only be configured before window initialization"); + } +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) && (FLAG_IS_SET(flags, FLAG_VSYNC_HINT))) + { + glfwSwapInterval(0); + FLAG_CLEAR(CORE.Window.flags, FLAG_VSYNC_HINT); + } + + // State change: FLAG_BORDERLESS_WINDOWED_MODE + // NOTE: This must be handled before FLAG_FULLSCREEN_MODE because ToggleBorderlessWindowed() needs to get some fullscreen values if fullscreen is running + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) && (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE))) + { + ToggleBorderlessWindowed(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_FULLSCREEN_MODE + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) && (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE))) + { + ToggleFullscreen(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_RESIZABLE + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) && (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE))) + { + glfwSetWindowAttrib(platform.handle, GLFW_RESIZABLE, GLFW_FALSE); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); + } + + // State change: FLAG_WINDOW_HIDDEN + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) && (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN))) + { + glfwShowWindow(platform.handle); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); + } + + // State change: FLAG_WINDOW_MINIMIZED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED))) + { + RestoreWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_MAXIMIZED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED))) + { + RestoreWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_UNDECORATED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED))) + { + glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_TRUE); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); + } + + // State change: FLAG_WINDOW_UNFOCUSED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED))) + { + glfwSetWindowAttrib(platform.handle, GLFW_FOCUS_ON_SHOW, GLFW_TRUE); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); + } + + // State change: FLAG_WINDOW_TOPMOST + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) && (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST))) + { + glfwSetWindowAttrib(platform.handle, GLFW_FLOATING, GLFW_FALSE); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_TOPMOST); + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN)) && (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN))) + { + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); + } + + // The following states can not be changed after window creation + + // State change: FLAG_WINDOW_TRANSPARENT + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT)) && (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT))) + { + TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) && (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI))) + { + TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization"); + } + + // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH))) + { + glfwSetWindowAttrib(platform.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH); + } + + // State change: FLAG_MSAA_4X_HINT + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) && (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT))) + { + TRACELOG(LOG_WARNING, "WINDOW: MSAA can only be configured before window initialization"); + } + + // State change: FLAG_INTERLACED_HINT + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_INTERLACED_HINT)) && (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT))) + { + TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only be configured before window initialization"); + } +} + +// Set icon for window +// NOTE 1: Image must be in RGBA format, 8bit per channel +// NOTE 2: Image is scaled by the OS for all required sizes +void SetWindowIcon(Image image) +{ + if (image.data == NULL) + { + // Revert to the default window icon, pass in an empty image array + glfwSetWindowIcon(platform.handle, 0, NULL); + } + else + { + if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + GLFWimage icon[1] = { 0 }; + + icon[0].width = image.width; + icon[0].height = image.height; + icon[0].pixels = (unsigned char *)image.data; + + // NOTE 1: Only one image icon supported + // NOTE 2: The specified image data is copied before this function returns + glfwSetWindowIcon(platform.handle, 1, icon); + } + else TRACELOG(LOG_WARNING, "GLFW: Window icon image must be in R8G8B8A8 pixel format"); + } +} + +// Set icon for window, multiple images +// NOTE 1: Images must be in RGBA format, 8bit per channel +// NOTE 2: The multiple images are used depending on provided sizes +// Standard Windows icon sizes: 256, 128, 96, 64, 48, 32, 24, 16 +void SetWindowIcons(Image *images, int count) +{ + if ((images == NULL) || (count <= 0)) + { + // Revert to the default window icon, pass in an empty image array + glfwSetWindowIcon(platform.handle, 0, NULL); + } + else + { + int valid = 0; + GLFWimage *icons = (GLFWimage *)RL_CALLOC(count, sizeof(GLFWimage)); + + for (int i = 0; i < count; i++) + { + if (images[i].format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + icons[valid].width = images[i].width; + icons[valid].height = images[i].height; + icons[valid].pixels = (unsigned char *)images[i].data; + + valid++; + } + else TRACELOG(LOG_WARNING, "GLFW: Window icon image must be in R8G8B8A8 pixel format"); + } + // NOTE: Images data is copied internally before this function returns + glfwSetWindowIcon(platform.handle, valid, icons); + + RL_FREE(icons); + } +} + +// Set title for window +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; + glfwSetWindowTitle(platform.handle, title); +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + // Update CORE.Window.position as well + CORE.Window.position.x = x; + CORE.Window.position.y = y; + glfwSetWindowPos(platform.handle, x, y); +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + { + TRACELOG(LOG_INFO, "GLFW: Selected fullscreen monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); + + const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); + glfwSetWindowMonitor(platform.handle, monitors[monitor], 0, 0, mode->width, mode->height, mode->refreshRate); + } + else + { + TRACELOG(LOG_INFO, "GLFW: Selected monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); + + // Here the render width has to be used again in case high dpi flag is enabled + const int screenWidth = CORE.Window.render.width; + const int screenHeight = CORE.Window.render.height; + int monitorWorkareaX = 0; + int monitorWorkareaY = 0; + int monitorWorkareaWidth = 0; + int monitorWorkareaHeight = 0; + glfwGetMonitorWorkarea(monitors[monitor], &monitorWorkareaX, &monitorWorkareaY, &monitorWorkareaWidth, &monitorWorkareaHeight); + + // If the screen size is larger than the monitor workarea, anchor it on the top left corner, otherwise, center it + if ((screenWidth >= monitorWorkareaWidth) || (screenHeight >= monitorWorkareaHeight)) glfwSetWindowPos(platform.handle, monitorWorkareaX, monitorWorkareaY); + else + { + const int x = monitorWorkareaX + (monitorWorkareaWidth/2) - (screenWidth/2); + const int y = monitorWorkareaY + (monitorWorkareaHeight/2) - (screenHeight/2); + glfwSetWindowPos(platform.handle, x, y); + } + } + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; + + int minWidth = (CORE.Window.screenMin.width == 0)? GLFW_DONT_CARE : (int)CORE.Window.screenMin.width; + int minHeight = (CORE.Window.screenMin.height == 0)? GLFW_DONT_CARE : (int)CORE.Window.screenMin.height; + int maxWidth = (CORE.Window.screenMax.width == 0)? GLFW_DONT_CARE : (int)CORE.Window.screenMax.width; + int maxHeight = (CORE.Window.screenMax.height == 0)? GLFW_DONT_CARE : (int)CORE.Window.screenMax.height; + + glfwSetWindowSizeLimits(platform.handle, minWidth, minHeight, maxWidth, maxHeight); +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; + + int minWidth = (CORE.Window.screenMin.width == 0)? GLFW_DONT_CARE : (int)CORE.Window.screenMin.width; + int minHeight = (CORE.Window.screenMin.height == 0)? GLFW_DONT_CARE : (int)CORE.Window.screenMin.height; + int maxWidth = (CORE.Window.screenMax.width == 0)? GLFW_DONT_CARE : (int)CORE.Window.screenMax.width; + int maxHeight = (CORE.Window.screenMax.height == 0)? GLFW_DONT_CARE : (int)CORE.Window.screenMax.height; + + glfwSetWindowSizeLimits(platform.handle, minWidth, minHeight, maxWidth, maxHeight); +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + + glfwSetWindowSize(platform.handle, width, height); +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + if (opacity >= 1.0f) opacity = 1.0f; + else if (opacity <= 0.0f) opacity = 0.0f; + glfwSetWindowOpacity(platform.handle, opacity); +} + +// Set window focused +void SetWindowFocused(void) +{ + glfwFocusWindow(platform.handle); +} + +// Get native window handle +void *GetWindowHandle(void) +{ + void *handle = NULL; + +#if defined(_WIN32) + handle = glfwGetWin32Window(platform.handle); // Type: HWND +#endif +#if defined(__linux__) + #if defined(_GLFW_WAYLAND) + #if defined(_GLFW_X11) + int platformID = glfwGetPlatform(); + if (platformID == GLFW_PLATFORM_WAYLAND) + { + handle = (void *)glfwGetWaylandWindow(platform.handle); // Type: struct wl_surface* + } + else + { + platform.windowHandleX11 = glfwGetX11Window(platform.handle); // Type: Window (unsigned long) + handle = &platform.windowHandleX11; + } + #else + handle = (void *)glfwGetWaylandWindow(platform.handle); + #endif + #elif defined(_GLFW_X11) + // Store the window handle locally and return a pointer to the variable instead + platform.windowHandleX11 = glfwGetX11Window(platform.handle); + handle = &platform.windowHandleX11; + #endif +#endif +#if defined(__APPLE__) + handle = (void *)glfwGetCocoaWindow(platform.handle); // Type: NSWindow* +#endif + + return handle; +} + +// Get number of monitors +int GetMonitorCount(void) +{ + int monitorCount = 0; + + glfwGetMonitors(&monitorCount); + + return monitorCount; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + int index = 0; + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + GLFWmonitor *monitor = NULL; + + if (monitorCount >= 1) + { + if (IsWindowFullscreen()) + { + // Get the handle of the monitor that the specified window is in full screen on + monitor = glfwGetWindowMonitor(platform.handle); + + for (int i = 0; i < monitorCount; i++) + { + if (monitors[i] == monitor) + { + index = i; + break; + } + } + } + else + { + // In case the window is between two monitors, below logic is used + // to try to detect the "current monitor" for that window, note that + // this is probably an overengineered solution for a side case + // trying to match SDL behaviour + + unsigned int closestDist = 0xFFFFFFFFu; + + // Window center position + int wcx = 0; + int wcy = 0; + + glfwGetWindowPos(platform.handle, &wcx, &wcy); + wcx += (int)CORE.Window.screen.width/2; + wcy += (int)CORE.Window.screen.height/2; + + for (int i = 0; i < monitorCount; i++) + { + // Monitor top-left position + int mx = 0; + int my = 0; + + monitor = monitors[i]; + glfwGetMonitorPos(monitor, &mx, &my); + const GLFWvidmode *mode = glfwGetVideoMode(monitor); + + if (mode) + { + const int right = mx + mode->width - 1; + const int bottom = my + mode->height - 1; + + if ((wcx >= mx) && + (wcx <= right) && + (wcy >= my) && + (wcy <= bottom)) + { + index = i; + break; + } + + int xclosest = wcx; + if (wcx < mx) xclosest = mx; + else if (wcx > right) xclosest = right; + + int yclosest = wcy; + if (wcy < my) yclosest = my; + else if (wcy > bottom) yclosest = bottom; + + int dx = wcx - xclosest; + int dy = wcy - yclosest; + + // Unsigned to dodge signed overflow UB; (-x)^2 == x^2 mod 2^32 so sign drops out. + // If |dx| or |dy| >= 65536, dist wraps and the wrong monitor may win. + // Not a concern for realistic monitor layouts. + unsigned int ux = (unsigned int)dx; + unsigned int uy = (unsigned int)dy; + unsigned int dist = ux*ux + uy*uy; + + if (dist < closestDist) + { + index = i; + closestDist = dist; + } + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); + } + } + } + + return index; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int x = 0; + int y = 0; + glfwGetMonitorPos(monitors[monitor], &x, &y); + + return (Vector2){ (float)x, (float)y }; + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + int width = 0; + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); + + if (mode) width = mode->width; + else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); + + return width; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + int height = 0; + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); + + if (mode) height = mode->height; + else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); + + return height; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + int width = 0; + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) glfwGetMonitorPhysicalSize(monitors[monitor], &width, NULL); + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); + + return width; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + int height = 0; + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) glfwGetMonitorPhysicalSize(monitors[monitor], NULL, &height); + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); + + return height; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + int refresh = 0; + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); + + if (mode) refresh = mode->refreshRate; + else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); + + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); + + return refresh; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + return glfwGetMonitorName(monitors[monitor]); + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); + return ""; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + glfwGetWindowPos(platform.handle, &CORE.Window.position.x, &CORE.Window.position.y); + + return (Vector2){ (float)CORE.Window.position.x, (float)CORE.Window.position.y }; +} + +// Get window scale DPI factor for current monitor +Vector2 GetWindowScaleDPI(void) +{ + Vector2 scale = { 1.0f, 1.0f }; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI) && !FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + glfwGetWindowContentScale(platform.handle, &scale.x, &scale.y); + return scale; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + glfwSetClipboardString(platform.handle, text); +} + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ + return glfwGetClipboardString(platform.handle); +} + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + +#if SUPPORT_CLIPBOARD_IMAGE && SUPPORT_MODULE_RTEXTURES +#if defined(_WIN32) + + unsigned int dataSize = 0; + void *bmpData = NULL; + int width = 0; + int height = 0; + + bmpData = (void *)Win32GetClipboardImageData(&width, &height, &dataSize); + + if (bmpData == NULL) TRACELOG(LOG_WARNING, "Clipboard image: Couldn't get clipboard data."); + else + { + image = LoadImageFromMemory(".bmp", (const unsigned char *)bmpData, (int)dataSize); + + RL_FREE(bmpData); + } + +#elif defined(__linux__) && defined(_GLFW_X11) + // REF: https://github.com/ColleagueRiley/Clipboard-Copy-Paste/blob/main/x11.c + + static Atom clipboard = 0; + static Atom targetType = 0; + static Atom property = 0; + + Display *display = glfwGetX11Display(); + XID window = glfwGetX11Window(platform.handle); + + // Lazy-load X11 atoms + if (clipboard == 0) + { + clipboard = XInternAtom(display, "CLIPBOARD", False); + targetType = XInternAtom(display, "image/png", False); + property = XInternAtom(display, "RAYLIB_CLIPBOARD_MANAGER", False); + } + + XConvertSelection(display, clipboard, targetType, property, window, CurrentTime); + XSync(display, 0); + + XEvent ev = { 0 }; + + // Keep calling until we get SelectionNotify + while (XCheckTypedEvent(display, SelectionNotify, &ev) == False); + + Atom actualType = { 0 }; + int actualFormat = 0; + unsigned long nitems = 0; + unsigned long bytesAfter = 0; + unsigned char *data = NULL; + + XGetWindowProperty(display, window, property, 0, ~0L, False, AnyPropertyType, + &actualType, &actualFormat, &nitems, &bytesAfter, &data); + + if (data != NULL) + { + image = LoadImageFromMemory(".png", data, (int)nitems); + XFree(data); + } + +#else + TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform"); +#endif // _WIN32 +#else + TRACELOG(LOG_WARNING, "Clipboard image: SUPPORT_CLIPBOARD_IMAGE requires SUPPORT_MODULE_RTEXTURES to work properly"); +#endif // SUPPORT_CLIPBOARD_IMAGE + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + glfwSetInputMode(platform.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + + CORE.Input.Mouse.cursorHidden = false; +} + +// Hide mouse cursor +void HideCursor(void) +{ + glfwSetInputMode(platform.handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + + CORE.Input.Mouse.cursorHidden = true; +} + +// Enable cursor (unlock cursor) +void EnableCursor(void) +{ + glfwSetInputMode(platform.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + if (glfwRawMouseMotionSupported()) glfwSetInputMode(platform.handle, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE); + + CORE.Input.Mouse.cursorHidden = false; + CORE.Input.Mouse.cursorLocked = false; +} + +// Disable cursor (lock cursor) +void DisableCursor(void) +{ + // Reset mouse position within the window area before disabling cursor + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + glfwSetInputMode(platform.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + + if (glfwRawMouseMotionSupported()) glfwSetInputMode(platform.handle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); + + CORE.Input.Mouse.cursorHidden = true; + CORE.Input.Mouse.cursorLocked = true; +} + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ + glfwSwapBuffers(platform.handle); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds since InitTimer() +double GetTime(void) +{ + double time = glfwGetTime(); // Elapsed time since glfwInit() + + return time; +} + +// Open URL with default system browser (if available) +// WARNING: This function is only safe to use if you control the URL given, +// a user could craft a malicious string to perform and undesired action +// NOTE: Some safety checks have been added to mitigate security issues +void OpenURL(const char *url) +{ + // Security check to (partially) avoid malicious code + if ((strchr(url, '\'') != NULL) || (strchr(url, '\"') != NULL)) + { + // Filter characters: ' and " + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'\"] characters"); + } + else if ((strncmp(url, "http://", 7) != 0) && (strncmp(url, "https://", 8) != 0)) + { + // Only allow URL starting with "http://" or "https://" protocols + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL must start with 'http://' or 'https://' protocols"); + } + else + { + char *cmd = (char *)RL_CALLOC(strlen(url) + 16, sizeof(char)); +#if defined(_WIN32) + sprintf(cmd, "explorer \"%s\"", url); +#endif +#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) + sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser +#endif +#if defined(__APPLE__) + sprintf(cmd, "open '%s'", url); +#endif + // TODO: Replace system() call by custom process + int result = system(cmd); + + if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created"); + RL_FREE(cmd); + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + return glfwUpdateGamepadMappings(mappings); +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + TRACELOG(LOG_WARNING, "SetGamepadVibration() not available on target platform"); +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; + + // NOTE: emscripten not implemented + glfwSetCursorPos(platform.handle, CORE.Input.Mouse.currentPosition.x, CORE.Input.Mouse.currentPosition.y); +} + +// Set mouse cursor +void SetMouseCursor(int cursor) +{ + CORE.Input.Mouse.cursor = cursor; + if (cursor == MOUSE_CURSOR_DEFAULT) glfwSetCursor(platform.handle, NULL); + else + { + // NOTE: Mapping internal GLFW enum values to MouseCursor enum values + glfwSetCursor(platform.handle, glfwCreateStandardCursor(0x00036000 + cursor)); + } +} + +// Get physical key name +const char *GetKeyName(int key) +{ + return glfwGetKeyName(key, glfwGetKeyScancode(key)); +} + +// Register all input events +void PollInputEvents(void) +{ +#if SUPPORT_GESTURES_SYSTEM + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = GAMEPAD_BUTTON_UNKNOWN; + //CORE.Input.Gamepad.axisCount = 0; + + // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) + + // Register previous keys states + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) + { + CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // Register previous mouse states + for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + + // Register previous mouse wheel state + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; + + // Register previous mouse position + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Reset touch positions + //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; + + // Map touch position to mouse position for convenience + // WARNING: If the target desktop device supports touch screen, this behaviour should be reviewed! + // TODO: GLFW does not support multi-touch input yet + // REF: https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch + // REF: https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages + CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + + // Check if gamepads are ready + // NOTE: Doing it here in case of disconnection + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (glfwJoystickPresent(i)) CORE.Input.Gamepad.ready[i] = true; + else CORE.Input.Gamepad.ready[i] = false; + } + + // Register gamepads buttons events + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available + { + // Register previous gamepad states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; + + // Get current gamepad state using internal GLFW mapping, instead of the immediate joystick API + // NOTE: There is no callback available, getting it manually + GLFWgamepadstate state = { 0 }; + int result = glfwGetGamepadState(i, &state); // This remaps all gamepads so they have their buttons mapped like an xbox controller + if (result == GLFW_FALSE) // No joystick is connected, no gamepad mapping or an error occurred + { + // Setting axes to expected resting value instead of GLFW 0.0f default when gamepad is not connected + state.axes[GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; + state.axes[GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; + } + + const unsigned char *buttons = state.buttons; + + for (int k = 0; (buttons != NULL) && (k < MAX_GAMEPAD_BUTTONS); k++) + { + int button = -1; // GamepadButton enum values assigned + + switch (k) + { + case GLFW_GAMEPAD_BUTTON_Y: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case GLFW_GAMEPAD_BUTTON_B: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case GLFW_GAMEPAD_BUTTON_A: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case GLFW_GAMEPAD_BUTTON_X: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + + case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + + case GLFW_GAMEPAD_BUTTON_BACK: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case GLFW_GAMEPAD_BUTTON_GUIDE: button = GAMEPAD_BUTTON_MIDDLE; break; + case GLFW_GAMEPAD_BUTTON_START: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + + case GLFW_GAMEPAD_BUTTON_DPAD_UP: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + + case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + default: break; + } + + if (button != -1) // Check for valid button + { + if (buttons[k] == GLFW_PRESS) + { + CORE.Input.Gamepad.currentButtonState[i][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + } + else CORE.Input.Gamepad.currentButtonState[i][button] = 0; + } + } + + // Get current state of axes + const float *axes = state.axes; + + for (int k = 0; (axes != NULL) && (k < GLFW_GAMEPAD_AXIS_LAST + 1); k++) + { + CORE.Input.Gamepad.axisState[i][k] = axes[k]; + } + + // Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather as axes) + if (CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1f) + { + CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = 1; + CORE.Input.Gamepad.lastButtonPressed = GAMEPAD_BUTTON_LEFT_TRIGGER_2; + } + else CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = 0; + if (CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1f) + { + CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = 1; + CORE.Input.Gamepad.lastButtonPressed = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; + } + else CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = 0; + + CORE.Input.Gamepad.axisCount[i] = GLFW_GAMEPAD_AXIS_LAST + 1; + } + } + + CORE.Window.resizedLastFrame = false; + + if ((CORE.Window.eventWaiting) || + (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED) && !FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN))) + { + glfwWaitEvents(); // Wait for in input events before continue (drawing is paused) + CORE.Time.previous = GetTime(); + } + else glfwPollEvents(); // Poll input events: keyboard/mouse/window events (callbacks) -> Update keys state + + CORE.Window.shouldClose = glfwWindowShouldClose(platform.handle); + + // Reset close status for next frame + glfwSetWindowShouldClose(platform.handle, GLFW_FALSE); +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +// Function wrappers around RL_*ALLOC macros, used by glfwInitAllocator() inside of InitPlatform() +// GLFWallocator expects function pointers with specific signatures to be provided +// REF: https://www.glfw.org/docs/latest/intro_guide.html#init_allocator +static void *AllocateWrapper(size_t size, void *user) +{ + (void)user; + return RL_CALLOC(size, 1); +} +static void *ReallocateWrapper(void *block, size_t size, void *user) +{ + (void)user; + return RL_REALLOC(block, size); +} +static void DeallocateWrapper(void *block, void *user) +{ + (void)user; + RL_FREE(block); +} + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + glfwSetErrorCallback(ErrorCallback); + + const GLFWallocator allocator = { + .allocate = AllocateWrapper, + .reallocate = ReallocateWrapper, + .deallocate = DeallocateWrapper, + .user = NULL, // RL_*ALLOC macros are not capable of handling user-provided data + }; + + glfwInitAllocator(&allocator); + +#if defined(__APPLE__) + glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE); +#endif + // Initialize GLFW internal global state + int result = glfwInit(); + if (result == GLFW_FALSE) { TRACELOG(LOG_WARNING, "GLFW: Failed to initialize GLFW"); return -1; } + + // Initialize graphic device: display/window and graphic context + //---------------------------------------------------------------------------- + glfwDefaultWindowHints(); // Set default windows hints + //glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits + //glfwWindowHint(GLFW_GREEN_BITS, 8); // Framebuffer green color component bits + //glfwWindowHint(GLFW_BLUE_BITS, 8); // Framebuffer blue color component bits + //glfwWindowHint(GLFW_ALPHA_BITS, 8); // Framebuffer alpha color component bits + //glfwWindowHint(GLFW_DEPTH_BITS, 24); // Depthbuffer bits + //glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window + //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API + //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers + + // Disable GlFW auto iconify behaviour + // Auto Iconify automatically minimizes (iconifies) the window if the window loses focus + // additionally auto iconify restores the hardware resolution of the monitor if the window that loses focus is a fullscreen window + glfwWindowHint(GLFW_AUTO_ICONIFY, 0); + + // Window flags requested before initialization to be applied after initialization + unsigned int requestedWindowFlags = CORE.Window.flags; + + // Check window creation flags + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window + else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window + else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window + else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable + + // Disable FLAG_WINDOW_MINIMIZED, not supported on initialization + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); + + // Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); + else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); + else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); + + // NOTE: Some GLFW flags are not supported on HTML5 + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT)) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer + else glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); // Opaque framebuffer + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { +#if defined(__APPLE__) + glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); +#endif + // Resize window content area based on the monitor content scale + // NOTE: This hint only has an effect on platforms where screen coordinates and + // pixels always map 1:1 such as Windows and X11 + // On platforms like macOS the resolution of the framebuffer is changed independently of the window size + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); +#if defined(__APPLE__) + glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); +#endif + } + else + { + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); +#if defined(__APPLE__) + glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); +#endif +#if defined(_GLFW_WAYLAND) && !defined(_GLFW_X11) + // GLFW 3.4+ defaults GLFW_SCALE_FRAMEBUFFER to TRUE, + // causing framebuffer/window size mismatch on Wayland with display scaling + glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); +#endif + } + + // Mouse passthrough + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); + else glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) + { + // NOTE: MSAA is only enabled for main framebuffer, not user-created FBOs + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); + glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0 + } + + // NOTE: When asking for an OpenGL context version, most drivers provide the highest supported version + // with backward compatibility to older OpenGL versions + // For example, if using OpenGL 1.1, driver can provide a 4.3 backwards compatible context + + // Check selection OpenGL version + if (rlGetVersion() == RL_OPENGL_21) + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint) + } + else if (rlGetVersion() == RL_OPENGL_33) + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above! + // Values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE +#if defined(__APPLE__) + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // OSX Requires forward compatibility +#else + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Forward Compatibility Hint: Only 3.3 and above! +#endif + //glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context + } + else if (rlGetVersion() == RL_OPENGL_43) + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); +#if RLGL_ENABLE_OPENGL_DEBUG_CONTEXT + glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Enable OpenGL Debug Context +#endif + } + else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); + } + else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); + } + + // NOTE: GLFW 3.4+ defers initialization of the Joystick subsystem on the first call to any Joystick related functions + // Forcing this initialization here avoids doing it on PollInputEvents() called by EndDrawing() after first frame has been drawn + // The initialization will still happen and possible delays still occur, but before the window is shown, which is a nicer experience + // REF: https://github.com/raysan5/raylib/issues/1554 + glfwSetJoystickCallback(NULL); + + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + + // Init window in fullscreen mode if requested + // NOTE: Keeping original screen size for toggle + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + { + // NOTE: Fullscreen applications default to the primary monitor + GLFWmonitor *monitor = glfwGetPrimaryMonitor(); + if (!monitor) + { + TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor"); + return -1; + } + + // Set dimensions from monitor + const GLFWvidmode *mode = glfwGetVideoMode(monitor); + + // Default display resolution to that of the current mode + CORE.Window.display.width = mode->width; + CORE.Window.display.height = mode->height; + + // Check if user requested some screen size + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) + { + // Set some default screen size in case user decides to exit fullscreen mode + CORE.Window.previousScreen.width = 800; + CORE.Window.previousScreen.height = 450; + CORE.Window.previousPosition.x = CORE.Window.display.width/2 - 800/2; + CORE.Window.previousPosition.y = CORE.Window.display.height/2 - 450/2; + + // Set screen width/height to the display width/height + if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; + if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; + } + else + { + CORE.Window.previousScreen = CORE.Window.screen; + CORE.Window.screen = CORE.Window.display; + } + + platform.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", monitor, NULL); + if (!platform.handle) + { + glfwTerminate(); + TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window"); + return -1; + } + } + else + { + // Default to at least one pixel in size, as creation with a zero dimension is not allowed + if (CORE.Window.screen.width == 0) CORE.Window.screen.width = 1; + if (CORE.Window.screen.height == 0) CORE.Window.screen.height = 1; + + platform.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL); + if (!platform.handle) + { + glfwTerminate(); + TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window"); + return -1; + } + + // After the window was created, determine the monitor that the window manager assigned + // Derive display sizes and, if possible, window size in case it was zero at beginning + + int monitorCount = 0; + int monitorIndex = GetCurrentMonitor(); + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + if (monitorIndex < monitorCount) + { + GLFWmonitor *monitor = monitors[monitorIndex]; + const GLFWvidmode *mode = glfwGetVideoMode(monitor); + + // Default display resolution to that of the current mode + CORE.Window.display.width = mode->width; + CORE.Window.display.height = mode->height; + + // Set screen width/height to the display width/height if they are 0 + if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; + if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; + + glfwSetWindowSize(platform.handle, CORE.Window.screen.width, CORE.Window.screen.height); + } + else + { + // The monitor for the window-manager-created window can not be determined, so it can not be centered + glfwTerminate(); + TRACELOG(LOG_WARNING, "GLFW: Failed to determine Monitor to center Window"); + return -1; + } + +#if defined(__APPLE__) + // AppKit can constrain the requested window size to the visible work area during creation + int windowWidth = 0; + int windowHeight = 0; + glfwGetWindowSize(platform.handle, &windowWidth, &windowHeight); + if ((windowWidth > 0) && (windowHeight > 0)) CORE.Window.screen = (Size){ windowWidth, windowHeight }; +#endif + + // NOTE: Not considering scale factor now, considered below + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + } + + glfwMakeContextCurrent(platform.handle); + result = glfwGetError(NULL); + if ((result != GLFW_NO_WINDOW_CONTEXT) && (result != GLFW_PLATFORM_ERROR)) CORE.Window.ready = true; // Checking context activation + + if (CORE.Window.ready) + { + // Setup additional windows configs and register required window size info + + glfwSwapInterval(0); // No V-Sync by default + + // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) + // NOTE: V-Sync can be enabled by graphic driver configuration, it doesn't need + // to be activated on web platforms since VSync is enforced there + if (FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) + { + // WARNING: It seems to hit a critical render path in Intel HD Graphics + glfwSwapInterval(1); + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable VSYNC"); + } + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + // Set screen size to logical pixel size, considering content scaling + Vector2 scaleDpi = GetWindowScaleDPI(); + CORE.Window.render.width = (int)(CORE.Window.screen.width*scaleDpi.x); + CORE.Window.render.height = (int)(CORE.Window.screen.height*scaleDpi.y); + //TRACELOG(LOG_INFO, "DPI SCALING: %.2f, %.2f", scaleDpi.x, scaleDpi.y); + + // Screen scaling matrix is required in case desired screen area is different from display area + CORE.Window.screenScale = MatrixScale(scaleDpi.x, scaleDpi.y, 1.0f); + + // NOTE: On APPLE platforms system manage window and input scaling + // Framebuffer scaling is activated with: glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); + +#if !defined(__APPLE__) + if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) + { + // On Wayland, GLFW_SCALE_FRAMEBUFFER handles scaling; read actual framebuffer size + // instead of resizing the window (which would double-scale) + int fbWidth = 0; + int fbHeight = 0; + glfwGetFramebufferSize(platform.handle, &fbWidth, &fbHeight); + + CORE.Window.render.width = fbWidth; + CORE.Window.render.height = fbHeight; + } + else + { + // Mouse input scaling for the new screen size + SetMouseScale(1.0f/scaleDpi.x, 1.0f/scaleDpi.y); + + // Force window size (and framebuffer) refresh + glfwSetWindowSize(platform.handle, CORE.Window.render.width, CORE.Window.render.height); + } +#endif + } + else CORE.Window.render = CORE.Window.screen; + + // Current active framebuffer size is main framebuffer size + CORE.Window.currentFbo = CORE.Window.render; + + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully %s", + FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)? "(HighDPI)" : ""); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + //TRACELOG(LOG_INFO, " > Content Scaling: %.2f, %.2f", scaleDpi.x, scaleDpi.y); + + // Try to center window on screen but avoiding window-bar outside of screen + int monitorCount = 0; + int monitorIndex = GetCurrentMonitor(); + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + GLFWmonitor *monitor = monitors[monitorIndex]; + + int monitorX = 0; + int monitorY = 0; + int monitorWidth = 0; + int monitorHeight = 0; + glfwGetMonitorWorkarea(monitor, &monitorX, &monitorY, &monitorWidth, &monitorHeight); + + // NOTE: It seems on macOS monitor size is not correct + //TRACELOG(LOG_WARNING, "Monitor info: [%i, %i, %i, %i]", monitorX, monitorY, monitorWidth, monitorHeight); + + // Center window into current monitor + #if defined(__APPLE__) + CORE.Window.position.x = monitorX + (monitorWidth - CORE.Window.screen.width)/2; + CORE.Window.position.y = monitorY + (monitorHeight - CORE.Window.screen.height)/2; + #else + CORE.Window.position.x = monitorX + (monitorWidth - CORE.Window.render.width)/2; + CORE.Window.position.y = monitorY + (monitorHeight - CORE.Window.render.height)/2; + #endif + SetWindowPosition(CORE.Window.position.x, CORE.Window.position.y); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); + } + else + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); + return -1; + } + + // Apply window flags requested previous to initialization + SetWindowState(requestedWindowFlags); + + // Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions + rlLoadExtensions(glfwGetProcAddress); + //---------------------------------------------------------------------------- + + // Initialize input events callbacks + //---------------------------------------------------------------------------- + // Set window callback events + glfwSetWindowSizeCallback(platform.handle, WindowSizeCallback); // NOTE: Resizing is not enabled by default + glfwSetFramebufferSizeCallback(platform.handle, FramebufferSizeCallback); + glfwSetWindowPosCallback(platform.handle, WindowPosCallback); + glfwSetWindowMaximizeCallback(platform.handle, WindowMaximizeCallback); + glfwSetWindowIconifyCallback(platform.handle, WindowIconifyCallback); + glfwSetWindowFocusCallback(platform.handle, WindowFocusCallback); + glfwSetDropCallback(platform.handle, WindowDropCallback); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) glfwSetWindowContentScaleCallback(platform.handle, WindowContentScaleCallback); + + // Set input callback events + glfwSetKeyCallback(platform.handle, KeyCallback); + glfwSetCharCallback(platform.handle, CharCallback); + glfwSetMouseButtonCallback(platform.handle, MouseButtonCallback); + glfwSetCursorPosCallback(platform.handle, MouseCursorPosCallback); // Track mouse position changes + glfwSetScrollCallback(platform.handle, MouseScrollCallback); + glfwSetCursorEnterCallback(platform.handle, CursorEnterCallback); + glfwSetJoystickCallback(JoystickCallback); + glfwSetInputMode(platform.handle, GLFW_LOCK_KEY_MODS, GLFW_TRUE); // Enable lock keys modifiers (CAPS, NUM) + + // Retrieve gamepad names + for (int i = 0; i < MAX_GAMEPADS; i++) + { + // WARNING: If glfwGetJoystickName() is longer than MAX_GAMEPAD_NAME_LENGTH, + // only copying up to (MAX_GAMEPAD_NAME_LENGTH - 1) + if (glfwJoystickPresent(i)) + { + CORE.Input.Gamepad.ready[i] = true; + CORE.Input.Gamepad.axisCount[i] = GLFW_GAMEPAD_AXIS_LAST + 1; + snprintf(CORE.Input.Gamepad.name[i], MAX_GAMEPAD_NAME_LENGTH, "%s", glfwGetJoystickName(i)); + } + } + //---------------------------------------------------------------------------- + + // Initialize timing system + //---------------------------------------------------------------------------- + InitTimer(); + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- + +#if defined(__NetBSD__) + // Workaround for NetBSD + char *glfwPlatform = "X11 (NetBSD)"; +#else + char *glfwPlatform = ""; + switch (glfwGetPlatform()) + { + case GLFW_PLATFORM_WIN32: glfwPlatform = "Win32"; break; + case GLFW_PLATFORM_COCOA: glfwPlatform = "Cocoa"; break; + case GLFW_PLATFORM_WAYLAND: glfwPlatform = "Wayland"; break; + case GLFW_PLATFORM_X11: glfwPlatform = "X11"; break; + case GLFW_PLATFORM_NULL: glfwPlatform = "Null"; break; + default: break; + } +#endif + + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (GLFW - %s): Initialized successfully", glfwPlatform); + + return 0; +} + +// Close platform +void ClosePlatform(void) +{ + glfwDestroyWindow(platform.handle); + glfwTerminate(); + +#if defined(_WIN32) && SUPPORT_WINMM_HIGHRES_TIMER && !SUPPORT_BUSY_WAIT_LOOP + timeEndPeriod(1); // Restore time period +#endif +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +// NOTE: Those functions are only required for current platform +//---------------------------------------------------------------------------------- + +// GLFW3: Error callback, runs on GLFW3 error +static void ErrorCallback(int error, const char *description) +{ + TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s", error, description); +} + +// GLFW3: Window size change callback, runs when window is resized +// NOTE: Window resizing not enabled by default, use SetConfigFlags() +static void WindowSizeCallback(GLFWwindow *window, int width, int height) +{ + // Nothing to do for now on window resize... + //TRACELOG(LOG_INFO, "GLFW3: Window size callback called [%i,%i]", width, height); +} + +// GLFW3: Framebuffer size change callback, runs when framebuffer is resized +// WARNING: If FLAG_WINDOW_HIGHDPI is set, WindowContentScaleCallback() is called before this function +static void FramebufferSizeCallback(GLFWwindow *window, int width, int height) +{ + //TRACELOG(LOG_INFO, "GLFW3: Window framebuffer size callback called [%i,%i]", width, height); + + // WARNING: On window minimization, callback is called with 0 values, + // but internal screen values should not be changed, it breaks things + if ((width == 0) || (height == 0)) return; + + // Reset viewport and projection matrix for new size + // NOTE: Stores current render size: CORE.Window.render + SetupViewport(width, height); + + // Set render size + CORE.Window.render.width = width; + CORE.Window.render.height = height; + CORE.Window.currentFbo = CORE.Window.render; + CORE.Window.resizedLastFrame = true; + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + { + // On fullscreen mode, strategy is ignoring high-dpi and + // use the all available display size + + // Set screen size to render size (physical pixel size) + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + CORE.Window.screenScale = MatrixScale(1.0f, 1.0f, 1.0f); + SetMouseScale(1.0f, 1.0f); + + // On Wayland with GLFW_SCALE_FRAMEBUFFER, the framebuffer is still scaled in fullscreen, use logical window size as screen and apply screenScale +#if defined(_GLFW_WAYLAND) && !defined(_GLFW_X11) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + int winWidth = 0; + int winHeight = 0; + glfwGetWindowSize(platform.handle, &winWidth, &winHeight); + + if ((winWidth != width) || (winHeight != height)) + { + CORE.Window.screen.width = winWidth; + CORE.Window.screen.height = winHeight; + float scaleX = (float)width/winWidth; + float scaleY = (float)height/winHeight; + + CORE.Window.screenScale = MatrixScale(scaleX, scaleY, 1.0f); + } + } +#endif + } + else // Window mode (including borderless window) + { + // Check if render size was actually scaled for high-dpi + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + // Set screen size to logical pixel size, considering content scaling + Vector2 scaleDpi = GetWindowScaleDPI(); + CORE.Window.screen.width = (int)((float)width/scaleDpi.x); + CORE.Window.screen.height = (int)((float)height/scaleDpi.y); + CORE.Window.screenScale = MatrixScale(scaleDpi.x, scaleDpi.y, 1.0f); +#if !defined(__APPLE__) && !defined(_GLFW_WAYLAND) + // On macOS and Linux-Wayland, mouse coords are already in logical space + SetMouseScale(1.0f/scaleDpi.x, 1.0f/scaleDpi.y); +#endif + } + else + { + // Set screen size to render size (physical pixel size) + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + } + } + + // WARNING: If using a render texture, it is not scaled to new size +} + +// GLFW3: Window content scale callback, runs on monitor content scale change detected +// WARNING: If FLAG_WINDOW_HIGHDPI is not set, this function is not called +static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley) +{ + //TRACELOG(LOG_INFO, "GLFW3: Window content scale changed, scale: [%.2f,%.2f]", scalex, scaley); + + CORE.Window.render.width = (int)((float)CORE.Window.screen.width*scalex); + CORE.Window.render.height = (int)((float)CORE.Window.screen.height*scaley); + CORE.Window.currentFbo = CORE.Window.render; + + // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling + // Framebuffer scaling is activated with: glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); + CORE.Window.screenScale = MatrixScale(scalex, scaley, 1.0f); + +#if !defined(__APPLE__) && !defined(_GLFW_WAYLAND) + // On macOS and Linux-Wayland, mouse coords are already in logical space + SetMouseScale(1.0f/scalex, 1.0f/scaley); +#endif +} + +// GLFW3: Window position callback, runs when window position changes +static void WindowPosCallback(GLFWwindow *window, int x, int y) +{ + // Set current window position + CORE.Window.position.x = x; + CORE.Window.position.y = y; +} + +// GLFW3: Window iconify callback, runs when window is minimized/restored +static void WindowIconifyCallback(GLFWwindow *window, int iconified) +{ + if (iconified) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was iconified + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was restored +} + +// GLFW3: Window maximize callback, runs when window is maximized/restored +static void WindowMaximizeCallback(GLFWwindow *window, int maximized) +{ + if (maximized) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was maximized + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was restored +} + +// GLFW3: Window focus callback, runs when window get/lose focus +static void WindowFocusCallback(GLFWwindow *window, int focused) +{ + if (focused) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was focused + else FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window lost focus +} + +// GLFW3: Window drop callback, runs when files are dropped into window +static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) +{ + if (count > 0) + { + // In case previous dropped filepaths have not been freed, free them + if (CORE.Window.dropFileCount > 0) + { + for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]); + + RL_FREE(CORE.Window.dropFilepaths); + + CORE.Window.dropFileCount = 0; + CORE.Window.dropFilepaths = NULL; + } + + // WARNING: Paths are freed by GLFW when the callback returns, keeping an internal copy + CORE.Window.dropFileCount = count; + CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *)); + + for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) + { + CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + snprintf(CORE.Window.dropFilepaths[i], MAX_FILEPATH_LENGTH, "%s", paths[i]); + } + } +} + +// GLFW3: Keyboard callback, runs on key pressed +static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) +{ + if (key < 0) return; // Security check, macOS fn key generates -1 + + // WARNING: GLFW could return GLFW_REPEAT, it needs to be considered as 1 + // to work properly with our implementation (IsKeyDown/IsKeyUp checks) + if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0; + else if (action == GLFW_PRESS) CORE.Input.Keyboard.currentKeyState[key] = 1; + else if (action == GLFW_REPEAT) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1; + + // WARNING: Check if CAPS/NUM key modifiers are enabled and force down state for those keys + if (((key == KEY_CAPS_LOCK) && (FLAG_IS_SET(mods, GLFW_MOD_CAPS_LOCK))) || + ((key == KEY_NUM_LOCK) && (FLAG_IS_SET(mods, GLFW_MOD_NUM_LOCK)))) CORE.Input.Keyboard.currentKeyState[key] = 1; + + // Check if there is space available in the key queue + if ((CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) && (action == GLFW_PRESS)) + { + // Add character to the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + + // Check the exit key to set close window + if ((key == CORE.Input.Keyboard.exitKey) && (action == GLFW_PRESS)) glfwSetWindowShouldClose(platform.handle, GLFW_TRUE); +} + +// GLFW3: Char callback, runs on key pressed to get unicode codepoint value +static void CharCallback(GLFWwindow *window, unsigned int codepoint) +{ + // NOTE: Registers any key down considering OS keyboard layout but + // does not detect action events, those should be managed by user... + // REF: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 + // REF: https://www.glfw.org/docs/latest/input_guide.html#input_char + + // Check if there is space available in the queue + if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) + { + // Add character to the queue + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = codepoint; + CORE.Input.Keyboard.charPressedQueueCount++; + } +} + +// GLFW3: Mouse button callback, runs on mouse button pressed +static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods) +{ + // WARNING: GLFW could only return GLFW_PRESS (1) or GLFW_RELEASE (0) for now, + // but future releases may add more actions (i.e. GLFW_REPEAT) + CORE.Input.Mouse.currentButtonState[button] = action; + CORE.Input.Touch.currentTouchState[button] = action; + +#if SUPPORT_GESTURES_SYSTEM && SUPPORT_MOUSE_GESTURES + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_ACTION_DOWN; + else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_ACTION_UP; + + // NOTE: TOUCH_ACTION_MOVE event is registered in MouseCursorPosCallback() + + // Assign a pointer ID + gestureEvent.pointId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = GetMousePosition(); + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures-system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +// GLFW3: Cursor position callback, runs on mouse movement +static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) +{ + CORE.Input.Mouse.currentPosition.x = (float)x; + CORE.Input.Mouse.currentPosition.y = (float)y; + CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + +#if SUPPORT_GESTURES_SYSTEM && SUPPORT_MOUSE_GESTURES + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + gestureEvent.touchAction = TOUCH_ACTION_MOVE; + + // Assign a pointer ID + gestureEvent.pointId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = CORE.Input.Touch.position[0]; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures-system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +// GLFW3: Mouse wheel scroll callback, runs on mouse wheel changes +static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset) +{ + CORE.Input.Mouse.currentWheelMove = (Vector2){ (float)xoffset, (float)yoffset }; +} + +// GLFW3: Cursor ennter callback, when cursor enters the window +static void CursorEnterCallback(GLFWwindow *window, int entered) +{ + if (entered) + { + // NOTE: Mouse position updated by MouseCursorPosCallback() + CORE.Input.Mouse.cursorOnScreen = true; + } + else + { + CORE.Input.Mouse.cursorOnScreen = false; + CORE.Input.Mouse.currentPosition = (Vector2){ 0 }; + } +} + +// GLFW3: Joystick connected/disconnected callback +static void JoystickCallback(int jid, int event) +{ + if (jid < MAX_GAMEPADS) + { + if (event == GLFW_CONNECTED) + { + // WARNING: If glfwGetJoystickName() is longer than MAX_GAMEPAD_NAME_LENGTH, + // only copy up to (MAX_GAMEPAD_NAME_LENGTH -1) to destination string + memset(CORE.Input.Gamepad.name[jid], 0, MAX_GAMEPAD_NAME_LENGTH); + snprintf(CORE.Input.Gamepad.name[jid], MAX_GAMEPAD_NAME_LENGTH, "%s", glfwGetJoystickName(jid)); + } + else if (event == GLFW_DISCONNECTED) + { + memset(CORE.Input.Gamepad.name[jid], 0, MAX_GAMEPAD_NAME_LENGTH); + } + } +} + +#ifdef _WIN32 +# define WIN32_CLIPBOARD_IMPLEMENTATION +# include "../external/win32_clipboard.h" +#endif +// EOF diff --git a/src/libraries/raylib/raylib/src/platforms/rcore_desktop_rgfw.c b/src/libraries/raylib/raylib/src/platforms/rcore_desktop_rgfw.c new file mode 100644 index 0000000..ceacf53 --- /dev/null +++ b/src/libraries/raylib/raylib/src/platforms/rcore_desktop_rgfw.c @@ -0,0 +1,2118 @@ +/********************************************************************************************** +* +* rcore_desktop_rgfw - Functions to manage window, graphics device and inputs +* +* PLATFORM: RGFW +* - Windows (Win32, Win64) +* - Linux (X11/Wayland desktop mode) +* - MacOS (Cocoa) +* - HTML5 (Emscripten) +* - Others (untested) +* +* LIMITATIONS: +* - TODO +* +* POSSIBLE IMPROVEMENTS: +* - TBD +* +* CONFIGURATION: +* #define RCORE_PLATFORM_RGFW +* Custom flag for rcore on target platform RGFW +* +* DEPENDENCIES: +* - RGFW.h (main library): Windowing and inputs management +* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5), Colleague Riley and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#if defined(_WIN32) || defined(_WIN64) + #define BI_ALPHABITFIELDS 4 + #define LoadImage LoadImageA + + // Temporarily rename conflicting symbols + #define CloseWindow CloseWindowWin32 + #define Rectangle RectangleWin32 + #define ShowCursor ShowCursorWin32 + + #define WIN32_LEAN_AND_MEAN + #include + + // Restore for raylib/RGFW + #undef CloseWindow + #undef Rectangle + #undef ShowCursor + #undef LoadImage + + #include "../external/fix_win32_compatibility.h" +#endif + +#if defined(PLATFORM_WEB_RGFW) + #define RGFW_NO_GL_HEADER +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) && !defined(PLATFORM_WEB_RGFW) + #define RGFW_OPENGL_ES2 +#endif + +void ShowCursor(void); +void CloseWindow(void); +double GetTimeSeconds(void); + +#if defined(__unix__) || defined(__linux__) + #define _XTYPEDEF_FONT +#endif + +#define RGFW_OPENGL +#define RGFW_IMPLEMENTATION + +#if defined(_WIN32) || defined(_WIN64) + #define WIN32_LEAN_AND_MEAN + #define Rectangle rectangle_win32 + #define CloseWindow CloseWindow_win32 + #define ShowCursor __imp_ShowCursor + #define _APISETSTRING_ + +#if defined(__cplusplus) +extern "C" { +#endif + __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); +#if defined(__cplusplus) +} +#endif + +#endif + +#if defined(__APPLE__) + #define Point NSPOINT + #define Size NSSIZE + + #ifdef GetColor + #undef GetColor + #endif + #define GetColor GetColor_osx + #ifdef EventType + #undef EventType + #endif + #define EventType EventType_osx +#endif + +#if defined(__APPLE__) + // older macs (unsupported?) are missing these + #include + #ifndef kHIDUsage_Button_5 + #define kHIDUsage_Button_5 0x05 + #endif + #ifndef kHIDUsage_Button_6 + #define kHIDUsage_Button_6 0x06 + #endif + #ifndef kHIDUsage_Button_7 + #define kHIDUsage_Button_7 0x07 + #endif + #ifndef kHIDUsage_Button_8 + #define kHIDUsage_Button_8 0x08 + #endif + #ifndef kHIDUsage_Button_9 + #define kHIDUsage_Button_9 0x09 + #endif + #ifndef kHIDUsage_Button_10 + #define kHIDUsage_Button_10 0x0A + #endif +#endif + +// minigamepad used for gamepad support +#define MG_MAX_GAMEPADS MAX_GAMEPADS // copy raylibs define into minigamepad +#define MG_IMPLEMENTATION +#include "../external/RGFW/deps/minigamepad.h" + +#define RGFW_ALLOC RL_MALLOC +#define RGFW_FREE RL_FREE +#define RGFW_CALLOC RL_CALLOC +#define RGFW_INT_DEFINED 1 // to avoid issues with minigamepad+RGFW definitions + +#include "../external/RGFW/RGFW.h" + +#if defined(_WIN32) || defined(_WIN64) + #undef DrawText + #undef ShowCursor + #undef CloseWindow + #undef Rectangle + + #ifdef MAX_PATH + #undef MAX_PATH + #endif + #define MAX_PATH 1025 +#endif + +#if defined(__APPLE__) + #undef Point + #undef Size +#endif + +#include + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct { + RGFW_window *window; // Native display device (physical screen connection) + RGFW_monitor *monitor; + mg_gamepads minigamepad; + +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + RGFW_surface *surface; + u8 *surfacePixels; + i32 surfaceWidth; + i32 surfaceHeight; +#endif +#if defined(__linux__) && defined(RGFW_X11) + Window windowHandleX11; // Underlying type: unsigned long +#endif +} PlatformData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context + +static PlatformData platform = { 0 }; // Platform specific + +#if defined(__linux__) // prevent collision of raylibs KEY_ and linux/input.h KEY_ + #undef KEY_NULL + // Alphanumeric keys + #undef KEY_APOSTROPHE + #undef KEY_COMMA + #undef KEY_MINUS + #undef KEY_PERIOD + #undef KEY_SLASH + #undef KEY_ZERO + #undef KEY_ONE + #undef KEY_TWO + #undef KEY_THREE + #undef KEY_FOUR + #undef KEY_FIVE + #undef KEY_SIX + #undef KEY_SEVEN + #undef KEY_EIGHT + #undef KEY_NINE + #undef KEY_SEMICOLON + #undef KEY_EQUAL + #undef KEY_A + #undef KEY_B + #undef KEY_C + #undef KEY_D + #undef KEY_E + #undef KEY_F + #undef KEY_G + #undef KEY_H + #undef KEY_I + #undef KEY_J + #undef KEY_K + #undef KEY_L + #undef KEY_M + #undef KEY_N + #undef KEY_O + #undef KEY_P + #undef KEY_Q + #undef KEY_R + #undef KEY_S + #undef KEY_T + #undef KEY_U + #undef KEY_V + #undef KEY_W + #undef KEY_X + #undef KEY_Y + #undef KEY_Z + #undef KEY_LEFT_BRACKET + #undef KEY_BACKSLASH + #undef KEY_RIGHT_BRACKET + #undef KEY_GRAVE + // Function keys + #undef KEY_SPACE + #undef KEY_ESCAPE + #undef KEY_ENTER + #undef KEY_TAB + #undef KEY_BACKSPACE + #undef KEY_INSERT + #undef KEY_DELETE + #undef KEY_RIGHT + #undef KEY_LEFT + #undef KEY_DOWN + #undef KEY_UP + #undef KEY_PAGE_UP + #undef KEY_PAGE_DOWN + #undef KEY_HOME + #undef KEY_END + #undef KEY_CAPS_LOCK + #undef KEY_SCROLL_LOCK + #undef KEY_NUM_LOCK + #undef KEY_PRINT_SCREEN + #undef KEY_PAUSE + #undef KEY_F1 + #undef KEY_F2 + #undef KEY_F3 + #undef KEY_F4 + #undef KEY_F5 + #undef KEY_F6 + #undef KEY_F7 + #undef KEY_F8 + #undef KEY_F9 + #undef KEY_F10 + #undef KEY_F11 + #undef KEY_F12 + #undef KEY_LEFT_SHIFT + #undef KEY_LEFT_CONTROL + #undef KEY_LEFT_ALT + #undef KEY_LEFT_SUPER + #undef KEY_RIGHT_SHIFT + #undef KEY_RIGHT_CONTROL + #undef KEY_RIGHT_ALT + #undef KEY_RIGHT_SUPER + #undef KEY_KB_MENU + // Keypad keys + #undef KEY_KP_0 + #undef KEY_KP_1 + #undef KEY_KP_2 + #undef KEY_KP_3 + #undef KEY_KP_4 + #undef KEY_KP_5 + #undef KEY_KP_6 + #undef KEY_KP_7 + #undef KEY_KP_8 + #undef KEY_KP_9 + #undef KEY_KP_DECIMAL + #undef KEY_KP_DIVIDE + #undef KEY_KP_MULTIPLY + #undef KEY_KP_SUBTRACT + #undef KEY_KP_ADD + #undef KEY_KP_ENTER + #undef KEY_KP_EQUAL +#endif + +static const unsigned short RGFW_keyConvertTable[] = { + [RGFW_keyNULL] = KEY_NULL, + [RGFW_keyApostrophe] = KEY_APOSTROPHE, + [RGFW_keyComma] = KEY_COMMA, + [RGFW_keyMinus] = KEY_MINUS, + [RGFW_keyPeriod] = KEY_PERIOD, + [RGFW_keySlash] = KEY_SLASH, + [RGFW_key0] = KEY_ZERO, + [RGFW_key1] = KEY_ONE, + [RGFW_key2] = KEY_TWO, + [RGFW_key3] = KEY_THREE, + [RGFW_key4] = KEY_FOUR, + [RGFW_key5] = KEY_FIVE, + [RGFW_key6] = KEY_SIX, + [RGFW_key7] = KEY_SEVEN, + [RGFW_key8] = KEY_EIGHT, + [RGFW_key9] = KEY_NINE, + [RGFW_keySemicolon] = KEY_SEMICOLON, + [RGFW_keyEquals] = KEY_EQUAL, + [RGFW_keyA] = KEY_A, + [RGFW_keyB] = KEY_B, + [RGFW_keyC] = KEY_C, + [RGFW_keyD] = KEY_D, + [RGFW_keyE] = KEY_E, + [RGFW_keyF] = KEY_F, + [RGFW_keyG] = KEY_G, + [RGFW_keyH] = KEY_H, + [RGFW_keyI] = KEY_I, + [RGFW_keyJ] = KEY_J, + [RGFW_keyK] = KEY_K, + [RGFW_keyL] = KEY_L, + [RGFW_keyM] = KEY_M, + [RGFW_keyN] = KEY_N, + [RGFW_keyO] = KEY_O, + [RGFW_keyP] = KEY_P, + [RGFW_keyQ] = KEY_Q, + [RGFW_keyR] = KEY_R, + [RGFW_keyS] = KEY_S, + [RGFW_keyT] = KEY_T, + [RGFW_keyU] = KEY_U, + [RGFW_keyV] = KEY_V, + [RGFW_keyW] = KEY_W, + [RGFW_keyX] = KEY_X, + [RGFW_keyY] = KEY_Y, + [RGFW_keyZ] = KEY_Z, + [RGFW_keyBracket] = KEY_LEFT_BRACKET, + [RGFW_keyBackSlash] = KEY_BACKSLASH, + [RGFW_keyCloseBracket] = KEY_RIGHT_BRACKET, + [RGFW_keyBacktick] = KEY_GRAVE, + [RGFW_keySpace] = KEY_SPACE, + [RGFW_keyEscape] = KEY_ESCAPE, + [RGFW_keyReturn] = KEY_ENTER, + [RGFW_keyTab] = KEY_TAB, + [RGFW_keyBackSpace] = KEY_BACKSPACE, + [RGFW_keyInsert] = KEY_INSERT, + [RGFW_keyDelete] = KEY_DELETE, + [RGFW_keyRight] = KEY_RIGHT, + [RGFW_keyLeft] = KEY_LEFT, + [RGFW_keyDown] = KEY_DOWN, + [RGFW_keyUp] = KEY_UP, + [RGFW_keyPageUp] = KEY_PAGE_UP, + [RGFW_keyPageDown] = KEY_PAGE_DOWN, + [RGFW_keyHome] = KEY_HOME, + [RGFW_keyEnd] = KEY_END, + [RGFW_keyCapsLock] = KEY_CAPS_LOCK, + [RGFW_keyScrollLock] = KEY_SCROLL_LOCK, + [RGFW_keyNumLock] = KEY_NUM_LOCK, + [RGFW_keyPrintScreen] = KEY_PRINT_SCREEN, + [RGFW_keyPause] = KEY_PAUSE, + [RGFW_keyF1] = KEY_F1, + [RGFW_keyF2] = KEY_F2, + [RGFW_keyF3] = KEY_F3, + [RGFW_keyF4] = KEY_F4, + [RGFW_keyF5] = KEY_F5, + [RGFW_keyF6] = KEY_F6, + [RGFW_keyF7] = KEY_F7, + [RGFW_keyF8] = KEY_F8, + [RGFW_keyF9] = KEY_F9, + [RGFW_keyF10] = KEY_F10, + [RGFW_keyF11] = KEY_F11, + [RGFW_keyF12] = KEY_F12, + [RGFW_keyShiftL] = KEY_LEFT_SHIFT, + [RGFW_keyControlL] = KEY_LEFT_CONTROL, + [RGFW_keyAltL] = KEY_LEFT_ALT, + [RGFW_keySuperL] = KEY_LEFT_SUPER, + // #ifndef RGFW_MACOS + [RGFW_keyShiftR] = KEY_RIGHT_SHIFT, + [RGFW_keyControlR] = KEY_RIGHT_CONTROL, + [RGFW_keyAltR] = KEY_RIGHT_ALT, + [RGFW_keySuperR] = KEY_RIGHT_SUPER, + // #endif + [RGFW_keyMenu] = KEY_KB_MENU, + [RGFW_keyPad0] = KEY_KP_0, + [RGFW_keyPad1] = KEY_KP_1, + [RGFW_keyPad2] = KEY_KP_2, + [RGFW_keyPad3] = KEY_KP_3, + [RGFW_keyPad4] = KEY_KP_4, + [RGFW_keyPad5] = KEY_KP_5, + [RGFW_keyPad6] = KEY_KP_6, + [RGFW_keyPad7] = KEY_KP_7, + [RGFW_keyPad8] = KEY_KP_8, + [RGFW_keyPad9] = KEY_KP_9, + [RGFW_keyPadPeriod] = KEY_KP_DECIMAL, + [RGFW_keyPadSlash] = KEY_KP_DIVIDE, + [RGFW_keyPadMultiply] = KEY_KP_MULTIPLY, + [RGFW_keyPadMinus] = KEY_KP_SUBTRACT, + [RGFW_keyPadPlus] = KEY_KP_ADD, + [RGFW_keyPadReturn] = KEY_KP_ENTER, + [RGFW_keyPadEqual] = KEY_KP_EQUAL, +}; + +static int mg_buttonConvertTable[] = { + [MG_BUTTON_NORTH] = GAMEPAD_BUTTON_RIGHT_FACE_UP, + [MG_BUTTON_EAST] = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, + [MG_BUTTON_SOUTH] = GAMEPAD_BUTTON_RIGHT_FACE_DOWN, + [MG_BUTTON_WEST] = GAMEPAD_BUTTON_RIGHT_FACE_LEFT, + [MG_BUTTON_LEFT_SHOULDER] = GAMEPAD_BUTTON_LEFT_TRIGGER_1, + [MG_BUTTON_RIGHT_SHOULDER] = GAMEPAD_BUTTON_RIGHT_TRIGGER_1, + [MG_BUTTON_LEFT_TRIGGER] = GAMEPAD_BUTTON_LEFT_TRIGGER_2, + [MG_BUTTON_RIGHT_TRIGGER] = GAMEPAD_BUTTON_RIGHT_TRIGGER_2, + [MG_BUTTON_BACK] = GAMEPAD_BUTTON_MIDDLE_LEFT, + [MG_BUTTON_GUIDE] = GAMEPAD_BUTTON_MIDDLE, + [MG_BUTTON_START] = GAMEPAD_BUTTON_MIDDLE_RIGHT, + [MG_BUTTON_DPAD_UP] = GAMEPAD_BUTTON_LEFT_FACE_UP, + [MG_BUTTON_DPAD_RIGHT] = GAMEPAD_BUTTON_LEFT_FACE_RIGHT, + [MG_BUTTON_DPAD_DOWN] = GAMEPAD_BUTTON_LEFT_FACE_DOWN, + [MG_BUTTON_DPAD_LEFT] = GAMEPAD_BUTTON_LEFT_FACE_LEFT, + [MG_BUTTON_LEFT_STICK] = GAMEPAD_BUTTON_LEFT_THUMB, + [MG_BUTTON_RIGHT_STICK] = GAMEPAD_BUTTON_RIGHT_THUMB, +}; + +static int mg_axisConvertTable[] = { + [MG_AXIS_LEFT_X] = GAMEPAD_AXIS_LEFT_X, + [MG_AXIS_LEFT_Y] = GAMEPAD_AXIS_LEFT_Y, + [MG_AXIS_RIGHT_X] = GAMEPAD_AXIS_RIGHT_X, + [MG_AXIS_RIGHT_Y] = GAMEPAD_AXIS_RIGHT_Y, + [MG_AXIS_LEFT_TRIGGER] = GAMEPAD_AXIS_LEFT_TRIGGER, + [MG_AXIS_RIGHT_TRIGGER] = GAMEPAD_AXIS_RIGHT_TRIGGER, + + /* unsupported in raylib */ + [MG_AXIS_HAT_DPAD_LEFT_RIGHT] = -1, + [MG_AXIS_HAT_DPAD_LEFT] = -1, + [MG_AXIS_HAT_DPAD_RIGHT] = -1, + [MG_AXIS_HAT_DPAD_UP_DOWN] = -1, + [MG_AXIS_HAT_DPAD_UP] = -1, + [MG_AXIS_HAT_DPAD_DOWN] = -1, +}; + +static KeyboardKey ConvertScancodeToKey(u32 keycode); +void RemapMouseToTouch(int touchAction); + +// --------------------------------------------------------------------------------- +// RGFW Callbacks (instead of the older polling) +// --------------------------------------------------------------------------------- +static void RGFW_cb_mousenotifyfunc(const RGFW_event *e) +{ + if (e->common.win != platform.window) return; + + CORE.Input.Mouse.cursorOnScreen = e->mouse.inWindow; +} +/* +static void RGFW_cb_windowclosefunc(const RGFW_event *e) +{ + if (e->common.win != platform.window) return; + + // Don't want to close here, raylib handles it + //RGFW_window_setShouldClose(platform.window, true); +} +*/ +static void RGFW_cb_dropfunc(const RGFW_event *e) +{ + if (e->common.win != platform.window) return; + + if (CORE.Window.dropFileCount == 0) + { + CORE.Window.dropFilepaths = (char **)RL_CALLOC(1024, sizeof(char *)); + + CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + snprintf(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], MAX_FILEPATH_LENGTH, "%s", e->drop.value->data); + + CORE.Window.dropFileCount++; + } + else if (CORE.Window.dropFileCount < 1024) + { + CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + snprintf(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], MAX_FILEPATH_LENGTH, "%s", e->drop.value->data); + + CORE.Window.dropFileCount++; + } + else TRACELOG(LOG_WARNING, "FILE: Maximum drag and drop files at once is limited to 1024 files!"); +} +static void RGFW_cb_windowresizefunc(const RGFW_event *e) +{ + if (e->common.win != platform.window) return; + + CORE.Window.resizedLastFrame = true; + + #if defined(__APPLE__) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + RGFW_monitor *currentMonitor = RGFW_window_getMonitor(platform.window); + SetupViewport(platform.window->w*currentMonitor->pixelRatio, platform.window->h*currentMonitor->pixelRatio); + CORE.Window.screenScale = MatrixScale(currentMonitor->pixelRatio, currentMonitor->pixelRatio, 1.0f); + + CORE.Window.screen.width = platform.window->w; + CORE.Window.screen.height = platform.window->h; + CORE.Window.render.width = CORE.Window.screen.width*currentMonitor->pixelRatio; + CORE.Window.render.height = CORE.Window.screen.height*currentMonitor->pixelRatio; + } + else + { + SetupViewport(platform.window->w, platform.window->h); + CORE.Window.screen.width = platform.window->w; + CORE.Window.screen.height = platform.window->h; + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + } + + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + #elif defined(PLATFORM_WEB_RGFW) + // do nothing but prevent other behavior + #else + SetupViewport(platform.window->w, platform.window->h); + + // Consider content scaling if required + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + Vector2 scaleDpi = GetWindowScaleDPI(); + CORE.Window.screen.width = (int)(platform.window->w/scaleDpi.x); + CORE.Window.screen.height = (int)(platform.window->h/scaleDpi.y); + CORE.Window.screenScale = MatrixScale(scaleDpi.x, scaleDpi.y, 1.0f); + } + else + { + CORE.Window.screen.width = platform.window->w; + CORE.Window.screen.height = platform.window->h; + } + + CORE.Window.currentFbo.width = CORE.Window.screen.width; + CORE.Window.currentFbo.height = CORE.Window.screen.height; + #endif + + #if defined(GRAPHICS_API_OPENGL_SOFTWARE) + #if defined(__APPLE__) + RGFW_monitor *currentMonitor = RGFW_window_getMonitor(platform.window); + CORE.Window.screenScale = MatrixScale(currentMonitor->pixelRatio, currentMonitor->pixelRatio, 1.0f); + SetupViewport(platform.window->w*currentMonitor->pixelRatio, platform.window->h*currentMonitor->pixelRatio); + + CORE.Window.render.width = CORE.Window.screen.width*currentMonitor->pixelRatio; + CORE.Window.render.height = CORE.Window.screen.height*currentMonitor->pixelRatio; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + #endif + platform.surfaceWidth = CORE.Window.currentFbo.width; + platform.surfaceHeight = CORE.Window.currentFbo.height; + + // in software mode we dont have the viewport so we need to reverse the highdpi changes + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + Vector2 scaleDpi = GetWindowScaleDPI(); + platform.surfaceWidth *= scaleDpi.x; + platform.surfaceHeight *= scaleDpi.y; + } + + if (platform.surfacePixels != NULL) + { + RL_FREE(platform.surfacePixels); + platform.surfacePixels = RL_MALLOC(platform.surfaceWidth*platform.surfaceHeight*4); + } + + if (platform.surface != NULL) + { + RGFW_surface_free(platform.surface); + platform.surface = RGFW_window_createSurface(platform.window, platform.surfacePixels, platform.surfaceWidth, platform.surfaceHeight, RGFW_formatBGRA8); + swResize(platform.surfaceWidth, platform.surfaceHeight); + } + #endif +} +static void RGFW_cb_windowmaximizefunc(const RGFW_event *e) +{ + if (e->common.win != platform.window) return; + + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was maximized +} +static void RGFW_cb_windowminimizefunc(const RGFW_event *e) +{ + if (e->common.win != platform.window) return; + + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was iconified +} +static void RGFW_cb_windowrestorefunc(const RGFW_event *e) +{ + if (e->common.win != platform.window) return; + + if (RGFW_window_isMaximized(platform.window)) + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was restored + if (RGFW_window_isMinimized(platform.window)) + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was restored +} +static void RGFW_cb_windowmovefunc(const RGFW_event *e) +{ + if (e->common.win != platform.window) return; + + CORE.Window.position.x = platform.window->x; + CORE.Window.position.y = platform.window->y; +} +static void RGFW_cb_keycharfunc(const RGFW_event *e) +{ + if (e->common.win != platform.window) return; + + // NOTE: event.text.text data comes an UTF-8 text sequence but registering codepoints (int) + // Check if there is space available in the queue + if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) + { + // Add character (codepoint) to the queue + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = e->keyChar.value; + CORE.Input.Keyboard.charPressedQueueCount++; + } +} +static void RGFW_cb_scrollfunc(const RGFW_event *e) +{ + if (e->common.win != platform.window) return; + + CORE.Input.Mouse.currentWheelMove.x += e->delta.x; + CORE.Input.Mouse.currentWheelMove.y += e->delta.y; +} +static void RGFW_cb_mousebuttonfunc(const RGFW_event *e) +{ + if (e->common.win != platform.window) return; + + int btn = e->button.value; + if (btn == RGFW_mouseLeft) btn = 1; + else if (btn == RGFW_mouseRight) btn = 2; + else if (btn == RGFW_mouseMiddle) btn = 3; + + // pressed or released + if (e->button.state){ + CORE.Input.Mouse.currentButtonState[btn - 1] = 1; + CORE.Input.Touch.currentTouchState[btn - 1] = 1; + + // simulate touch with the mouse + RemapMouseToTouch(1); + } else + { + CORE.Input.Mouse.currentButtonState[btn - 1] = 0; + CORE.Input.Touch.currentTouchState[btn - 1] = 0; + + // simulate touch with the mouse + RemapMouseToTouch(0); + } +} +static void RGFW_cb_mouserawmotionfunc(const RGFW_event *e) +{ + if (!RGFW_window_isRawMouseMode(platform.window)) + { + // if not raw, use non-raw motion. this prevents the doubled events + return; + } + + float mouseX = 0.0f; + float mouseY = 0.0f; + mouseX = e->delta.x; + mouseY = e->delta.y; + + #if defined(__EMSCRIPTEN__) + double canvasWidth = 0.0; + double canvasHeight = 0.0; + emscripten_get_element_css_size("#canvas", &canvasWidth, &canvasHeight); + mouseX *= ((float)GetScreenWidth()/(float)canvasWidth); + mouseY *= ((float)GetScreenHeight()/(float)canvasHeight); + #endif + + CORE.Input.Mouse.currentPosition.x += mouseX; + CORE.Input.Mouse.currentPosition.y += mouseY; + + // simulate touch with the mouse + CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + RemapMouseToTouch(2); +} +static void RGFW_cb_mousemotionfunc(const RGFW_event *e) +{ + if (RGFW_window_isRawMouseMode(platform.window)) + { + // if raw, use raw motion. this prevents the doubled events + return; + } + + float mouseX = 0.0f; + float mouseY = 0.0f; + mouseX = e->mouse.x; + mouseY = e->mouse.y; + + #if defined(__EMSCRIPTEN__) + double canvasWidth = 0.0; + double canvasHeight = 0.0; + emscripten_get_element_css_size("#canvas", &canvasWidth, &canvasHeight); + mouseX *= ((float)GetScreenWidth()/(float)canvasWidth); + mouseY *= ((float)GetScreenHeight()/(float)canvasHeight); + #endif + + CORE.Input.Mouse.currentPosition.x = mouseX; + CORE.Input.Mouse.currentPosition.y = mouseY; + + // simulate touch with the mouse + CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + RemapMouseToTouch(2); +} +static void RGFW_cb_keyfunc(const RGFW_keyEvent* e) +{ + if (e->win != platform.window) return; + + KeyboardKey key = ConvertScancodeToKey(e->value); + if (key == KEY_NULL) return; + + // pressed or released + if (e->state) + { + // If key was up, add it to the key pressed queue + if ((CORE.Input.Keyboard.currentKeyState[key] == 0) && (CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE)) + { + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + + CORE.Input.Keyboard.currentKeyState[key] = 1; + + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey]) RGFW_window_setShouldClose(platform.window, true); + } + else + { + CORE.Input.Keyboard.currentKeyState[key] = 0; + } +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +bool InitGraphicsDevice(void); // Initialize graphics device + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +bool WindowShouldClose(void) +{ + if (CORE.Window.shouldClose == false) CORE.Window.shouldClose = RGFW_window_shouldClose(platform.window); + if (CORE.Window.ready) return CORE.Window.shouldClose; + return true; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + { + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + // Store previous window position (in case of exiting fullscreen) + Vector2 currentPosition = GetWindowPosition(); + CORE.Window.previousPosition.x = currentPosition.x; + CORE.Window.previousPosition.y = currentPosition.y; + CORE.Window.previousScreen = CORE.Window.screen; + + RGFW_monitor *currentMonitor = RGFW_window_getMonitor(platform.window); + RGFW_monitor_scaleToWindow(currentMonitor, platform.window); + RGFW_window_setFullscreen(platform.window, 1); + } + else + { + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + + // Update the window position right away + CORE.Window.position = CORE.Window.previousPosition; + RGFW_window_setFullscreen(platform.window, 0); + RGFW_window_move(platform.window, CORE.Window.position.x, CORE.Window.position.y); + RGFW_window_resize(platform.window, CORE.Window.previousScreen.width, CORE.Window.previousScreen.height); + } + + // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) + // NOTE: V-Sync can be enabled by graphic driver configuration + if (FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) RGFW_window_swapInterval_OpenGL(platform.window, 1); +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + { + ToggleFullscreen(); + + // it seems like returning here is a more desireable outcome + return; + } + + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + + Vector2 currentPosition = GetWindowPosition(); + CORE.Window.previousPosition.x = (int)currentPosition.x; + CORE.Window.previousPosition.y = (int)currentPosition.y; + CORE.Window.previousScreen = CORE.Window.screen; + + RGFW_monitor *currentMonitor = RGFW_window_getMonitor(platform.window); + RGFW_window_setBorder(platform.window, 0); + RGFW_window_move(platform.window, 0, 0); + RGFW_window_resize(platform.window, currentMonitor->mode.w, currentMonitor->mode.h); + } + else + { + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + RGFW_window_setBorder(platform.window, 1); + + CORE.Window.position = CORE.Window.previousPosition; + + RGFW_window_resize(platform.window, CORE.Window.previousScreen.width, CORE.Window.previousScreen.height); + RGFW_window_move(platform.window, CORE.Window.position.x, CORE.Window.position.y); + } +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + RGFW_window_maximize(platform.window); +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + RGFW_window_minimize(platform.window); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); + + RGFW_window_restore(platform.window); +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); + + FLAG_SET(CORE.Window.flags, flags); + + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + RGFW_window_swapInterval_OpenGL(platform.window, 1); + } + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + ToggleFullscreen(); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) + { + RGFW_window_setMaxSize(platform.window, 0, 0); + RGFW_window_setMinSize(platform.window, 0, 0); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + RGFW_window_setBorder(platform.window, 0); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + RGFW_window_hide(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + RGFW_window_minimize(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) + { + RGFW_window_maximize(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) + { + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); + FLAG_CLEAR(platform.window->internal.flags, RGFW_windowFocusOnShow); + RGFW_window_setFlags(platform.window, platform.window->internal.flags); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) + { + RGFW_window_setFloating(platform.window, RGFW_TRUE); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + RGFW_window_setMousePassthrough(platform.window, 1); + } + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + ToggleBorderlessWindowed(); + } + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + RGFW_glHints *hints = RGFW_getGlobalHints_OpenGL(); + hints->samples = 4; + RGFW_setGlobalHints_OpenGL(hints); + } + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only be configured before window initialization"); + } +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + FLAG_CLEAR(CORE.Window.flags, flags); + + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + RGFW_window_swapInterval_OpenGL(platform.window, 0); + } + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + ToggleFullscreen(); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) + { + RGFW_window_setMaxSize(platform.window, platform.window->w, platform.window->h); + RGFW_window_setMinSize(platform.window, platform.window->w, platform.window->h); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + RGFW_window_setBorder(platform.window, 1); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); + + RGFW_window_show(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); + + RGFW_window_restore(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) + { + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); + + RGFW_window_restore(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) + { + RGFW_window_setFlags(platform.window, platform.window->internal.flags | RGFW_windowFocusOnShow); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) + { + RGFW_window_setFloating(platform.window, RGFW_FALSE); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + RGFW_window_setMousePassthrough(platform.window, 0); + } + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + ToggleBorderlessWindowed(); + } + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + RGFW_glHints *hints = RGFW_getGlobalHints_OpenGL(); + hints->samples = 0; + RGFW_setGlobalHints_OpenGL(hints); + } + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only be configured before window initialization"); + } +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + if (image.format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + TRACELOG(LOG_WARNING, "RGFW: Window icon image must be in R8G8B8A8 pixel format"); + return; + } + RGFW_window_setIcon(platform.window, (u8 *)image.data, image.width, image.height, RGFW_formatRGBA8); +} + +// Set icon for window +void SetWindowIcons(Image *images, int count) +{ + if ((images == NULL) || (count <= 0)) + { + RGFW_window_setIcon(platform.window, NULL, 0, 0, 0); + } + else + { + Image *bigIcon = NULL; + Image *smallIcon = NULL; + + for (int i = 0; i < count; i++) + { + if (images[i].format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + TRACELOG(LOG_WARNING, "RGFW: Window icon image must be in R8G8B8A8 pixel format"); + continue; + } + if ((bigIcon == NULL) || ((images[i].width > bigIcon->width) && (images[i].height > bigIcon->height))) bigIcon = &images[i]; + if ((smallIcon == NULL) || ((images[i].width < smallIcon->width) && (images[i].height > smallIcon->height))) smallIcon = &images[i]; + } + + if (smallIcon != NULL) RGFW_window_setIconEx(platform.window, (u8 *)smallIcon->data, smallIcon->width, smallIcon->height, RGFW_formatRGBA8, RGFW_iconWindow); + if (bigIcon != NULL) RGFW_window_setIconEx(platform.window, (u8 *)bigIcon->data, bigIcon->width, bigIcon->height, RGFW_formatRGBA8, RGFW_iconTaskbar); + } +} + +// Set title for window +void SetWindowTitle(const char *title) +{ + RGFW_window_setName(platform.window, (char *)title); + CORE.Window.title = title; +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + RGFW_window_move(platform.window, x, y); +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + RGFW_window_moveToMonitor(platform.window, RGFW_getMonitors(NULL)[monitor]); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + RGFW_window_setMinSize(platform.window, width, height); + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + RGFW_window_setMaxSize(platform.window, width, height); + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + + Vector2 scaleDpi = GetWindowScaleDPI(); + + #if defined(__APPLE__) + RGFW_monitor *currentMonitor = RGFW_window_getMonitor(platform.window); + CORE.Window.screenScale = MatrixScale(currentMonitor->pixelRatio, currentMonitor->pixelRatio, 1.0f); + + CORE.Window.render.width = CORE.Window.screen.width*currentMonitor->pixelRatio; + CORE.Window.render.height = CORE.Window.screen.height*currentMonitor->pixelRatio; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + #else + SetMouseScale(1.0f/scaleDpi.x, 1.0f/scaleDpi.y); + CORE.Window.screenScale = MatrixScale(scaleDpi.x, scaleDpi.y, 1.0f); + #endif + + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + } + else + { + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + } + + if (!CORE.Window.usingFbo) + { + SetupViewport(CORE.Window.screen.width, CORE.Window.screen.height); + } + + RGFW_window_resize(platform.window, CORE.Window.screen.width, CORE.Window.screen.height); +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + RGFW_window_setOpacity(platform.window, opacity); +} + +// Set window focused +void SetWindowFocused(void) +{ + RGFW_window_focus(platform.window); +} + +// Get native window handle +void *GetWindowHandle(void) +{ + void *handle = NULL; + + if (platform.window != NULL) + { +#if defined(_WIN32) + handle = (void *)platform.window->src.window; // Type: HWND +#elif defined(__linux__) + #if defined(RGFW_X11) + platform.windowHandleX11 = platform.window->src.window; // Type: Window (unsigned long) + handle = &platform.window->src.window; + #elif defined(RGFW_WAYLAND) + handle = (void *)platform.window->src.surface; // Type: struct wl_surface* + #endif +#elif defined(__APPLE__) + handle = (void *)platform.window->src.window; // Type: id, NSWindow* +#endif + } + + return handle; +} + +// Get number of monitors +int GetMonitorCount(void) +{ + size_t count = 0; + RGFW_getMonitors(&count); + + return count; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + RGFW_monitor **mons = RGFW_getMonitors(NULL); + RGFW_monitor *mon = NULL; + + if (platform.window) mon = RGFW_window_getMonitor(platform.window); + else mon = RGFW_getPrimaryMonitor(); + + for (int i = 0; i < 6; i++) + { + if ((mons[i]->x == mon->x) && (mons[i]->y == mon->y)) return i; + } + + return 0; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + RGFW_monitor **mons = RGFW_getMonitors(NULL); + + return (Vector2){ (float)mons[monitor]->x, (float)mons[monitor]->y }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + RGFW_monitor **mons = RGFW_getMonitors(NULL); + + return mons[monitor]->mode.w; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + RGFW_monitor **mons = RGFW_getMonitors(NULL); + + return mons[monitor]->mode.h; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + RGFW_monitor **mons = RGFW_getMonitors(NULL); + + return mons[monitor]->physW; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + RGFW_monitor **mons = RGFW_getMonitors(NULL); + + return (int)mons[monitor]->physH; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + RGFW_monitor **mons = RGFW_getMonitors(NULL); + + return (int)mons[monitor]->mode.refreshRate; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ + RGFW_monitor **mons = RGFW_getMonitors(NULL); + + return mons[monitor]->name; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + if (platform.window == NULL) + { + return (Vector2){ 0.0f, 0.0f }; + } + + if (RGFW_window_getPosition(platform.window, &platform.window->x, &platform.window->y)) { + return (Vector2){ (float)platform.window->x, (float)platform.window->y }; + } + + return (Vector2){ 0.0f, 0.0f }; +} + +// Get window scale DPI factor for current monitor +Vector2 GetWindowScaleDPI(void) +{ + RGFW_monitor *monitor = NULL; + + if (platform.window) monitor = RGFW_window_getMonitor(platform.window); + else monitor = RGFW_getPrimaryMonitor(); + + #if defined(__APPLE__) + // Apple does < 1.0f scaling, example: 0.66f, 0.5f + // it needs to be convert to be consistent + return (Vector2){ 1.0f/monitor->scaleX, 1.0f/monitor->scaleX }; + #else + // Linux and Windows do >= 1.0f scaling, example: 1.0f, 1.25f, 2.0f + return (Vector2){ monitor->scaleX, monitor->scaleX }; + #endif +} + +// Get monitor pixel ratio +// WARNING: Function not used, neither exposed by raylib +float GetMonitorPixelRatio(void) +{ + RGFW_monitor *monitor = NULL; + + if (platform.window) monitor = RGFW_window_getMonitor(platform.window); + else monitor = RGFW_getPrimaryMonitor(); + + return monitor->pixelRatio; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + // add 1 for null terminator + RGFW_writeClipboard(text, strlen(text)+1); +} + +// Get clipboard text content +// NOTE: returned string is allocated and freed by RGFW +const char *GetClipboardText(void) +{ + return RGFW_readClipboard(NULL); +} + +#if SUPPORT_CLIPBOARD_IMAGE +#if defined(_WIN32) + #define WIN32_CLIPBOARD_IMPLEMENTATION + #define WINUSER_ALREADY_INCLUDED + #define WINBASE_ALREADY_INCLUDED + #define WINGDI_ALREADY_INCLUDED + #include "../external/win32_clipboard.h" +#elif defined(__linux__) && defined(DRGFW_X11) + #include + #include +#endif +#endif + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + +#if SUPPORT_CLIPBOARD_IMAGE && SUPPORT_MODULE_RTEXTURES +#if defined(_WIN32) + + unsigned int dataSize = 0; + void *fileData = NULL; + + int width = 0; + int height = 0; + fileData = (void *)Win32GetClipboardImageData(&width, &height, &dataSize); + + if (fileData == NULL) TRACELOG(LOG_WARNING, "Clipboard image: Couldn't get clipboard data"); + else + { + image = LoadImageFromMemory(".bmp", (const unsigned char *)fileData, (int)dataSize); + + RL_FREE(fileData); + } + +#elif defined(__linux__) && defined(DRGFW_X11) + + // REF: https://github.com/ColleagueRiley/Clipboard-Copy-Paste/blob/main/x11.c + Display *dpy = XOpenDisplay(NULL); + if (!dpy) return image; + + Window root = DefaultRootWindow(dpy); + Window win = XCreateSimpleWindow( + dpy, // The connection to the X Server + root, // The 'Parent' window (usually the desktop/root) + 0, 0, // X and Y position on the screen + 1, 1, // Width and Height (1x1 pixel) + 0, // Border width + 0, // Border color + 0 // Background color + ); + + Atom clipboard = XInternAtom(dpy, "CLIPBOARD", False); + Atom targetType = XInternAtom(dpy, "image/png", False); // Ask for PNG + Atom property = XInternAtom(dpy, "RAYLIB_CLIPBOARD_MANAGER", False); + + // Request the data: "Convert whatever is in CLIPBOARD to image/png and put it in RAYLIB_CLIPBOARD_MANAGER" + XConvertSelection(dpy, clipboard, targetType, property, win, CurrentTime); + + // Wait for the SelectionNotify event + XEvent ev = { 0 }; + XNextEvent(dpy, &ev); + + Atom actualType = { 0 }; + int actualFormat = 0; + unsigned long nitems = 0; + unsigned long bytesAfter = 0; + unsigned char *data = NULL; + + // Read the data from our ghost window's property + XGetWindowProperty(dpy, win, property, 0, ~0L, False, AnyPropertyType, + &actualType, &actualFormat, &nitems, &bytesAfter, &data); + + if (data != NULL) + { + image = LoadImageFromMemory(".png", data, (int)nitems); + XFree(data); + } + + XDestroyWindow(dpy, win); + XCloseDisplay(dpy); +#else + TRACELOG(LOG_WARNING, "Clipboard image: PLATFORM_DESKTOP_RGFW doesn't implement GetClipboardImage() for this OS"); +#endif // _WIN32 +#else + TRACELOG(LOG_WARNING, "Clipboard image: SUPPORT_CLIPBOARD_IMAGE requires SUPPORT_MODULE_RTEXTURES to work properly"); +#endif // SUPPORT_CLIPBOARD_IMAGE + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + RGFW_window_showMouse(platform.window, true); + CORE.Input.Mouse.cursorHidden = false; +} + +// Hide mouse cursor +void HideCursor(void) +{ + RGFW_window_showMouse(platform.window, false); + CORE.Input.Mouse.cursorHidden = true; +} + +// Enable cursor (unlock cursor) +void EnableCursor(void) +{ + RGFW_window_captureRawMouse(platform.window, false); + + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + ShowCursor(); + + CORE.Input.Mouse.cursorLocked = true; +} + +// Disable cursor (lock cursor) +void DisableCursor(void) +{ + RGFW_window_captureRawMouse(platform.window, true); + HideCursor(); + + CORE.Input.Mouse.cursorLocked = true; +} + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ + #if defined(GRAPHICS_API_OPENGL_SOFTWARE) + { + if (platform.surface) + { + // Copy rlsw pixel data to the surface framebuffer + rlCopyFramebuffer(0, 0, platform.surfaceWidth, platform.surfaceHeight, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.surfacePixels); + + // Mac wants a different pixel order. I cant seem to get this to work any other way + #if defined(__APPLE__) + unsigned char temp = 0; + unsigned char *p = NULL; + for (int i = 0; i < (platform.surfaceWidth*platform.surfaceHeight); i += 1) + { + p = platform.surfacePixels + (i*4); + temp = p[0]; + p[0] = p[2]; + p[2] = temp; + } + #endif + + // blit surface to the window + RGFW_window_blitSurface(platform.window, platform.surface); + } + } + #else + { + RGFW_window_swapBuffers_OpenGL(platform.window); + } + #endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds since InitTimer() +double GetTime(void) +{ + // CORE.Time.base is nanoseconds as integer + double baseTime = (double)CORE.Time.base*1e-9; + double time = GetTimeSeconds() - baseTime; + + return time; +} + +// Open URL with default system browser (if available) +// WARNING: This function is only safe to use if you control the URL given, +// a user could craft a malicious string to perform and undesired action +// NOTE: Some safety checks have been added to mitigate security issues +void OpenURL(const char *url) +{ + // Security check to (partially) avoid malicious code + if ((strchr(url, '\'') != NULL) || (strchr(url, '\"') != NULL)) + { + // Filter characters: ' and " + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'\"] characters"); + } + else if ((strncmp(url, "http://", 7) != 0) && (strncmp(url, "https://", 8) != 0)) + { + // Only allow URL starting with "http://" or "https://" protocols + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL must start with 'http://' or 'https://' protocols"); + } + else + { + char *cmd = (char *)RL_CALLOC(strlen(url) + 16, sizeof(char)); +#if defined(_WIN32) + sprintf(cmd, "explorer \"%s\"", url); +#endif +#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) + sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser +#endif +#if defined(__APPLE__) + sprintf(cmd, "open '%s'", url); +#endif + int result = system(cmd); + if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created"); + RL_FREE(cmd); + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + return mg_update_gamepad_mappings(&platform.minigamepad, mappings); +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + TRACELOG(LOG_WARNING, "SetGamepadVibration() unsupported on target platform"); +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + RGFW_window_moveMouse(platform.window, x, y); + CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; +} + +// Set mouse cursor +void SetMouseCursor(int cursor) +{ + RGFW_window_setMouseStandard(platform.window, cursor); +} + +// Get physical key name +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName() unsupported on target platform"); + + return ""; +} + +// Register all input events +void PollInputEvents(void) +{ +#if SUPPORT_GESTURES_SYSTEM + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + + // Reset mouse wheel + CORE.Input.Mouse.currentWheelMove.x = 0; + CORE.Input.Mouse.currentWheelMove.y = 0; + + // Register previous mouse position + + // Reset last gamepad button/axis registered state + for (int i = 0; i < MAX_GAMEPADS; i++) + { + // Check if gamepad is available + if (CORE.Input.Gamepad.ready[i]) + { + // Register previous gamepad button states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) + { + CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; + } + } + } + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Map touch position to mouse position for convenience + CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + + // Register previous keys states + // NOTE: Android supports up to 260 keys + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) + { + CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // Register previous mouse states + for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + + // Poll input events for current platform + //----------------------------------------------------------------------------- + CORE.Window.resizedLastFrame = false; + + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + if (RGFW_window_isCaptured(platform.window)) + { + CORE.Input.Mouse.previousPosition = (Vector2){ 0.0f, 0.0f }; + CORE.Input.Mouse.currentPosition = (Vector2){ 0.0f, 0.0f }; + } + else + { + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + } + + if ((CORE.Window.eventWaiting) || (IsWindowState(FLAG_WINDOW_MINIMIZED) && !IsWindowState(FLAG_WINDOW_ALWAYS_RUN))) + { + CORE.Time.previous = GetTime(); + } + + //----------------------------------------------------------------------------- + // Using RGFW callbacks instead of polling + RGFW_pollEvents(); + //----------------------------------------------------------------------------- + + mg_event gamepad_event = { 0 }; + while (mg_gamepads_check_event(&platform.minigamepad, &gamepad_event)) + { + int gamepadIndex = gamepad_event.gamepad->index; + + switch (gamepad_event.type) + { + case MG_EVENT_BUTTON_PRESS: + { + int button = mg_buttonConvertTable[gamepad_event.button]; + if (button >= 0) + { + CORE.Input.Gamepad.currentButtonState[gamepadIndex][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + } + } break; + case MG_EVENT_BUTTON_RELEASE: + { + int button = mg_buttonConvertTable[gamepad_event.button]; + if (button >= 0) + { + CORE.Input.Gamepad.currentButtonState[gamepadIndex][button] = 0; + if (CORE.Input.Gamepad.lastButtonPressed == button) CORE.Input.Gamepad.lastButtonPressed = 0; + } + } break; + case MG_EVENT_AXIS_MOVE: + { + int axis = mg_axisConvertTable[gamepad_event.axis]; + + switch (axis) { + case GAMEPAD_AXIS_LEFT_X: + case GAMEPAD_AXIS_LEFT_Y: + case GAMEPAD_AXIS_RIGHT_X: + case GAMEPAD_AXIS_RIGHT_Y: + { + CORE.Input.Gamepad.axisState[gamepadIndex][axis] = platform.minigamepad.gamepads[gamepadIndex].axes[gamepad_event.axis].value; + } break; + case GAMEPAD_AXIS_LEFT_TRIGGER: + case GAMEPAD_AXIS_RIGHT_TRIGGER: + { + CORE.Input.Gamepad.axisState[gamepadIndex][axis] = platform.minigamepad.gamepads[gamepadIndex].axes[gamepad_event.axis].value; + + // Trigger button press when axis is all the way + int button = (axis == GAMEPAD_AXIS_LEFT_TRIGGER) ? GAMEPAD_BUTTON_LEFT_TRIGGER_2 : GAMEPAD_BUTTON_RIGHT_TRIGGER_2; + int pressed = (platform.minigamepad.gamepads[gamepadIndex].axes[gamepad_event.axis].value >= 1.0f); + + CORE.Input.Gamepad.currentButtonState[gamepadIndex][button] = pressed; + if (pressed) CORE.Input.Gamepad.lastButtonPressed = button; + else if (CORE.Input.Gamepad.lastButtonPressed == button) CORE.Input.Gamepad.lastButtonPressed = 0; + } break; + default: break; + } + } break; + case MG_EVENT_GAMEPAD_CONNECT: + { + CORE.Input.Gamepad.ready[gamepadIndex] = true; + CORE.Input.Gamepad.axisState[gamepadIndex][GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; + CORE.Input.Gamepad.axisState[gamepadIndex][GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; + + int axisCount = 0; + for (int i = 0; i < MG_AXIS_COUNT; i += 1) + { + if (platform.minigamepad.gamepads[gamepadIndex].axes[i].supported) axisCount += 1; + else break; + } + + CORE.Input.Gamepad.axisCount[gamepadIndex] = axisCount; + snprintf(CORE.Input.Gamepad.name[gamepadIndex], MAX_GAMEPAD_NAME_LENGTH, "%s", platform.minigamepad.gamepads[gamepadIndex].name); + + } break; + case MG_EVENT_GAMEPAD_DISCONNECT: CORE.Input.Gamepad.ready[gamepadIndex] = false; break; + default: break; + } + } +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + // Initialize RGFW internal global state, only required systems + unsigned int flags = RGFW_windowCenter | RGFW_windowAllowDND; + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) FLAG_SET(flags, RGFW_windowNoBorder); + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) FLAG_SET(flags, RGFW_windowNoResize); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT)) FLAG_SET(flags, RGFW_windowTransparent); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) FLAG_SET(flags, RGFW_windowHide); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_SET(flags, RGFW_windowMaximize); + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) FLAG_SET(flags, RGFW_windowFocusOnShow | RGFW_windowFocus); + + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) + { + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + + // Check window creation flags + + // Init window in fullscreen mode if requested + // NOTE: Keeping original screen size for toggle + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + { + FLAG_SET(flags, RGFW_windowFullscreen); + int result = RGFW_init(); + if (result != 0) + { + TRACELOG(LOG_WARNING, "RGFW: Failed to initialize RGFW"); + return -1; + } + + // NOTE: Fullscreen applications default to the primary monitor + RGFW_monitor *monitor = RGFW_getPrimaryMonitor(); + if (!monitor) + { + TRACELOG(LOG_WARNING, "RGFW: Failed to get primary monitor"); + return -1; + } + + // Default display resolution to that of the current mode + CORE.Window.display.width = monitor->mode.w; + CORE.Window.display.height = monitor->mode.h; + + // Check if user requested some screen size + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) + { + // Set some default screen size in case user decides to exit fullscreen mode + CORE.Window.previousScreen.width = 800; + CORE.Window.previousScreen.height = 450; + CORE.Window.previousPosition.x = (CORE.Window.display.width - CORE.Window.previousScreen.width)/2; + CORE.Window.previousPosition.y = (CORE.Window.display.height - CORE.Window.previousScreen.height)/2; + + // Set screen width/height to the display width/height + if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; + if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; + } + else + { + CORE.Window.previousScreen = CORE.Window.screen; + CORE.Window.screen = CORE.Window.display; + } + } + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + FLAG_SET(flags, RGFW_windowedFullscreen); + } + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + #if !defined(__APPLE__) + CORE.Window.screen.width = CORE.Window.screen.width*GetWindowScaleDPI().x; + CORE.Window.screen.height = CORE.Window.screen.height*GetWindowScaleDPI().y; + #endif + } + + // NOTE: Some OpenGL context attributes must be set before window creation + // Check selection OpenGL version + RGFW_glHints *hints = RGFW_getGlobalHints_OpenGL(); + if (rlGetVersion() == RL_OPENGL_21) + { + hints->major = 2; + hints->minor = 1; + } + else if (rlGetVersion() == RL_OPENGL_33) + { + hints->major = 3; + hints->minor = 3; + } + else if (rlGetVersion() == RL_OPENGL_43) + { + hints->major = 4; + hints->minor = 3; + } + else if (rlGetVersion() == RL_OPENGL_SOFTWARE) + { + hints->major = 1; + hints->minor = 1; + hints->renderer = RGFW_glSoftware; + } + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) hints->samples = 4; + RGFW_setGlobalHints_OpenGL(hints); + + platform.window = RGFW_createWindow((CORE.Window.title != 0)? CORE.Window.title : " ", 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, flags | RGFW_windowOpenGL); + +#ifndef PLATFORM_WEB_RGFW + i32 screenSizeWidth; + i32 screenSizeHeight; + RGFW_window_getSize(platform.window, &screenSizeWidth, &screenSizeHeight); + CORE.Window.display.width = screenSizeWidth; + CORE.Window.display.height = screenSizeHeight; +#else + CORE.Window.display.width = CORE.Window.screen.width; + CORE.Window.display.height = CORE.Window.screen.height; +#endif + if (FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) RGFW_window_swapInterval_OpenGL(platform.window, 1); + + // Check surface and context activation + if (platform.window != NULL) + { + CORE.Window.ready = true; + + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + } + else + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); + return -1; + } + + // NOTE: RGFW's exit key is distinct from raylib's exit key and + // must be set to NULL to not interfere + RGFW_window_setExitKey(platform.window, RGFW_keyNULL); + RGFW_window_makeCurrentWindow_OpenGL(platform.window); + + //---------------------------------------------------------------------------- + + // If everything work as expected, continue + CORE.Window.position.x = platform.window->x; + CORE.Window.position.y = platform.window->y; + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + + // adjust scale if using highdpi + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { + Vector2 scaleDpi = GetWindowScaleDPI(); + + #if defined(__APPLE__) + RGFW_monitor *currentMonitor = RGFW_window_getMonitor(platform.window); + CORE.Window.screenScale = MatrixScale(currentMonitor->pixelRatio, currentMonitor->pixelRatio, 1.0f); + + CORE.Window.render.width = CORE.Window.screen.width*currentMonitor->pixelRatio; + CORE.Window.render.height = CORE.Window.screen.height*currentMonitor->pixelRatio; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + #else + SetMouseScale(1.0f/scaleDpi.x, 1.0f/scaleDpi.y); + CORE.Window.screenScale = MatrixScale(scaleDpi.x, scaleDpi.y, 1.0f); + CORE.Window.screen.width /= scaleDpi.x; + CORE.Window.screen.height /= scaleDpi.y; + #endif + } + + #if defined(GRAPHICS_API_OPENGL_SOFTWARE) + // apple always scales for retina + #if defined(__APPLE__) + RGFW_monitor *currentMonitor = RGFW_window_getMonitor(platform.window); + CORE.Window.screenScale = MatrixScale(currentMonitor->pixelRatio, currentMonitor->pixelRatio, 1.0f); + + CORE.Window.render.width = CORE.Window.screen.width*currentMonitor->pixelRatio; + CORE.Window.render.height = CORE.Window.screen.height*currentMonitor->pixelRatio; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + #endif + + platform.surfaceWidth = CORE.Window.currentFbo.width; + platform.surfaceHeight = CORE.Window.currentFbo.height; + + platform.surfacePixels = RL_MALLOC(platform.surfaceWidth*platform.surfaceHeight*4); + if (platform.surfacePixels == NULL) + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize software pixel buffer"); + return -1; + } + + platform.surface = RGFW_window_createSurface(platform.window, platform.surfacePixels, platform.surfaceWidth, platform.surfaceHeight, RGFW_formatBGRA8); + + if (platform.surface == NULL) + { + RL_FREE(platform.surfacePixels); + + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize software surface"); + return -1; + } + #endif + + // Set callbacks + RGFW_setEventCallback(RGFW_mouseEnter, RGFW_cb_mousenotifyfunc); + RGFW_setEventCallback(RGFW_mouseLeave, RGFW_cb_mousenotifyfunc); + // RGFW_setEventCallback(RGFW_windowClose, RGFW_cb_windowclosefunc); // do not close here. let raylib handle it + RGFW_setEventCallback(RGFW_dataDrop, RGFW_cb_dropfunc); + RGFW_setEventCallback(RGFW_windowResized, RGFW_cb_windowresizefunc); + RGFW_setEventCallback(RGFW_windowMaximized, RGFW_cb_windowmaximizefunc); + RGFW_setEventCallback(RGFW_windowMinimized, RGFW_cb_windowminimizefunc); + RGFW_setEventCallback(RGFW_windowRestored, RGFW_cb_windowrestorefunc); + RGFW_setEventCallback(RGFW_windowMoved, RGFW_cb_windowmovefunc); + RGFW_setEventCallback(RGFW_keyChar, RGFW_cb_keycharfunc); + RGFW_setEventCallback(RGFW_mouseScroll, RGFW_cb_scrollfunc); + RGFW_setEventCallback(RGFW_mouseButtonPressed, RGFW_cb_mousebuttonfunc); + RGFW_setEventCallback(RGFW_mouseButtonReleased, RGFW_cb_mousebuttonfunc); + RGFW_setEventCallback(RGFW_mouseRawMotion, RGFW_cb_mouserawmotionfunc); + RGFW_setEventCallback(RGFW_mouseMotion, RGFW_cb_mousemotionfunc); + RGFW_setEventCallback(RGFW_keyPressed, (RGFW_genericFunc)RGFW_cb_keyfunc); + RGFW_setEventCallback(RGFW_keyReleased, (RGFW_genericFunc)RGFW_cb_keyfunc); + + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully %s", + FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)? "(HighDPI)" : ""); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + + // Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions + //---------------------------------------------------------------------------- + rlLoadExtensions((void *)RGFW_getProcAddress_OpenGL); + //---------------------------------------------------------------------------- + + // Initialize timing system + //---------------------------------------------------------------------------- + InitTimer(); + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + #if defined(__APPLE__) + // mac defaults to the users home folder for some reason + // this is done to help them read relative paths to the binary + ChangeDirectory(GetApplicationDirectory()); + #endif + + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- + +#if defined(RGFW_WAYLAND) + if (rlGetVersion() == RL_OPENGL_SOFTWARE) + { + if (RGFW_usingWayland()) TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - Wayland, Software): Initialized successfully"); + else TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - X11, Software (fallback)): Initialized successfully"); + } + else + { + if (RGFW_usingWayland()) TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - Wayland): Initialized successfully"); + else TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - X11 (fallback)): Initialized successfully"); + } +#elif defined(RGFW_X11) + #if defined(__APPLE__) + if (rlGetVersion() == RL_OPENGL_SOFTWARE) + { + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - X11, Software, (MacOS)): Initialized successfully"); + } + else + { + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - X11, (MacOS)): Initialized successfully"); + } + #else + if (rlGetVersion() == RL_OPENGL_SOFTWARE) + { + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - X11, Software): Initialized successfully"); + } + else + { + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - X11): Initialized successfully"); + } + #endif +#elif defined (RGFW_WINDOWS) + if (rlGetVersion() == RL_OPENGL_SOFTWARE) + { + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - Win32, Software): Initialized successfully"); + } + else + { + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - Win32): Initialized successfully"); + } +#elif defined(RGFW_WASM) + if (rlGetVersion() == RL_OPENGL_SOFTWARE) + { + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - WASMs, Software): Initialized successfully"); + } + else + { + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - WASMs): Initialized successfully"); + } +#elif defined(RGFW_MACOS) + if (rlGetVersion() == RL_OPENGL_SOFTWARE) + { + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - MacOS, Software): Initialized successfully"); + } + else + { + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (RGFW - MacOS): Initialized successfully"); + } +#endif + + mg_gamepads_init(&platform.minigamepad); + + return 0; +} + +// Close platform +void ClosePlatform(void) +{ + mg_gamepads_free(&platform.minigamepad); + RGFW_window_close(platform.window); + + #if defined(GRAPHICS_API_OPENGL_SOFTWARE) + if (platform.surfacePixels != NULL) + { + RL_FREE(platform.surfacePixels); + } + + if (platform.surface != NULL) + { + RGFW_surface_free(platform.surface); + } + #endif +} + +// Keycode mapping +static KeyboardKey ConvertScancodeToKey(u32 keycode) +{ + if (keycode > sizeof(RGFW_keyConvertTable)/sizeof(unsigned short)) return KEY_NULL; + + return (KeyboardKey)RGFW_keyConvertTable[keycode]; +} + +// assign mouse to touches +// 0-TOUCH_ACTION_UP, 1-TOUCH_ACTION_DOWN, 2-TOUCH_ACTION_MOVE +void RemapMouseToTouch(int touchAction) +{ + #if SUPPORT_GESTURES_SYSTEM + if (touchAction < 0) return; + + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + gestureEvent.touchAction = touchAction; + + // Assign a pointer ID + gestureEvent.pointId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + if (touchAction == 2 /*|| realTouch*/) gestureEvent.position[0] = CORE.Input.Touch.position[0]; + else gestureEvent.position[0] = GetMousePosition(); + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures-system for processing + ProcessGestureEvent(gestureEvent); + #endif +} + +// Helper functions for Time +double GetTimeSeconds(void) +{ + double currentTime = 0.0; + + #if defined(_WIN32) + static LARGE_INTEGER freq = { 0 }; + static bool freqInitialized = false; + LARGE_INTEGER counter = { 0 }; + if (!freqInitialized) + { + // Lazy initialization + QueryPerformanceFrequency(&freq); + freqInitialized = true; + } + QueryPerformanceCounter(&counter); + currentTime = (double)counter.QuadPart/(double)freq.QuadPart; + #elif defined(__EMSCRIPTEN__) + currentTime = emscripten_get_now()/1000.0; + #elif defined(__APPLE__) + static mach_timebase_info_data_t tb = { 0 }; + static bool tbInitialized = false; + if (!tbInitialized) + { + mach_timebase_info(&tb); + tbInitialized = true; + } + uint64_t ticks = mach_absolute_time(); + + currentTime = (double)ticks*(double)tb.numer/(double)tb.denom/1e9; + #elif defined(__linux__) + struct timespec ts = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &ts); + currentTime = (double)ts.tv_sec + (double)ts.tv_nsec/1e9; + #else + // Fallback to cstd + currentTime = (double)clock()/(double)CLOCKS_PER_SEC; + #endif + + return currentTime; +} diff --git a/src/libraries/raylib/raylib/src/platforms/rcore_desktop_sdl.c b/src/libraries/raylib/raylib/src/platforms/rcore_desktop_sdl.c new file mode 100644 index 0000000..d2d1ef8 --- /dev/null +++ b/src/libraries/raylib/raylib/src/platforms/rcore_desktop_sdl.c @@ -0,0 +1,2316 @@ +/********************************************************************************************** +* +* rcore_desktop_sdl - Functions to manage window, graphics device and inputs +* +* PLATFORM: DESKTOP: SDL +* - Windows (Win32, Win64) +* - Linux (X11/Wayland desktop mode) +* - Others (not tested) +* +* LIMITATIONS: +* - Limitation 01 +* - Limitation 02 +* +* POSSIBLE IMPROVEMENTS: +* - Improvement 01 +* - Improvement 02 +* +* CONFIGURATION: +* #define RCORE_PLATFORM_CUSTOM_FLAG +* Custom flag for rcore on target platform -not used- +* +* DEPENDENCIES: +* - SDL 2 or SDL 3 (main library): Windowing and inputs management +* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifdef USING_SDL3_PACKAGE + #define USING_SDL3_PROJECT +#endif +#ifndef SDL_ENABLE_OLD_NAMES + #define SDL_ENABLE_OLD_NAMES // In case on SDL3, some in-between compatibily is needed +#endif +// SDL base library (window/rendered, input, timing... functionality) +#ifdef USING_SDL3_PROJECT + #include "SDL3/SDL.h" +#elif defined(USING_SDL2_PROJECT) + #include "SDL2/SDL.h" + + // WARNING: On Linux, this header includes Xlib.h, which defines Font + // To prevent a conflict with raylib's own Font type, we temporarily + // rename it to FontX11 + #define Font FontX11 + #include "SDL2/SDL_syswm.h" // Required to get window handlers + #undef Font +#else + #include "SDL.h" +#endif + +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) + #if defined(GRAPHICS_API_OPENGL_ES2) + // It seems it does not need to be included to work + //#include "SDL_opengles2.h" + #else + // SDL OpenGL functionality (if required, instead of internal renderer) + #ifdef USING_SDL3_PROJECT + #include "SDL3/SDL_opengl.h" + #elif defined(USING_SDL2_PROJECT) + #include "SDL2/SDL_opengl.h" + #else + #include "SDL_opengl.h" + #endif + #endif +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef MAX_CLIPBOARD_BUFFER_LENGTH + #define MAX_CLIPBOARD_BUFFER_LENGTH 1024 // Size of the clipboard buffer used on GetClipboardText() +#endif + +#if ((defined(SDL_MAJOR_VERSION) && (SDL_MAJOR_VERSION == 3)) && (defined(SDL_MINOR_VERSION) && (SDL_MINOR_VERSION >= 1))) + #ifndef USING_VERSION_SDL3 + #define USING_VERSION_SDL3 + #endif +#endif + +#define SCANCODE_MAPPED_NUM 232 + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct { + SDL_Window *window; + SDL_GLContext glContext; + + SDL_GameController *gamepad[MAX_GAMEPADS]; + SDL_JoystickID gamepadId[MAX_GAMEPADS]; // Joystick instance ids, they do not start from 0 + SDL_Cursor *cursor; + +#if defined(__linux__) + // Local storage for the window handle (X11) + // NOTE: On SDL is not possible to know windowing backend at compile time, so, + // just in case, avoiding X11 specific types here + unsigned long windowHandleX11; // Underlying type for: XID, Window +#endif +} PlatformData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context + +static PlatformData platform = { 0 }; // Platform specific data + +static const KeyboardKey mapScancodeToKey[SCANCODE_MAPPED_NUM] = { + KEY_NULL, // SDL_SCANCODE_UNKNOWN + 0, + 0, + 0, + KEY_A, // SDL_SCANCODE_A + KEY_B, // SDL_SCANCODE_B + KEY_C, // SDL_SCANCODE_C + KEY_D, // SDL_SCANCODE_D + KEY_E, // SDL_SCANCODE_E + KEY_F, // SDL_SCANCODE_F + KEY_G, // SDL_SCANCODE_G + KEY_H, // SDL_SCANCODE_H + KEY_I, // SDL_SCANCODE_I + KEY_J, // SDL_SCANCODE_J + KEY_K, // SDL_SCANCODE_K + KEY_L, // SDL_SCANCODE_L + KEY_M, // SDL_SCANCODE_M + KEY_N, // SDL_SCANCODE_N + KEY_O, // SDL_SCANCODE_O + KEY_P, // SDL_SCANCODE_P + KEY_Q, // SDL_SCANCODE_Q + KEY_R, // SDL_SCANCODE_R + KEY_S, // SDL_SCANCODE_S + KEY_T, // SDL_SCANCODE_T + KEY_U, // SDL_SCANCODE_U + KEY_V, // SDL_SCANCODE_V + KEY_W, // SDL_SCANCODE_W + KEY_X, // SDL_SCANCODE_X + KEY_Y, // SDL_SCANCODE_Y + KEY_Z, // SDL_SCANCODE_Z + KEY_ONE, // SDL_SCANCODE_1 + KEY_TWO, // SDL_SCANCODE_2 + KEY_THREE, // SDL_SCANCODE_3 + KEY_FOUR, // SDL_SCANCODE_4 + KEY_FIVE, // SDL_SCANCODE_5 + KEY_SIX, // SDL_SCANCODE_6 + KEY_SEVEN, // SDL_SCANCODE_7 + KEY_EIGHT, // SDL_SCANCODE_8 + KEY_NINE, // SDL_SCANCODE_9 + KEY_ZERO, // SDL_SCANCODE_0 + KEY_ENTER, // SDL_SCANCODE_RETURN + KEY_ESCAPE, // SDL_SCANCODE_ESCAPE + KEY_BACKSPACE, // SDL_SCANCODE_BACKSPACE + KEY_TAB, // SDL_SCANCODE_TAB + KEY_SPACE, // SDL_SCANCODE_SPACE + KEY_MINUS, // SDL_SCANCODE_MINUS + KEY_EQUAL, // SDL_SCANCODE_EQUALS + KEY_LEFT_BRACKET, // SDL_SCANCODE_LEFTBRACKET + KEY_RIGHT_BRACKET, // SDL_SCANCODE_RIGHTBRACKET + KEY_BACKSLASH, // SDL_SCANCODE_BACKSLASH + 0, // SDL_SCANCODE_NONUSHASH + KEY_SEMICOLON, // SDL_SCANCODE_SEMICOLON + KEY_APOSTROPHE, // SDL_SCANCODE_APOSTROPHE + KEY_GRAVE, // SDL_SCANCODE_GRAVE + KEY_COMMA, // SDL_SCANCODE_COMMA + KEY_PERIOD, // SDL_SCANCODE_PERIOD + KEY_SLASH, // SDL_SCANCODE_SLASH + KEY_CAPS_LOCK, // SDL_SCANCODE_CAPSLOCK + KEY_F1, // SDL_SCANCODE_F1 + KEY_F2, // SDL_SCANCODE_F2 + KEY_F3, // SDL_SCANCODE_F3 + KEY_F4, // SDL_SCANCODE_F4 + KEY_F5, // SDL_SCANCODE_F5 + KEY_F6, // SDL_SCANCODE_F6 + KEY_F7, // SDL_SCANCODE_F7 + KEY_F8, // SDL_SCANCODE_F8 + KEY_F9, // SDL_SCANCODE_F9 + KEY_F10, // SDL_SCANCODE_F10 + KEY_F11, // SDL_SCANCODE_F11 + KEY_F12, // SDL_SCANCODE_F12 + KEY_PRINT_SCREEN, // SDL_SCANCODE_PRINTSCREEN + KEY_SCROLL_LOCK, // SDL_SCANCODE_SCROLLLOCK + KEY_PAUSE, // SDL_SCANCODE_PAUSE + KEY_INSERT, // SDL_SCANCODE_INSERT + KEY_HOME, // SDL_SCANCODE_HOME + KEY_PAGE_UP, // SDL_SCANCODE_PAGEUP + KEY_DELETE, // SDL_SCANCODE_DELETE + KEY_END, // SDL_SCANCODE_END + KEY_PAGE_DOWN, // SDL_SCANCODE_PAGEDOWN + KEY_RIGHT, // SDL_SCANCODE_RIGHT + KEY_LEFT, // SDL_SCANCODE_LEFT + KEY_DOWN, // SDL_SCANCODE_DOWN + KEY_UP, // SDL_SCANCODE_UP + KEY_NUM_LOCK, // SDL_SCANCODE_NUMLOCKCLEAR + KEY_KP_DIVIDE, // SDL_SCANCODE_KP_DIVIDE + KEY_KP_MULTIPLY, // SDL_SCANCODE_KP_MULTIPLY + KEY_KP_SUBTRACT, // SDL_SCANCODE_KP_MINUS + KEY_KP_ADD, // SDL_SCANCODE_KP_PLUS + KEY_KP_ENTER, // SDL_SCANCODE_KP_ENTER + KEY_KP_1, // SDL_SCANCODE_KP_1 + KEY_KP_2, // SDL_SCANCODE_KP_2 + KEY_KP_3, // SDL_SCANCODE_KP_3 + KEY_KP_4, // SDL_SCANCODE_KP_4 + KEY_KP_5, // SDL_SCANCODE_KP_5 + KEY_KP_6, // SDL_SCANCODE_KP_6 + KEY_KP_7, // SDL_SCANCODE_KP_7 + KEY_KP_8, // SDL_SCANCODE_KP_8 + KEY_KP_9, // SDL_SCANCODE_KP_9 + KEY_KP_0, // SDL_SCANCODE_KP_0 + KEY_KP_DECIMAL, // SDL_SCANCODE_KP_PERIOD + 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, + 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, + 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, + 0, 0, 0, 0, + KEY_LEFT_CONTROL, //SDL_SCANCODE_LCTRL + KEY_LEFT_SHIFT, //SDL_SCANCODE_LSHIFT + KEY_LEFT_ALT, //SDL_SCANCODE_LALT + KEY_LEFT_SUPER, //SDL_SCANCODE_LGUI + KEY_RIGHT_CONTROL, //SDL_SCANCODE_RCTRL + KEY_RIGHT_SHIFT, //SDL_SCANCODE_RSHIFT + KEY_RIGHT_ALT, //SDL_SCANCODE_RALT + KEY_RIGHT_SUPER //SDL_SCANCODE_RGUI +}; + +static const int CursorsLUT[] = { + SDL_SYSTEM_CURSOR_ARROW, // 0 MOUSE_CURSOR_DEFAULT + SDL_SYSTEM_CURSOR_ARROW, // 1 MOUSE_CURSOR_ARROW + SDL_SYSTEM_CURSOR_IBEAM, // 2 MOUSE_CURSOR_IBEAM + SDL_SYSTEM_CURSOR_CROSSHAIR, // 3 MOUSE_CURSOR_CROSSHAIR + SDL_SYSTEM_CURSOR_HAND, // 4 MOUSE_CURSOR_POINTING_HAND + SDL_SYSTEM_CURSOR_SIZEWE, // 5 MOUSE_CURSOR_RESIZE_EW + SDL_SYSTEM_CURSOR_SIZENS, // 6 MOUSE_CURSOR_RESIZE_NS + SDL_SYSTEM_CURSOR_SIZENWSE, // 7 MOUSE_CURSOR_RESIZE_NWSE + SDL_SYSTEM_CURSOR_SIZENESW, // 8 MOUSE_CURSOR_RESIZE_NESW + SDL_SYSTEM_CURSOR_SIZEALL, // 9 MOUSE_CURSOR_RESIZE_ALL + SDL_SYSTEM_CURSOR_NO // 10 MOUSE_CURSOR_NOT_ALLOWED + //SDL_SYSTEM_CURSOR_WAIT, // No equivalent implemented on MouseCursor enum on raylib.h + //SDL_SYSTEM_CURSOR_WAITARROW, // No equivalent implemented on MouseCursor enum on raylib.h +}; + +// SDL3 migration layer made to avoid 'ifdefs' inside functions +#if defined(USING_VERSION_SDL3) + +// SDL3 migration: +// SDL_WINDOW_FULLSCREEN_DESKTOP has been removed, +// SDL_GetWindowFullscreenMode() can be called +// to see whether an exclusive fullscreen mode will be used +// or the borderless fullscreen desktop mode +#define SDL_WINDOW_FULLSCREEN_DESKTOP SDL_WINDOW_FULLSCREEN + +#define SDL_IGNORE false +#define SDL_DISABLE false +#define SDL_ENABLE true + +// SDL3 Migration: SDL_INIT_TIMER - no longer needed before calling SDL_AddTimer() +#define SDL_INIT_TIMER 0x0 // It's a flag, so no problem in setting it to zero to be used in a bitor (|) + +// SDL3 Migration: The SDL_WINDOW_SHOWN flag has been removed. Windows are shown by default and can be created hidden by using the SDL_WINDOW_HIDDEN flag +#define SDL_WINDOW_SHOWN 0x0 // It's a flag, so no problem in setting it to zero to be used in a bitor (|) + +// SDL3 Migration: Renamed +// IMPORTANT: Might need to call SDL_CleanupEvent somewhere see :https://github.com/libsdl-org/SDL/issues/3540#issuecomment-1793449852 +#define SDL_DROPFILE SDL_EVENT_DROP_FILE + +// SDL2 implementation for SDL3 function +const char *SDL_GameControllerNameForIndex(int joystickIndex) +{ + // NOTE: SDL3 uses the IDs itself (SDL_JoystickID) instead of SDL2 joystick_index + const char *name = NULL; + int numJoysticks = 0; + SDL_JoystickID *joysticks = SDL_GetJoysticks(&numJoysticks); + + if (joysticks) + { + if (joystickIndex < numJoysticks) + { + SDL_JoystickID instance_id = joysticks[joystickIndex]; + name = SDL_GetGamepadNameForID(instance_id); + } + + SDL_free(joysticks); + } + + return name; +} + +int SDL_GetNumVideoDisplays(void) +{ + int monitorCount = 0; + SDL_DisplayID *displays = SDL_GetDisplays(&monitorCount); + + // Safe because If 'mem' is NULL, SDL_free does nothing + SDL_free(displays); + + return monitorCount; +} + +// SLD3 Migration: To emulate SDL2 this function should return 'SDL_DISABLE' or 'SDL_ENABLE' +// representing the *processing state* of the event before this function makes any changes to it +Uint8 SDL_EventState(Uint32 type, int state) +{ + Uint8 stateBefore = SDL_EventEnabled(type); + + switch (state) + { + case SDL_DISABLE: SDL_SetEventEnabled(type, false); break; + case SDL_ENABLE: SDL_SetEventEnabled(type, true); break; + default: TRACELOG(LOG_WARNING, "SDL: Event state of unknow type"); + } + + return stateBefore; +} + +void SDL_GetCurrentDisplayMode_Adapter(SDL_DisplayID displayID, SDL_DisplayMode *mode) +{ + const SDL_DisplayMode *currentMode = SDL_GetCurrentDisplayMode(displayID); + + if (currentMode == NULL) TRACELOG(LOG_WARNING, "SDL: No possible to get current display mode"); + else *mode = *currentMode; +} + +// SDL3 Migration: Renamed +#define SDL_GetCurrentDisplayMode SDL_GetCurrentDisplayMode_Adapter + +SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) +{ + return SDL_CreateSurface(width, height, SDL_GetPixelFormatForMasks(depth, Rmask, Gmask, Bmask, Amask)); +} + +// SDL3 Migration: +// SDL_GetDisplayDPI() not reliable across platforms, approximately replaced by multiplying +// SDL_GetWindowDisplayScale() times 160 on iPhone and Android, and 96 on other platforms +// returns 0 on success or a negative error code on failure +int SDL_GetDisplayDPI(int displayIndex, float *ddpi, float *hdpi, float *vdpi) +{ + float dpi = SDL_GetWindowDisplayScale(platform.window)*96.0; + + if (ddpi != NULL) *ddpi = dpi; + if (hdpi != NULL) *hdpi = dpi; + if (vdpi != NULL) *vdpi = dpi; + + return 0; +} + +SDL_Surface *SDL_CreateRGBSurfaceWithFormat(Uint32 flags, int width, int height, int depth, Uint32 format) +{ + return SDL_CreateSurface(width, height, format); +} + +SDL_Surface *SDL_CreateRGBSurfaceFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) +{ + return SDL_CreateSurfaceFrom(width, height, SDL_GetPixelFormatForMasks(depth, Rmask, Gmask, Bmask, Amask), pixels, pitch); +} + +SDL_Surface *SDL_CreateRGBSurfaceWithFormatFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 format) +{ + return SDL_CreateSurfaceFrom(width, height, format, pixels, pitch); +} + +int SDL_NumJoysticks(void) +{ + int numJoysticks; + SDL_JoystickID *joysticks = SDL_GetJoysticks(&numJoysticks); + SDL_free(joysticks); + return numJoysticks; +} + +// SDL_SetRelativeMouseMode +// returns 0 on success or a negative error code on failure +// If relative mode is not supported, this returns -1 +int SDL_SetRelativeMouseMode_Adapter(SDL_bool enabled) +{ + // SDL_SetWindowRelativeMouseMode(SDL_Window *window, bool enabled) + // \returns true on success or false on failure; call SDL_GetError() for more + if (SDL_SetWindowRelativeMouseMode(platform.window, enabled)) + { + return 0; // success + } + else + { + return -1; // failure + } +} + +#define SDL_SetRelativeMouseMode SDL_SetRelativeMouseMode_Adapter + +bool SDL_GetRelativeMouseMode_Adapter(void) +{ + return SDL_GetWindowRelativeMouseMode(platform.window); +} + +#define SDL_GetRelativeMouseMode SDL_GetRelativeMouseMode_Adapter + +int SDL_GetNumTouchFingers(SDL_TouchID touchID) +{ + // SDL_Finger **SDL_GetTouchFingers(SDL_TouchID touchID, int *count) + int count = 0; + SDL_Finger **fingers = SDL_GetTouchFingers(touchID, &count); + SDL_free(fingers); + return count; +} + +#else // SDL2 fallback + +// Since SDL2 doesn't have this function, leaving a stub +// SDL_GetClipboardData function is available since SDL 3.1.3. (e.g. SDL3) +void *SDL_GetClipboardData(const char *mime_type, size_t *size) +{ + TRACELOG(LOG_WARNING, "SDL: Getting clipboard data that is not text not available in SDL2"); + + // TODO: Implement getting clipboard data + + return NULL; +} +#endif // USING_VERSION_SDL3 + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +void ClosePlatform(void); // Close platform + +static KeyboardKey ConvertScancodeToKey(SDL_Scancode sdlScancode); // Help convert SDL scancodes to raylib key +static int GetCodepointNextSDL(const char *text, int *codepointSize); // Get next codepoint in a byte sequence and bytes processed +static void UpdateTouchPointsSDL(SDL_TouchFingerEvent event); // Update CORE input touch point info from SDL touch data + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +bool WindowShouldClose(void) +{ + if (CORE.Window.ready) return CORE.Window.shouldClose; + else return true; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + const int monitor = SDL_GetWindowDisplayIndex(platform.window); + +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid +#else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) +#endif + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + { + SDL_SetWindowFullscreen(platform.window, 0); + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + else + { + SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN); + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + const int monitor = SDL_GetWindowDisplayIndex(platform.window); + +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid +#else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) +#endif + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + SDL_SetWindowFullscreen(platform.window, 0); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + else + { + SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN_DESKTOP); + FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + SDL_MaximizeWindow(platform.window); + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + SDL_MinimizeWindow(platform.window); + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + SDL_RestoreWindow(platform.window); + // CORE.Window.flags will be removed on PollInputEvents() +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); + + FLAG_SET(CORE.Window.flags, flags); + + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + SDL_GL_SetSwapInterval(1); + } + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + const int monitor = SDL_GetWindowDisplayIndex(platform.window); + + #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid + #else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) + #endif + { + SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN); + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) + { + SDL_SetWindowResizable(platform.window, SDL_TRUE); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + SDL_SetWindowBordered(platform.window, SDL_FALSE); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + SDL_HideWindow(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + SDL_MinimizeWindow(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) + { + SDL_MaximizeWindow(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) + { + TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_UNFOCUSED is not supported on PLATFORM_DESKTOP_SDL"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) + { + SDL_SetWindowAlwaysOnTop(platform.window, SDL_FALSE); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_TRANSPARENT is not supported on PLATFORM_DESKTOP_SDL"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + // NOTE: Such a function does not seem to exist + TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_HIGHDPI is not supported on PLATFORM_DESKTOP_SDL"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + //SDL_SetWindowGrab(platform.window, SDL_FALSE); + TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_MOUSE_PASSTHROUGH is not supported on PLATFORM_DESKTOP_SDL"); + } + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + const int monitor = SDL_GetWindowDisplayIndex(platform.window); + + #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid + #else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) + #endif + { + SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); + } + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); // Enable multisampling buffers + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); // Enable multisampling + } + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_INTERLACED_HINT is not supported on PLATFORM_DESKTOP_SDL"); + } +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + FLAG_CLEAR(CORE.Window.flags, flags); + + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + SDL_GL_SetSwapInterval(0); + } + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + SDL_SetWindowFullscreen(platform.window, 0); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) + { + SDL_SetWindowResizable(platform.window, SDL_FALSE); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + SDL_SetWindowBordered(platform.window, SDL_TRUE); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + SDL_ShowWindow(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + SDL_RestoreWindow(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) + { + SDL_RestoreWindow(platform.window); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) + { + //SDL_RaiseWindow(platform.window); + TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_WINDOW_UNFOCUSED is not supported on PLATFORM_DESKTOP_SDL"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) + { + SDL_SetWindowAlwaysOnTop(platform.window, SDL_FALSE); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_WINDOW_TRANSPARENT is not supported on PLATFORM_DESKTOP_SDL"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + // NOTE: There also doesn't seem to be a feature to disable high DPI once enabled + TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_WINDOW_HIGHDPI is not supported on PLATFORM_DESKTOP_SDL"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + //SDL_SetWindowGrab(platform.window, SDL_TRUE); + TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_WINDOW_MOUSE_PASSTHROUGH is not supported on PLATFORM_DESKTOP_SDL"); + } + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + SDL_SetWindowFullscreen(platform.window, 0); + } + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); // Disable multisampling buffers + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); // Disable multisampling + } + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_INTERLACED_HINT is not supported on PLATFORM_DESKTOP_SDL"); + } +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + SDL_Surface *iconSurface = NULL; + + unsigned int rmask = 0, gmask = 0, bmask = 0, amask = 0; + int depth = 0; // Depth in bits + int pitch = 0; // Pixel spacing (pitch) in bytes + + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + rmask = 0xFF, gmask = 0; + bmask = 0, amask = 0; + depth = 8, pitch = image.width; + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + rmask = 0xFF, gmask = 0xFF00; + bmask = 0, amask = 0; + depth = 16, pitch = image.width*2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + rmask = 0xF800, gmask = 0x07E0; + bmask = 0x001F, amask = 0; + depth = 16, pitch = image.width*2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + // WARNING: SDL2 could be using BGR but SDL3 RGB + rmask = 0xFF0000, gmask = 0x00FF00; + bmask = 0x0000FF, amask = 0; + depth = 24, pitch = image.width*3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + rmask = 0xF800, gmask = 0x07C0; + bmask = 0x003E, amask = 0x0001; + depth = 16, pitch = image.width*2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + rmask = 0xF000, gmask = 0x0F00; + bmask = 0x00F0, amask = 0x000F; + depth = 16, pitch = image.width*2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + rmask = 0xFF000000, gmask = 0x00FF0000; + bmask = 0x0000FF00, amask = 0x000000FF; + depth = 32, pitch = image.width*4; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + rmask = 0xFFFFFFFF, gmask = 0; + bmask = 0, amask = 0; + depth = 32, pitch = image.width*4; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + rmask = 0xFFFFFFFF, gmask = 0xFFFFFFFF; + bmask = 0xFFFFFFFF, amask = 0; + depth = 96, pitch = image.width*12; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + rmask = 0xFFFFFFFF, gmask = 0xFFFFFFFF; + bmask = 0xFFFFFFFF, amask = 0xFFFFFFFF; + depth = 128, pitch = image.width*16; + } break; + case PIXELFORMAT_UNCOMPRESSED_R16: + { + rmask = 0xFFFF, gmask = 0; + bmask = 0, amask = 0; + depth = 16, pitch = image.width*2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16: + { + rmask = 0xFFFF, gmask = 0xFFFF; + bmask = 0xFFFF, amask = 0; + depth = 48, pitch = image.width*6; + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + { + rmask = 0xFFFF, gmask = 0xFFFF; + bmask = 0xFFFF, amask = 0xFFFF; + depth = 64, pitch = image.width*8; + } break; + default: return; // Compressed formats are not supported + } + + iconSurface = SDL_CreateRGBSurfaceFrom( image.data, image.width, image.height, depth, pitch, rmask, gmask, bmask, amask ); + + if (iconSurface) + { + SDL_SetWindowIcon(platform.window, iconSurface); + SDL_FreeSurface(iconSurface); + } +} + +// Set icon for window +void SetWindowIcons(Image *images, int count) +{ + TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform"); +} + +// Set title for window +void SetWindowTitle(const char *title) +{ + SDL_SetWindowTitle(platform.window, title); + + CORE.Window.title = title; +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + SDL_SetWindowPosition(platform.window, x, y); + + CORE.Window.position.x = x; + CORE.Window.position.y = y; +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid +#else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) +#endif + { + // NOTE 1: SDL started supporting moving exclusive fullscreen windows between displays on SDL3, + // see commit https://github.com/libsdl-org/SDL/commit/3f5ef7dd422057edbcf3e736107e34be4b75d9ba + // NOTE 2: A workaround for SDL2 is leaving fullscreen, moving the window, then entering full screen again + const bool wasFullscreen = (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE))? true : false; + + const int screenWidth = CORE.Window.screen.width; + const int screenHeight = CORE.Window.screen.height; + SDL_Rect usableBounds; + + #if defined(USING_VERSION_SDL3) // Different style for success checking + if (SDL_GetDisplayUsableBounds(monitor, &usableBounds)) + #else + if (SDL_GetDisplayUsableBounds(monitor, &usableBounds) == 0) + #endif + { + if (wasFullscreen == 1) ToggleFullscreen(); // Leave fullscreen + + // If the screen size is larger than the monitor usable area, anchor it on the top left corner, otherwise, center it + if ((screenWidth >= usableBounds.w) || (screenHeight >= usableBounds.h)) + { + // NOTE 1: There's a known issue where if the window larger than the target display bounds, + // when moving the windows to that display, the window could be clipped back + // ending up positioned partly outside the target display + // NOTE 2: The workaround for that is, previously to moving the window, + // setting the window size to the target display size, so they match + // NOTE 3: It wasn't done here because it can not be assumed that changing + // the window size automatically is acceptable behavior by the user + SDL_SetWindowPosition(platform.window, usableBounds.x, usableBounds.y); + CORE.Window.position.x = usableBounds.x; + CORE.Window.position.y = usableBounds.y; + } + else + { + const int x = usableBounds.x + (usableBounds.w/2) - (screenWidth/2); + const int y = usableBounds.y + (usableBounds.h/2) - (screenHeight/2); + SDL_SetWindowPosition(platform.window, x, y); + CORE.Window.position.x = x; + CORE.Window.position.y = y; + } + + if (wasFullscreen == 1) ToggleFullscreen(); // Re-enter fullscreen + } + else TRACELOG(LOG_WARNING, "SDL: Failed to get selected display usable bounds"); + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + SDL_SetWindowMinimumSize(platform.window, width, height); + + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + SDL_SetWindowMaximumSize(platform.window, width, height); + + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + SDL_SetWindowSize(platform.window, width, height); + + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + if (opacity >= 1.0f) opacity = 1.0f; + else if (opacity <= 0.0f) opacity = 0.0f; + + SDL_SetWindowOpacity(platform.window, opacity); +} + +// Set window focused +void SetWindowFocused(void) +{ + SDL_RaiseWindow(platform.window); +} + +// Get native window handle +// NOTE: Handle type depends on OS and windowing system +void *GetWindowHandle(void) +{ + void *handle = NULL; + +#if defined(USING_SDL3_PROJECT) + // REF: https://github.com/libsdl-org/SDL/blob/main/include/SDL3/SDL_video.h#L1590 + SDL_PropertiesID props = SDL_GetWindowProperties(platform.window); + #if defined(_WIN32) + handle = (void *)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); // Type: HWND + #elif defined(__linux__) + unsigned long windowId = (unsigned long)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); // Type: unsigned long (XID, Window) + if (windowId != 0) + { + // X11 window ID + platform.windowHandleX11 = windowId; + handle = &platform.windowHandleX11; + } + else + { + // Wayland, get display surface pointer + // NOTE: Alternative SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER + handle = (void *)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL); // Type: struct wl_surface* + } + #elif defined(__APPLE__) + handle = (void *)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); // Type: NSWindow* + #endif +#elif defined(USING_SDL2_PROJECT) + SDL_SysWMinfo wmInfo = { 0 }; + SDL_VERSION(&wmInfo.version); + if (SDL_GetWindowWMInfo(platform.window, &wmInfo)) + { + #if defined(_WIN32) + handle = (void *)wmInfo.info.win.window; // Type: HWND + #elif defined(__linux__) + if (wmInfo.subsystem == SDL_SYSWM_X11) + { + // X11, get window ID (unsigned long) + platform.windowHandleX11 = (unsigned long)wmInfo.info.x11.window; + handle = &platform.windowHandleX11; + } + else if (wmInfo.subsystem == SDL_SYSWM_WAYLAND) + { + // Wayland, get display surface pointer + // NOTE: Alternative: wmInfo.info.wl.display + handle = (void *)wmInfo.info.wl.surface; // Type: struct wl_surface* + } + #elif defined(__APPLE__) + handle = (void *)wmInfo.info.cocoa.window; // Type: NSWindow* + #endif + } +#endif + + return handle; +} + +// Get number of monitors +int GetMonitorCount(void) +{ + return SDL_GetNumVideoDisplays(); +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + // Be aware that this returns an ID in SDL3 and a Index in SDL2 + return SDL_GetWindowDisplayIndex(platform.window); +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid +#else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) +#endif + { + SDL_Rect displayBounds; + + #if defined(USING_VERSION_SDL3) + if (SDL_GetDisplayUsableBounds(monitor, &displayBounds)) + #else + if (SDL_GetDisplayUsableBounds(monitor, &displayBounds) == 0) + #endif + { + return (Vector2){ (float)displayBounds.x, (float)displayBounds.y }; + } + else TRACELOG(LOG_WARNING, "SDL: Failed to get selected display usable bounds"); + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); + return (Vector2){ 0.0f, 0.0f }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + int width = 0; + +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid +#else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) +#endif + { + SDL_DisplayMode mode; + SDL_GetCurrentDisplayMode(monitor, &mode); + width = mode.w; + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); + + return width; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + int height = 0; + +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid +#else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) +#endif + { + SDL_DisplayMode mode; + SDL_GetCurrentDisplayMode(monitor, &mode); + height = mode.h; + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); + + return height; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + int width = 0; + +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid +#else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) +#endif + { + float ddpi = 0.0f; + SDL_GetDisplayDPI(monitor, &ddpi, NULL, NULL); + SDL_DisplayMode mode; + SDL_GetCurrentDisplayMode(monitor, &mode); + // Calculate size on inches, then convert to millimeter + if (ddpi > 0.0f) width = (int)((mode.w/ddpi)*25.4f); + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); + + return width; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + int height = 0; + +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid +#else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) +#endif + { + float ddpi = 0.0f; + SDL_GetDisplayDPI(monitor, &ddpi, NULL, NULL); + SDL_DisplayMode mode; + SDL_GetCurrentDisplayMode(monitor, &mode); + // Calculate size on inches, then convert to millimeter + if (ddpi > 0.0f) height = (int)((mode.h/ddpi)*25.4f); + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); + + return height; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + int refresh = 0; + +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid +#else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) +#endif + { + SDL_DisplayMode mode; + SDL_GetCurrentDisplayMode(monitor, &mode); + refresh = mode.refresh_rate; + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); + + return refresh; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid +#else + if ((monitor >= 0) && (monitor < SDL_GetNumVideoDisplays())) +#endif + { + return SDL_GetDisplayName(monitor); + } + else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); + + return ""; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + int x = 0; + int y = 0; + + SDL_GetWindowPosition(platform.window, &x, &y); + + return (Vector2){ (float)x, (float)y }; +} + +// Get window scale DPI factor for current monitor +Vector2 GetWindowScaleDPI(void) +{ + Vector2 scale = { 1.0f, 1.0f }; + +#if defined(USING_VERSION_SDL3) + // NOTE: SDL_GetWindowDisplayScale added on SDL3 + // REF: https://wiki.libsdl.org/SDL3/SDL_GetWindowDisplayScale + scale.x = SDL_GetWindowDisplayScale(platform.window); + scale.y = scale.x; +#else + // NOTE: SDL_GetWindowDisplayScale not available on SDL2 + // TODO: Implement the window scale factor calculation manually + TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform"); +#endif + + return scale; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + SDL_SetClipboardText(text); +} + +// Get clipboard text content +const char *GetClipboardText(void) +{ + static char buffer[MAX_CLIPBOARD_BUFFER_LENGTH] = { 0 }; + + char *clipboard = SDL_GetClipboardText(); + + int clipboardSize = snprintf(buffer, MAX_CLIPBOARD_BUFFER_LENGTH, "%s", clipboard); + if (clipboardSize >= MAX_CLIPBOARD_BUFFER_LENGTH) + { + char *truncate = buffer + MAX_CLIPBOARD_BUFFER_LENGTH - 4; + sprintf(truncate, "..."); + } + + SDL_free(clipboard); + + return buffer; +} + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + +#if SUPPORT_CLIPBOARD_IMAGE +#if !SUPPORT_MODULE_RTEXTURES + TRACELOG(LOG_WARNING, "Enabling SUPPORT_CLIPBOARD_IMAGE requires SUPPORT_MODULE_RTEXTURES to work properly"); + return image; +#endif + +// It's nice to have support Bitmap on Linux as well, but not as necessary as Windows +#if !SUPPORT_FILEFORMAT_BMP && defined(_WIN32) + TRACELOG(LOG_WARNING, "WARNING: Enabling SUPPORT_CLIPBOARD_IMAGE requires SUPPORT_FILEFORMAT_BMP, specially on Windows"); + return image; +#endif + +// From what I've tested applications on Wayland saves images on clipboard as PNG +#if (!SUPPORT_FILEFORMAT_PNG || !SUPPORT_FILEFORMAT_JPG) && !defined(_WIN32) + TRACELOG(LOG_WARNING, "WARNING: Getting image from the clipboard might not work without SUPPORT_FILEFORMAT_PNG or SUPPORT_FILEFORMAT_JPG"); +#endif + // Let's hope compiler put these arrays in static memory + const char *imageFormats[] = { + "image/bmp", + "image/png", + "image/jpg", + "image/tiff", + }; + const char *imageExtensions[] = { + ".bmp", + ".png", + ".jpg", + ".tiff", + }; + + size_t dataSize = 0; + void *fileData = NULL; + + for (int i = 0; i < SDL_arraysize(imageFormats); i++) + { + fileData = SDL_GetClipboardData(imageFormats[i], &dataSize); + + if (fileData) + { + image = LoadImageFromMemory(imageExtensions[i], fileData, (int)dataSize); + + SDL_free(fileData); + + if (IsImageValid(image)) + { + TRACELOG(LOG_INFO, "Clipboard: Got image from clipboard successfully: %s", imageExtensions[i]); + return image; + } + } + } + + if (!IsImageValid(image)) TRACELOG(LOG_WARNING, "Clipboard: Couldn't get clipboard data. ERROR: %s", SDL_GetError()); +#endif // SUPPORT_CLIPBOARD_IMAGE + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ +#if defined(USING_VERSION_SDL3) + SDL_ShowCursor(); +#else + SDL_ShowCursor(SDL_ENABLE); +#endif + CORE.Input.Mouse.cursorHidden = false; +} + +// Hide mouse cursor +void HideCursor(void) +{ +#if defined(USING_VERSION_SDL3) + SDL_HideCursor(); +#else + SDL_ShowCursor(SDL_DISABLE); +#endif + CORE.Input.Mouse.cursorHidden = true; +} + +// Enable cursor (unlock cursor) +void EnableCursor(void) +{ + SDL_SetRelativeMouseMode(SDL_FALSE); + + ShowCursor(); + CORE.Input.Mouse.cursorLocked = false; +} + +// Disable cursor (lock cursor) +void DisableCursor(void) +{ + SDL_SetRelativeMouseMode(SDL_TRUE); + + HideCursor(); + CORE.Input.Mouse.cursorLocked = true; +} + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + // NOTE: Using a preprocessor condition here because rlCopyFramebuffer() is only declared for software rendering + SDL_Surface *surface = SDL_GetWindowSurface(platform.window); + rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, surface->pixels); + SDL_UpdateWindowSurface(platform.window); +#else + SDL_GL_SwapWindow(platform.window); +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds +double GetTime(void) +{ + double time = ((double)SDL_GetPerformanceCounter()/(double)SDL_GetPerformanceFrequency()) - CORE.Time.base; + + return time; +} + +// Open URL with default system browser (if available) +// WARNING: This function is only safe to use if you control the URL given, +// a user could craft a malicious string to perform and undesired action +// NOTE: Some safety checks have been added to mitigate security issues +void OpenURL(const char *url) +{ + // Security check to (partially) avoid malicious code + if ((strchr(url, '\'') != NULL) || (strchr(url, '\"') != NULL)) + { + // Filter characters: ' and " + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'\"] characters"); + } + else if ((strncmp(url, "http://", 7) != 0) && (strncmp(url, "https://", 8) != 0)) + { + // Only allow URL starting with "http://" or "https://" protocols + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL must start with 'http://' or 'https://' protocols"); + } + else SDL_OpenURL(url); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + const int mappingsLength = strlen(mappings); + char *buffer = (char *)RL_CALLOC(mappingsLength + 1, sizeof(char)); + memcpy(buffer, mappings, mappingsLength); + char *p = strtok(buffer, "\n"); + bool succeed = true; + + while (p != NULL) + { + if (SDL_GameControllerAddMapping(p) == -1) + { + succeed = false; + } + p = strtok(NULL, "\n"); + } + + RL_FREE(buffer); + + // To make return value is consistent with the GLFW version. + return (succeed)? 1 : 0; +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (duration > 0.0f)) + { + if (leftMotor < 0.0f) leftMotor = 0.0f; + if (leftMotor > 1.0f) leftMotor = 1.0f; + if (rightMotor < 0.0f) rightMotor = 0.0f; + if (rightMotor > 1.0f) rightMotor = 1.0f; + if (duration > MAX_GAMEPAD_VIBRATION_TIME) duration = MAX_GAMEPAD_VIBRATION_TIME; + + SDL_GameControllerRumble(platform.gamepad[gamepad], (Uint16)(leftMotor*65535.0f), (Uint16)(rightMotor*65535.0f), (Uint32)(duration*1000.0f)); + } +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + SDL_WarpMouseInWindow(platform.window, x, y); + + CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; +} + +// Set mouse cursor +void SetMouseCursor(int cursor) +{ + platform.cursor = SDL_CreateSystemCursor(CursorsLUT[cursor]); + SDL_SetCursor(platform.cursor); + + CORE.Input.Mouse.cursor = cursor; +} + +// Get physical key name +const char *GetKeyName(int key) +{ + return SDL_GetKeyName(key); +} + +// Register all input events +void PollInputEvents(void) +{ +#if SUPPORT_GESTURES_SYSTEM + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + + // Reset mouse wheel + CORE.Input.Mouse.currentWheelMove.x = 0; + CORE.Input.Mouse.currentWheelMove.y = 0; + + // Register previous mouse position + if (CORE.Input.Mouse.cursorLocked) CORE.Input.Mouse.currentPosition = (Vector2){ 0.0f, 0.0f }; + else CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + + // Reset last gamepad button/axis registered state + for (int i = 0; (i < SDL_NumJoysticks()) && (i < MAX_GAMEPADS); i++) + { + // Check if gamepad is available + if (CORE.Input.Gamepad.ready[i]) + { + // Register previous gamepad button states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) + { + CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; + } + } + } + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Map touch position to mouse position for convenience + if (CORE.Input.Touch.pointCount == 0) CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + + int touchAction = -1; // 0-TOUCH_ACTION_UP, 1-TOUCH_ACTION_DOWN, 2-TOUCH_ACTION_MOVE + bool realTouch = false; // Flag to differentiate real touch gestures from mouse ones + + // Register previous keys states + // NOTE: Android supports up to 260 keys + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) + { + CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // Register previous mouse states + for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + + // Poll input events for current platform + //----------------------------------------------------------------------------- + // WARNING: Indexes into this array are obtained by using SDL_Scancode values, not SDL_Keycode values + //const Uint8 *keys = SDL_GetKeyboardState(NULL); + //for (int i = 0; i < 256; i++) CORE.Input.Keyboard.currentKeyState[i] = keys[i]; + + CORE.Window.resizedLastFrame = false; + + if ((CORE.Window.eventWaiting) || (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED) && !FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN))) + { + SDL_WaitEvent(NULL); + CORE.Time.previous = GetTime(); + } + + SDL_Event event = { 0 }; + while (SDL_PollEvent(&event) != 0) + { + // All input events can be processed after polling + switch (event.type) + { + case SDL_QUIT: CORE.Window.shouldClose = true; break; + + case SDL_DROPFILE: // Dropped file + { + if (CORE.Window.dropFileCount == 0) + { + // When a new file is dropped, reserve a fixed number of slots for all possible dropped files + // at the moment limit the number of drops at once to 1024 files but this behaviour should probably be reviewed + // TODO: Pointers should probably be reallocated for any new file added... + CORE.Window.dropFilepaths = (char **)RL_CALLOC(1024, sizeof(char *)); + + CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + + #if defined(USING_VERSION_SDL3) + // const char *data; // The text for SDL_EVENT_DROP_TEXT and the file name for SDL_EVENT_DROP_FILE, NULL for other events + // Event memory is now managed by SDL, so it should not be freed in SDL_EVENT_DROP_FILE, + // in case data needs to be hold onto the text in SDL_EVENT_TEXT_EDITING and SDL_EVENT_TEXT_INPUT events, + // a copy is required, SDL_TEXTINPUTEVENT_TEXT_SIZE is no longer necessary and has been removed + snprintf(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], MAX_FILEPATH_LENGTH, "%s", event.drop.data); + #else + snprintf(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], MAX_FILEPATH_LENGTH, "%s", event.drop.file); + SDL_free(event.drop.file); + #endif + + CORE.Window.dropFileCount++; + } + else if (CORE.Window.dropFileCount < 1024) + { + CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + + #if defined(USING_VERSION_SDL3) + snprintf(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], MAX_FILEPATH_LENGTH, "%s", event.drop.data); + #else + snprintf(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], MAX_FILEPATH_LENGTH, "%s", event.drop.file); + SDL_free(event.drop.file); + #endif + + CORE.Window.dropFileCount++; + } + else TRACELOG(LOG_WARNING, "FILE: Maximum drag and drop files at once is limited to 1024 files!"); + + } break; + + // Window events are also polled (minimized, maximized, close...) + + #ifndef USING_VERSION_SDL3 + // The SDL_WINDOWEVENT_* events have been moved to top level events, and SDL_WINDOWEVENT has been removed + // In general, handling this change means checking for the individual events instead of first checking for SDL_WINDOWEVENT + // and then checking for window events; Events >= SDL_EVENT_WINDOW_FIRST and <= SDL_EVENT_WINDOW_LAST can be compared + // to see whether it's a window event + case SDL_WINDOWEVENT: + { + switch (event.window.event) + { + #endif + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_SIZE_CHANGED: + { + const int width = event.window.data1; + const int height = event.window.data2; + SetupViewport(width, height); + + // Consider content scaling if required + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + CORE.Window.screen.width = (int)(width/GetWindowScaleDPI().x); + CORE.Window.screen.height = (int)(height/GetWindowScaleDPI().y); + } + else + { + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + } + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + CORE.Window.resizedLastFrame = true; + + #ifndef USING_VERSION_SDL3 + // Manually detect if the window was maximized (due to SDL2 restore being unreliable on some platforms) + // to remove the FLAG_WINDOW_MAXIMIZED accordingly + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) + { + int borderTop = 0; + int borderLeft = 0; + int borderBottom = 0; + int borderRight = 0; + SDL_GetWindowBordersSize(platform.window, &borderTop, &borderLeft, &borderBottom, &borderRight); + SDL_Rect usableBounds; + SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(platform.window), &usableBounds); + + if ((width + borderLeft + borderRight != usableBounds.w) && (height + borderTop + borderBottom != usableBounds.h)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } + #endif + + #if defined(GRAPHICS_API_OPENGL_SOFTWARE) + swResize(width, height); + #endif + } break; + + case SDL_WINDOWEVENT_ENTER: CORE.Input.Mouse.cursorOnScreen = true; break; + case SDL_WINDOWEVENT_LEAVE: CORE.Input.Mouse.cursorOnScreen = false; break; + + case SDL_WINDOWEVENT_MINIMIZED: + { + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); + } break; + case SDL_WINDOWEVENT_MAXIMIZED: + { + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } break; + case SDL_WINDOWEVENT_RESTORED: + { + if (!FLAG_IS_SET(SDL_GetWindowFlags(platform.window), SDL_WINDOW_MINIMIZED)) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); + } + + #ifdef USING_VERSION_SDL3 + if (!FLAG_IS_SET(SDL_GetWindowFlags(platform.window), SDL_WINDOW_MAXIMIZED)) + { + if (FLAG_IS_SET(CORE.Window.flags, SDL_WINDOW_MAXIMIZED)) FLAG_CLEAR(CORE.Window.flags, SDL_WINDOW_MAXIMIZED); + } + #endif + } break; + + case SDL_WINDOWEVENT_HIDDEN: + { + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); + } break; + case SDL_WINDOWEVENT_SHOWN: + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); + } break; + + case SDL_WINDOWEVENT_FOCUS_GAINED: + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); + } break; + case SDL_WINDOWEVENT_FOCUS_LOST: + { + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); + } break; + + #ifndef USING_VERSION_SDL3 + default: break; + } + } break; + #endif + + // Check keyboard events + case SDL_KEYDOWN: + { + #if defined(USING_VERSION_SDL3) + // SDL3 Migration: The following structures have been removed: SDL_Keysym + KeyboardKey key = ConvertScancodeToKey(event.key.scancode); + #else + KeyboardKey key = ConvertScancodeToKey(event.key.keysym.scancode); + #endif + + if (key != KEY_NULL) + { + // If key was up, add it to the key pressed queue + if ((CORE.Input.Keyboard.currentKeyState[key] == 0) && (CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE)) + { + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + + CORE.Input.Keyboard.currentKeyState[key] = 1; + } + + if (event.key.repeat) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1; + + // Check for registered exit key to request exit game loop on next iteration + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey]) CORE.Window.shouldClose = true; + + } break; + case SDL_KEYUP: + { + #if defined(USING_VERSION_SDL3) + KeyboardKey key = ConvertScancodeToKey(event.key.scancode); + #else + KeyboardKey key = ConvertScancodeToKey(event.key.keysym.scancode); + #endif + if (key != KEY_NULL) CORE.Input.Keyboard.currentKeyState[key] = 0; + } break; + case SDL_TEXTINPUT: + { + // NOTE: event.text.text data comes an UTF-8 text sequence but register codepoints (int) + + // Check if there is space available in the queue + if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) + { + // Add character (codepoint) to the queue + #if defined(USING_VERSION_SDL3) + size_t textLen = strlen(event.text.text); + unsigned int codepoint = (unsigned int)SDL_StepUTF8(&event.text.text, &textLen); + #else + int codepointSize = 0; + int codepoint = GetCodepointNextSDL(event.text.text, &codepointSize); + #endif + + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = codepoint; + CORE.Input.Keyboard.charPressedQueueCount++; + } + } break; + + // Check mouse events + case SDL_MOUSEBUTTONDOWN: + { + // NOTE: SDL2 mouse button order is LEFT, MIDDLE, RIGHT, but raylib uses LEFT, RIGHT, MIDDLE like GLFW + // The following conditions align SDL with raylib.h MouseButton enum order + int btn = event.button.button - 1; + if (btn == 2) btn = 1; + else if (btn == 1) btn = 2; + + CORE.Input.Mouse.currentButtonState[btn] = 1; + CORE.Input.Touch.currentTouchState[btn] = 1; + + touchAction = 1; + } break; + case SDL_MOUSEBUTTONUP: + { + // NOTE: SDL2 mouse button order is LEFT, MIDDLE, RIGHT, but raylib uses LEFT, RIGHT, MIDDLE like GLFW + // The following conditions align SDL with raylib.h MouseButton enum order + int btn = event.button.button - 1; + if (btn == 2) btn = 1; + else if (btn == 1) btn = 2; + + CORE.Input.Mouse.currentButtonState[btn] = 0; + CORE.Input.Touch.currentTouchState[btn] = 0; + + touchAction = 0; + } break; + case SDL_MOUSEWHEEL: + { +#if defined(USING_VERSION_SDL3) + CORE.Input.Mouse.currentWheelMove.x = event.wheel.x; + CORE.Input.Mouse.currentWheelMove.y = event.wheel.y; +#else + CORE.Input.Mouse.currentWheelMove.x = event.wheel.preciseX; + CORE.Input.Mouse.currentWheelMove.y = event.wheel.preciseY; +#endif + } break; + case SDL_MOUSEMOTION: + { + if (CORE.Input.Mouse.cursorLocked) + { + CORE.Input.Mouse.currentPosition.x = (float)event.motion.xrel; + CORE.Input.Mouse.currentPosition.y = (float)event.motion.yrel; + CORE.Input.Mouse.previousPosition = (Vector2){ 0.0f, 0.0f }; + } + else + { + CORE.Input.Mouse.currentPosition.x = (float)event.motion.x; + CORE.Input.Mouse.currentPosition.y = (float)event.motion.y; + } + + CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + touchAction = 2; + } break; + + // Check Touch events + case SDL_FINGERDOWN: + { + UpdateTouchPointsSDL(event.tfinger); + touchAction = 1; + realTouch = true; + } break; + case SDL_FINGERUP: + { + UpdateTouchPointsSDL(event.tfinger); + touchAction = 0; + realTouch = true; + } break; + case SDL_FINGERMOTION: + { + UpdateTouchPointsSDL(event.tfinger); + touchAction = 2; + realTouch = true; + } break; + + // Check Gamepad events + case SDL_JOYDEVICEADDED: + { + int jid = event.jdevice.which; // Joystick device index + + // Check if already added at InitPlatform + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (jid == platform.gamepadId[i]) return; + } + + int nextAvailableSlot = 0; + while (nextAvailableSlot < MAX_GAMEPADS && CORE.Input.Gamepad.ready[nextAvailableSlot]) + { + nextAvailableSlot++; + } + + if ((nextAvailableSlot < MAX_GAMEPADS) && !CORE.Input.Gamepad.ready[nextAvailableSlot]) + { + platform.gamepad[nextAvailableSlot] = SDL_GameControllerOpen(jid); + platform.gamepadId[nextAvailableSlot] = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(platform.gamepad[nextAvailableSlot])); + + if (platform.gamepad[nextAvailableSlot]) + { + CORE.Input.Gamepad.ready[nextAvailableSlot] = true; + CORE.Input.Gamepad.axisCount[nextAvailableSlot] = SDL_JoystickNumAxes(SDL_GameControllerGetJoystick(platform.gamepad[nextAvailableSlot])); + CORE.Input.Gamepad.axisState[nextAvailableSlot][GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; + CORE.Input.Gamepad.axisState[nextAvailableSlot][GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; + memset(CORE.Input.Gamepad.name[nextAvailableSlot], 0, MAX_GAMEPAD_NAME_LENGTH); + const char *controllerName = SDL_GameControllerNameForIndex(nextAvailableSlot); + if (controllerName != NULL) snprintf(CORE.Input.Gamepad.name[nextAvailableSlot], MAX_GAMEPAD_NAME_LENGTH, "%s", controllerName); + else memcpy(CORE.Input.Gamepad.name[nextAvailableSlot], "noname", 6); + } + else TRACELOG(LOG_WARNING, "PLATFORM: Unable to open game controller [ERROR: %s]", SDL_GetError()); + } + } break; + case SDL_JOYDEVICEREMOVED: + { + int jid = event.jdevice.which; // Joystick instance id + + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (platform.gamepadId[i] == jid) + { + SDL_GameControllerClose(platform.gamepad[i]); + CORE.Input.Gamepad.ready[i] = false; + memset(CORE.Input.Gamepad.name[i], 0, MAX_GAMEPAD_NAME_LENGTH); + platform.gamepadId[i] = -1; + break; + } + } + } break; + case SDL_CONTROLLERBUTTONDOWN: + { + int button = -1; + + #if defined(USING_VERSION_SDL3) + switch (event.gbutton.button) + #else + switch (event.jbutton.button) + #endif + { + case SDL_CONTROLLER_BUTTON_Y: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case SDL_CONTROLLER_BUTTON_B: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case SDL_CONTROLLER_BUTTON_A: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case SDL_CONTROLLER_BUTTON_X: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + + case SDL_CONTROLLER_BUTTON_BACK: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case SDL_CONTROLLER_BUTTON_GUIDE: button = GAMEPAD_BUTTON_MIDDLE; break; + case SDL_CONTROLLER_BUTTON_START: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + + case SDL_CONTROLLER_BUTTON_DPAD_UP: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + + case SDL_CONTROLLER_BUTTON_LEFTSTICK: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + default: break; + } + + if (button >= 0) + { + for (int i = 0; i < MAX_GAMEPADS; i++) + { + #if defined(USING_VERSION_SDL3) + if (platform.gamepadId[i] == event.gbutton.which) + #else + if (platform.gamepadId[i] == event.jbutton.which) + #endif + { + CORE.Input.Gamepad.currentButtonState[i][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + break; + } + } + } + } break; + case SDL_CONTROLLERBUTTONUP: + { + int button = -1; + + #if defined(USING_VERSION_SDL3) + switch (event.gbutton.button) + #else + switch (event.jbutton.button) + #endif + { + case SDL_CONTROLLER_BUTTON_Y: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case SDL_CONTROLLER_BUTTON_B: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case SDL_CONTROLLER_BUTTON_A: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case SDL_CONTROLLER_BUTTON_X: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + + case SDL_CONTROLLER_BUTTON_BACK: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case SDL_CONTROLLER_BUTTON_GUIDE: button = GAMEPAD_BUTTON_MIDDLE; break; + case SDL_CONTROLLER_BUTTON_START: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + + case SDL_CONTROLLER_BUTTON_DPAD_UP: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + + case SDL_CONTROLLER_BUTTON_LEFTSTICK: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + default: break; + } + + if (button >= 0) + { + for (int i = 0; i < MAX_GAMEPADS; i++) + { + #if defined(USING_VERSION_SDL3) + if (platform.gamepadId[i] == event.gbutton.which) + #else + if (platform.gamepadId[i] == event.jbutton.which) + #endif + { + CORE.Input.Gamepad.currentButtonState[i][button] = 0; + if (CORE.Input.Gamepad.lastButtonPressed == button) CORE.Input.Gamepad.lastButtonPressed = 0; + break; + } + } + } + } break; + case SDL_CONTROLLERAXISMOTION: + { + int axis = -1; + + switch (event.jaxis.axis) + { + case SDL_CONTROLLER_AXIS_LEFTX: axis = GAMEPAD_AXIS_LEFT_X; break; + case SDL_CONTROLLER_AXIS_LEFTY: axis = GAMEPAD_AXIS_LEFT_Y; break; + case SDL_CONTROLLER_AXIS_RIGHTX: axis = GAMEPAD_AXIS_RIGHT_X; break; + case SDL_CONTROLLER_AXIS_RIGHTY: axis = GAMEPAD_AXIS_RIGHT_Y; break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: axis = GAMEPAD_AXIS_LEFT_TRIGGER; break; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: axis = GAMEPAD_AXIS_RIGHT_TRIGGER; break; + default: break; + } + + if (axis >= 0) + { + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (platform.gamepadId[i] == event.jaxis.which) + { + // SDL axis value range is -32768 to 32767, normalizing it to raylib's -1.0 to 1.0f range + float value = event.jaxis.value/(float)32767; + CORE.Input.Gamepad.axisState[i][axis] = value; + + // Register button state for triggers in addition to their axes + if ((axis == GAMEPAD_AXIS_LEFT_TRIGGER) || (axis == GAMEPAD_AXIS_RIGHT_TRIGGER)) + { + int button = (axis == GAMEPAD_AXIS_LEFT_TRIGGER)? GAMEPAD_BUTTON_LEFT_TRIGGER_2 : GAMEPAD_BUTTON_RIGHT_TRIGGER_2; + int pressed = (value > 0.1f); + CORE.Input.Gamepad.currentButtonState[i][button] = pressed; + if (pressed) CORE.Input.Gamepad.lastButtonPressed = button; + else if (CORE.Input.Gamepad.lastButtonPressed == button) CORE.Input.Gamepad.lastButtonPressed = 0; + } + break; + } + } + } + } break; + default: break; + } + +#if SUPPORT_GESTURES_SYSTEM + if (touchAction > -1) + { + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + gestureEvent.touchAction = touchAction; + + // Assign a pointer ID + gestureEvent.pointId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + if (touchAction == 2 || realTouch) gestureEvent.position[0] = CORE.Input.Touch.position[0]; + else gestureEvent.position[0] = GetMousePosition(); + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures-system for processing + ProcessGestureEvent(gestureEvent); + + touchAction = -1; + } +#endif + } + //----------------------------------------------------------------------------- +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + // Initialize SDL internal global state, only required systems + // NOTE: Not all systems need to be initialized, SDL_INIT_AUDIO is not required, managed by miniaudio + int result = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER); + if (result < 0) { TRACELOG(LOG_WARNING, "SDL: Failed to initialize SDL"); return -1; } + + // Initialize graphic device: display/window and graphic context + //---------------------------------------------------------------------------- + unsigned int flags = 0; + FLAG_SET(flags, SDL_WINDOW_SHOWN); + FLAG_SET(flags, SDL_WINDOW_INPUT_FOCUS); + FLAG_SET(flags, SDL_WINDOW_MOUSE_FOCUS); + FLAG_SET(flags, SDL_WINDOW_MOUSE_CAPTURE); // Window has mouse captured + + // Check window creation flags + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) FLAG_SET(flags, SDL_WINDOW_FULLSCREEN); + + //if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) FLAG_SET(flags, SDL_WINDOW_HIDDEN); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) FLAG_SET(flags, SDL_WINDOW_BORDERLESS); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) FLAG_SET(flags, SDL_WINDOW_RESIZABLE); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_SET(flags, SDL_WINDOW_MINIMIZED); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_SET(flags, SDL_WINDOW_MAXIMIZED); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) + { + FLAG_CLEAR(flags, SDL_WINDOW_INPUT_FOCUS); + FLAG_CLEAR(flags, SDL_WINDOW_MOUSE_FOCUS); + } + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) FLAG_SET(flags, SDL_WINDOW_ALWAYS_ON_TOP); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) FLAG_CLEAR(flags, SDL_WINDOW_MOUSE_CAPTURE); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) FLAG_SET(flags, SDL_WINDOW_ALLOW_HIGHDPI); + + //if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT)) FLAG_SET(flags, SDL_WINDOW_TRANSPARENT); // Alternative: SDL_GL_ALPHA_SIZE = 8 + //if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_DESKTOP)) FLAG_SET(flags, SDL_WINDOW_FULLSCREEN_DESKTOP); + + // NOTE: Some OpenGL context attributes must be set before window creation + + if (rlGetVersion() != RL_OPENGL_SOFTWARE) + { + // Add the flag telling the window to use an OpenGL context + flags |= SDL_WINDOW_OPENGL; + + // Check selection OpenGL version + if (rlGetVersion() == RL_OPENGL_21) + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + } + else if (rlGetVersion() == RL_OPENGL_33) + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + } + else if (rlGetVersion() == RL_OPENGL_43) + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + #if RLGL_ENABLE_OPENGL_DEBUG_CONTEXT + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); // Enable OpenGL Debug Context + #endif + } + else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + } + else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + } + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); + } + } + + // Init window +#if defined(USING_VERSION_SDL3) + platform.window = SDL_CreateWindow(CORE.Window.title, CORE.Window.screen.width, CORE.Window.screen.height, flags); + + + // NOTE: SDL3 no longer enables text input by default, + // it is needed to be enabled manually to keep GetCharPressed() working + // REF: https://github.com/libsdl-org/SDL/commit/72fc6f86e5d605a3787222bc7dc18c5379047f4a + const char *enableOSK = SDL_GetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD); + if (enableOSK == NULL) SDL_SetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD, "0"); + if (!SDL_StartTextInput(platform.window)) TRACELOG(LOG_WARNING, "SDL: Failed to start text input: %s", SDL_GetError()); + if (enableOSK == NULL) SDL_SetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD, NULL); +#else + platform.window = SDL_CreateWindow(CORE.Window.title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, CORE.Window.screen.width, CORE.Window.screen.height, flags); +#endif + + // Init OpenGL context + if (rlGetVersion() != RL_OPENGL_SOFTWARE) + { + platform.glContext = SDL_GL_CreateContext(platform.window); + } + + if ((platform.window != NULL) && ((rlGetVersion() == RL_OPENGL_SOFTWARE) || (platform.glContext != NULL))) + { + CORE.Window.ready = true; + + SDL_DisplayMode displayMode = { 0 }; + SDL_GetCurrentDisplayMode(GetCurrentMonitor(), &displayMode); + + CORE.Window.display.width = displayMode.w; + CORE.Window.display.height = displayMode.h; + + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully %s", + FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)? "(HighDPI)" : ""); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + + if (platform.glContext != NULL) + { + SDL_GL_SetSwapInterval((FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT))? 1: 0); + + // Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions + rlLoadExtensions(SDL_GL_GetProcAddress); + } + } + else + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); + return -1; + } + + //---------------------------------------------------------------------------- + + // Initialize input events system + //---------------------------------------------------------------------------- + // Initialize gamepads + for (int i = 0; i < MAX_GAMEPADS; i++) + { + platform.gamepadId[i] = -1; // Set all gamepad initial instance ids as invalid to not conflict with instance id zero + } + +#if defined(USING_VERSION_SDL3) + int numJoysticks; + SDL_JoystickID *joysticks = SDL_GetJoysticks(&numJoysticks); +#else + int numJoysticks = SDL_NumJoysticks(); +#endif + + for (int i = 0; (i < numJoysticks) && (i < MAX_GAMEPADS); i++) + { +#if defined(USING_VERSION_SDL3) + platform.gamepad[i] = SDL_OpenGamepad(joysticks[i]); +#else + platform.gamepad[i] = SDL_GameControllerOpen(i); +#endif + platform.gamepadId[i] = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(platform.gamepad[i])); + + if (platform.gamepad[i]) + { + CORE.Input.Gamepad.ready[i] = true; + CORE.Input.Gamepad.axisCount[i] = SDL_JoystickNumAxes(SDL_GameControllerGetJoystick(platform.gamepad[i])); + CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; + CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; +#if defined(USING_VERSION_SDL3) + const char *joystickName = SDL_GetJoystickNameForID(joysticks[i]); +#else + const char *joystickName = SDL_GameControllerNameForIndex(i); +#endif + snprintf(CORE.Input.Gamepad.name[i], MAX_GAMEPAD_NAME_LENGTH, "%s", joystickName); + CORE.Input.Gamepad.name[i][MAX_GAMEPAD_NAME_LENGTH - 1] = '\0'; + } + else TRACELOG(LOG_WARNING, "PLATFORM: Unable to open game controller [ERROR: %s]", SDL_GetError()); + } + + // Disable mouse events being interpreted as touch events + // NOTE: This is wanted because there are SDL_FINGER* events available which provide unique data + // Due to the way PollInputEvents() and rgestures.h are currently implemented, setting this won't break SUPPORT_MOUSE_GESTURES + SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); + + SDL_EventState(SDL_DROPFILE, SDL_ENABLE); + //---------------------------------------------------------------------------- + + // Initialize timing system + //---------------------------------------------------------------------------- + // Get base time from window initialization + CORE.Time.base = (double)SDL_GetPerformanceCounter()/(double)SDL_GetPerformanceFrequency(); + + #if defined(_WIN32) && SUPPORT_WINMM_HIGHRES_TIMER && !SUPPORT_BUSY_WAIT_LOOP + SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "1"); // SDL equivalent of timeBeginPeriod() and timeEndPeriod() + #endif + + // NOTE: No need to call InitTimer(), let SDL manage it internally + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + // Define base path for storage + CORE.Storage.basePath = SDL_GetBasePath(); // Alternative: GetWorkingDirectory(); + //---------------------------------------------------------------------------- + +#if defined(USING_VERSION_SDL3) + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (SDL3): Initialized successfully"); +#else + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (SDL2): Initialized successfully"); +#endif + + return 0; +} + +// Close platform +void ClosePlatform(void) +{ + SDL_FreeCursor(platform.cursor); // Free cursor + if (platform.glContext != NULL) SDL_GL_DeleteContext(platform.glContext); // Deinitialize OpenGL context + SDL_DestroyWindow(platform.window); + SDL_Quit(); // Deinitialize SDL internal global state +} + +// Scancode to keycode mapping +static KeyboardKey ConvertScancodeToKey(SDL_Scancode sdlScancode) +{ + if ((sdlScancode >= 0) && (sdlScancode < SCANCODE_MAPPED_NUM)) + { + return mapScancodeToKey[sdlScancode]; + } + + return KEY_NULL; // No equivalent key in raylib +} + +// Get next codepoint in a byte sequence and bytes processed +static int GetCodepointNextSDL(const char *text, int *codepointSize) +{ + const char *ptr = text; + int codepoint = 0x3f; // Codepoint (defaults to '?') + *codepointSize = 1; + + // Get current codepoint and bytes processed + if (0xf0 == (0xf8 & ptr[0])) + { + // 4 byte UTF-8 codepoint + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } // 10xxxxxx checks + codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); + *codepointSize = 4; + } + else if (0xe0 == (0xf0 & ptr[0])) + { + // 3 byte UTF-8 codepoint */ + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } // 10xxxxxx checks + codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); + *codepointSize = 3; + } + else if (0xc0 == (0xe0 & ptr[0])) + { + // 2 byte UTF-8 codepoint + if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } // 10xxxxxx checks + codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); + *codepointSize = 2; + } + else if (0x00 == (0x80 & ptr[0])) + { + // 1 byte UTF-8 codepoint + codepoint = ptr[0]; + *codepointSize = 1; + } + + return codepoint; +} + +// Update CORE input touch point info from SDL touch data +static void UpdateTouchPointsSDL(SDL_TouchFingerEvent event) +{ +#if defined(USING_VERSION_SDL3) // SDL3 + int count = 0; + SDL_Finger **fingers = SDL_GetTouchFingers(event.touchID, &count); + CORE.Input.Touch.pointCount = count; + + for (int i = 0; i < CORE.Input.Touch.pointCount; i++) + { + SDL_Finger *finger = fingers[i]; + CORE.Input.Touch.pointId[i] = finger->id; + CORE.Input.Touch.position[i].x = finger->x*CORE.Window.screen.width; + CORE.Input.Touch.position[i].y = finger->y*CORE.Window.screen.height; + CORE.Input.Touch.currentTouchState[i] = 1; + } + + SDL_free(fingers); + +#else // SDL2 + + CORE.Input.Touch.pointCount = SDL_GetNumTouchFingers(event.touchId); + + for (int i = 0; i < CORE.Input.Touch.pointCount; i++) + { + SDL_Finger *finger = SDL_GetTouchFinger(event.touchId, i); + CORE.Input.Touch.pointId[i] = (int)finger->id; + CORE.Input.Touch.position[i].x = finger->x*CORE.Window.screen.width; + CORE.Input.Touch.position[i].y = finger->y*CORE.Window.screen.height; + CORE.Input.Touch.currentTouchState[i] = 1; + } +#endif + + for (int i = CORE.Input.Touch.pointCount; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.currentTouchState[i] = 0; +} diff --git a/src/libraries/raylib/raylib/src/platforms/rcore_desktop_win32.c b/src/libraries/raylib/raylib/src/platforms/rcore_desktop_win32.c new file mode 100644 index 0000000..fcc7c4c --- /dev/null +++ b/src/libraries/raylib/raylib/src/platforms/rcore_desktop_win32.c @@ -0,0 +1,2311 @@ +/********************************************************************************************** +* +* rcore_desktop_win32 - Functions to manage window, graphics device and inputs +* +* PLATFORM: DESKTOP: WIN32 +* - Windows (Win32, Win64) +* +* LIMITATIONS: +* - Initial development stage, lot of functionality missing +* - No support for MOUSE_BUTTON_FORWARD/MOUSE_BUTTON_BACK +* +* POSSIBLE IMPROVEMENTS: +* - Improvement 01 +* - Improvement 02 +* +* CONFIGURATION: +* #define RCORE_PLATFORM_CUSTOM_FLAG +* Custom flag for rcore on target platform -not used- +* +* DEPENDENCIES: +* - Win32 API (windows.h) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +// Move windows.h symbols to new names to avoid redefining the same names as raylib +#define CloseWindow CloseWindowWin32 +#define Rectangle RectangleWin32 +#define ShowCursor ShowCursorWin32 +#define DrawTextA DrawTextAWin32 +#define DrawTextW DrawTextWin32 +#define DrawTextExA DrawTextExAWin32 +#define DrawTextExW DrawTextExWin32 + +#define WIN32_LEAN_AND_MEAN +#include + +#undef CloseWindow // raylib symbol collision +#undef Rectangle // raylib symbol collision +#undef ShowCursor // raylib symbol collision +#undef LoadImage // raylib symbol collision +#undef DrawText // raylib symbol collision +#undef DrawTextA +#undef DrawTextW +#undef DrawTextEx // raylib symbol collision +#undef DrawTextExA +#undef DrawTextExW + +#include +#include +#include + +#include // Required for alloca() + +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) + #include +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// NOTE: appScreenSize is the last screen size requested by the app, +// the backend must keep the client area this size (after DPI scaling is applied) +// when the window isn't fullscreen/maximized/minimized +typedef struct { + HWND hwnd; // Window handler + HDC hdc; // Graphic context handler + HGLRC glContext; // OpenGL context handler + + // Software renderer variables + HDC hdcmem; // Memory graphic context handler + HBITMAP hbitmap; // GDI bitmap handler + unsigned int *pixels; // Pointer to pixel data buffer (BGRA format) + + unsigned int appScreenWidth; + unsigned int appScreenHeight; + unsigned int desiredFlags; + + LARGE_INTEGER timerFrequency; +} PlatformData; + +// Define WGL function pointer types (no wglext.h needed) +typedef HGLRC (WINAPI *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int *); +typedef BOOL (WINAPI *PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC, const int *, const FLOAT *, UINT, int *, UINT *); +typedef BOOL (WINAPI *PFNWGLSWAPINTERVALEXTPROC)(int); +typedef const char *(WINAPI *PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC hdc); + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context + +static PlatformData platform = { 0 }; // Platform specific data + +// Required WGL functions +static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; +static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL; +static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; +static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL; + +// -------------------------------------------------------------------------------- +// This part of the file contains pure functions that never access global state +// This distinction helps keep the backend maintainable as the inputs and outputs +// of every function called in this section can be fully derived from the call-site alone +// -------------------------------------------------------------------------------- + +// Prevent any code in this part of the file from accessing the global CORE state +#define CORE DONT_USE_CORE_HERE + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define A_TO_W_ALLOCA(outWstr, inAnsi) do { \ + size_t outLen = AToWLen(inAnsi); \ + outWstr = (WCHAR *)alloca(sizeof(WCHAR)*(outLen + 1)); \ + AToWCopy(inAnsi, outWstr, outLen); \ + outWstr[outLen] = 0; \ +} while (0) + +#define STYLE_MASK_ALL 0xffffffff +#define STYLE_MASK_READONLY (WS_MINIMIZE | WS_MAXIMIZE) +#define STYLE_MASK_WRITABLE (~STYLE_MASK_READONLY) + +#define STYLE_FLAGS_RESIZABLE (WS_THICKFRAME | WS_MAXIMIZEBOX) + +#define STYLE_FLAGS_UNDECORATED_OFF (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX) +#define STYLE_FLAGS_UNDECORATED_ON WS_POPUP + +#define WINDOW_STYLE_EX 0 + +#define CLASS_NAME L"raylibWindow" + +#define FLAG_MASK_OPTIONAL (FLAG_VSYNC_HINT) +#define FLAG_MASK_REQUIRED ~(FLAG_MASK_OPTIONAL) + +// Flags that have no operations to perform during an update +#define FLAG_MASK_NO_UPDATE (FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT) + +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_RED_SHIFT_ARB 0x2016 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_GREEN_SHIFT_ARB 0x2018 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_BLUE_SHIFT_ARB 0x201a +#define WGL_ALPHA_BITS_ARB 0x201b +#define WGL_ALPHA_SHIFT_ARB 0x201c +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_TYPE_RGBA_ARB 0x202b + +// Context acceleration types +#define WGL_NO_ACCELERATION_ARB 0x2025 // OpenGL 1.1 GDI software rasterizer +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 // OpenGL hardware-accelerated, using GPU-drivers provided by vendor + +// WGL_ARB_multisample extension supported +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 // Multisampling: 1 if multisample buffers are supported +#define WGL_SAMPLES_ARB 0x2042 // Multisampling: Number of samples per pixel (4, 8, 16) + +// WGL_ARB_framebuffer_sRGB extension supported +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 // GL_TRUE if the framebuffer can do sRGB conversion + +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 +#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Maximize-minimize request types +typedef enum { + MIZED_NONE, + MIZED_MIN, + MIZED_MAX +} Mized; + +// Flag operations +// NOTE: Some ops need to be deferred +typedef struct { + DWORD set; + DWORD clear; +} FlagsOp; + +// Monitor info type +typedef struct { + HMONITOR needle; + int index; + int matchIndex; + RECT rect; +} MonitorInfo; + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +// Get ASCII to WCHAR length +static size_t AToWLen(const char *ascii) +{ + int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, NULL, 0); + + if (sizeNeeded < 0) TRACELOG(LOG_ERROR, "WIN32: Failed to calculate wide length [ERROR: %u]", GetLastError()); + + return sizeNeeded; +} + +// Copy ASCII to WCHAR string +static void AToWCopy(const char *ascii, wchar_t *outPtr, size_t outLen) +{ + int size = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, outPtr, (int)outLen); + if (size != outLen) TRACELOG(LOG_WARNING, "WIN32: Failed to convert %i UTF-8 chars to WCHAR, converted %i chars", outLen, size); +} + +static bool DecoratedFromStyle(DWORD style) +{ + if (style & STYLE_FLAGS_UNDECORATED_ON) + { + if (style & STYLE_FLAGS_UNDECORATED_OFF) TRACELOG(LOG_ERROR, "WIN32: FLAGS: Style 0x%x has both undecorated on/off flags", style); + return false; // Not decorated + } + + DWORD masked = (style & STYLE_FLAGS_UNDECORATED_OFF); + if (STYLE_FLAGS_UNDECORATED_OFF != masked) TRACELOG(LOG_ERROR, "WIN32: FLAGS: Style 0x%x is missing flags 0x%x", masked, masked ^ STYLE_FLAGS_UNDECORATED_OFF); + + return true; // Decorated +} + +// Get window style from required flags +static DWORD MakeWindowStyle(unsigned flags) +{ + // Flag is not needed because there are no child windows, + // but supposedly it improves efficiency, plus, windows adds this + // flag automatically anyway so it keeps flags in sync with the OS + DWORD style = WS_CLIPSIBLINGS; + + style |= (flags & FLAG_WINDOW_HIDDEN)? 0 : WS_VISIBLE; + style |= (flags & FLAG_WINDOW_RESIZABLE)? STYLE_FLAGS_RESIZABLE : 0; + style |= (flags & FLAG_WINDOW_UNDECORATED)? STYLE_FLAGS_UNDECORATED_ON : STYLE_FLAGS_UNDECORATED_OFF; + + // Minimized takes precedence over maximized + int mized = MIZED_NONE; + if (flags & FLAG_WINDOW_MINIMIZED) mized = MIZED_MIN; + else if (flags & FLAG_WINDOW_MAXIMIZED) mized = MIZED_MAX; + + switch (mized) + { + case MIZED_NONE: break; + case MIZED_MIN: style |= WS_MINIMIZE; break; + case MIZED_MAX: style |= WS_MAXIMIZE; break; + default: break; + } + + return style; +} + +// Check flags state, enforces that the actual window/platform state is in sync with raylib's flags +static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expectedStyle, DWORD styleCheckMask) +{ + DWORD styleFromFlags = MakeWindowStyle(flags); + if ((styleFromFlags & styleCheckMask) != (expectedStyle & styleCheckMask)) + { + TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s: window flags (0x%x) produced style 0x%x which != expected 0x%x (diff=0x%x, mask=0x%x)", + context, flags, styleFromFlags & styleCheckMask, expectedStyle & styleCheckMask, + (styleFromFlags & styleCheckMask) ^ (expectedStyle & styleCheckMask), styleCheckMask); + } + + SetLastError(0); + LONG actualStyle = (LONG)GetWindowLongPtrW(hwnd, GWL_STYLE); + if ((actualStyle & styleCheckMask) != (expectedStyle & styleCheckMask)) + { + TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s: expected style 0x%x but got 0x%x (diff=0x%x, mask=0x%x, lasterror=%lu)", + context, expectedStyle & styleCheckMask, actualStyle & styleCheckMask, + (expectedStyle & styleCheckMask) ^ (actualStyle & styleCheckMask), + styleCheckMask, GetLastError()); + } + + if (styleCheckMask & WS_MINIMIZE) + { + bool isIconic = IsIconic(hwnd); + bool styleMinimized = !!(WS_MINIMIZE & actualStyle); + if (isIconic != styleMinimized) TRACELOG(LOG_ERROR, "WIN32: FLAGS: IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized); + } + + if (styleCheckMask & WS_MAXIMIZE) + { + WINDOWPLACEMENT placement; + placement.length = sizeof(placement); + if (!GetWindowPlacement(hwnd, &placement)) + { + TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s failed, error=%lu", "GetWindowPlacement", GetLastError()); + } + bool placementMaximized = (placement.showCmd == SW_SHOWMAXIMIZED); + bool styleMaximized = WS_MAXIMIZE & actualStyle; + if (placementMaximized != styleMaximized) + { + TRACELOG(LOG_ERROR, "WIN32: FLAGS: Maximized state desync, placement maximized=%d (showCmd=%lu) style maximized=%d", + placementMaximized, placement.showCmd, styleMaximized); + } + } +} + +// Calculate window size (with borders, title-bar...) from desired client size (framebuffer size) +static SIZE CalcWindowSize(UINT dpi, SIZE clientSize, DWORD style) +{ + RECT rect = { 0, 0, clientSize.cx, clientSize.cy }; + + int result = AdjustWindowRectExForDpi(&rect, style, 0, WINDOW_STYLE_EX, dpi); + if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to adjust window rect [ERROR: %lu]", GetLastError()); + + return (SIZE){ rect.right - rect.left, rect.bottom - rect.top }; +} + +// Update window size if required +// NOTE: Returns true if the window size was updated, false otherwise +static bool UpdateWindowSize(int mode, HWND hwnd, int width, int height, unsigned flags) +{ + if (flags & FLAG_WINDOW_MINIMIZED) return false; + + if (flags & FLAG_WINDOW_MAXIMIZED) + { + CheckFlags("UpdateWindowSize(maximized)", hwnd, flags, MakeWindowStyle(flags), STYLE_MASK_ALL); + return false; + } + + if (flags & FLAG_BORDERLESS_WINDOWED_MODE) + { + MONITORINFO info = { 0 }; + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + info.cbSize = sizeof(info); + if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor info [ERROR: %lu]", GetLastError()); + + RECT windowRect = { 0 }; + if (!GetWindowRect(hwnd, &windowRect)) TRACELOG(LOG_ERROR, "WIN32: Failed to get window rect [ERROR: %lu]", GetLastError()); + + if ((windowRect.left == info.rcMonitor.left) && + (windowRect.top == info.rcMonitor.top) && + ((windowRect.right - windowRect.left) == (info.rcMonitor.right - info.rcMonitor.left)) && + ((windowRect.bottom - windowRect.top) == (info.rcMonitor.bottom - info.rcMonitor.top))) return false; + + if (!SetWindowPos(hwnd, HWND_TOP, + info.rcMonitor.left, info.rcMonitor.top, + info.rcMonitor.right - info.rcMonitor.left, + info.rcMonitor.bottom - info.rcMonitor.top, + SWP_NOOWNERZORDER)) + { + TRACELOG(LOG_ERROR, "WIN32: Failed to set window position [ERROR: %lu]", GetLastError()); + } + + return true; + } + + // Get size in pixels from points, considering high-dpi + UINT dpi = GetDpiForWindow(hwnd); + float dpiScale = ((float)dpi)/96.0f; + bool dpiScaling = flags & FLAG_WINDOW_HIGHDPI; + SIZE desiredSize = { + .cx = dpiScaling? (int)((float)width*dpiScale) : width, + .cy = dpiScaling? (int)((float)height*dpiScale) : height + }; + + // Get client size (framebuffer inside the window) + RECT rect = { 0 }; + GetClientRect(hwnd, &rect); + SIZE clientSize = { rect.right, rect.bottom }; + + // If client size is alread desired size, no need to update + if ((clientSize.cx == desiredSize.cx) || (clientSize.cy == desiredSize.cy)) return false; + + TRACELOG(LOG_INFO, "WIN32: Restoring client size from [%dx%d] to [%dx%d] (dpi:%lu dpiScaling:%d app:%ix%i)", + clientSize.cx, clientSize.cy, desiredSize.cx, desiredSize.cy, dpi, dpiScaling, width, height); + + // Calculate window size from desired framebuffer size and window flags + SIZE windowSize = CalcWindowSize(dpi, desiredSize, MakeWindowStyle(flags)); + POINT windowPos = { 0 }; + UINT swpFlags = SWP_NOZORDER | SWP_FRAMECHANGED; + + if (mode == 0) // UPDATE_WINDOW_FIRST + { + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + if (!monitor) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor from window [ERROR: %lu]", GetLastError()); + + MONITORINFO info = { 0 }; + info.cbSize = sizeof(info); + if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor info [ERROR: %lu]", GetLastError()); + + #define MAX(a,b) (((a)>(b))? (a):(b)) + + LONG monitorWidth = info.rcMonitor.right - info.rcMonitor.left; + LONG monitorHeight = info.rcMonitor.bottom - info.rcMonitor.top; + windowPos = (POINT){ + MAX(0, (monitorWidth - windowSize.cx)/2), + MAX(0, (monitorHeight - windowSize.cy)/2), + }; + } + else swpFlags |= SWP_NOMOVE; + + // WARNING: This code must be called after swInit() has been called, after InitPlatform() in [rcore] + SetWindowPos(hwnd, NULL, windowPos.x, windowPos.y, windowSize.cx, windowSize.cy, SWP_NOMOVE | SWP_NOZORDER); + + return true; +} + +// Check if running in Windows 10 version 1703 (Creators Update) +static BOOL IsWindows10Version1703OrGreaterWin32(void) +{ + HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); + + DWORD (*Verify)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG) = + (DWORD (*)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG))GetProcAddress(ntdll, "RtlVerifyVersionInfo"); + if (!Verify) + { + TRACELOG(LOG_ERROR, "WIN32: Failed to verify Windows version [ERROR: %lu]", GetLastError()); + return 0; + } + + RTL_OSVERSIONINFOEXW osvi = { 0 }; + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + osvi.dwMajorVersion = 10; + osvi.dwMinorVersion = 0; + osvi.dwBuildNumber = 15063; // Build 15063 corresponds to Windows 10 version 1703 (Creators Update) + + DWORDLONG cond = 0; + VER_SET_CONDITION(cond, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(cond, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(cond, VER_BUILDNUMBER, VER_GREATER_EQUAL); + + return 0 == (*Verify)(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER, cond); +} + +// Get OpenGL function pointers +static void *WglGetProcAddress(const char *procname) +{ + void *proc = (void *)wglGetProcAddress(procname); + + if ((proc == NULL) || + // NOTE: Some GPU drivers could return following + // invalid sentinel values instead of NULL + (proc == (void *)0x1) || + (proc == (void *)0x2) || + (proc == (void *)0x3) || + (proc == (void *)-1)) + { + // TODO: Keep gl module pointer as global platform data? + HMODULE glModule = LoadLibraryW(L"opengl32.dll"); + proc = (void *)GetProcAddress(glModule, procname); + + //if (proc == NULL) TRACELOG(LOG_ERROR, "GL: GetProcAddress() failed to get %s [%p], error=%u", procname, proc, GetLastError()); + //else TRACELOG(LOG_INFO, "GL: Found entry point for %s [%p]", procname, proc); + } + + return proc; +} + +// Get key from wparam (mapping) +static KeyboardKey GetKeyFromWparam(WPARAM wparam) +{ + switch (wparam) + { + /* case VK_LBUTTON: return KEY_; */ + /* case VK_RBUTTON: return KEY_; */ + /* case VK_CANCEL: return KEY_; */ + /* case VK_MBUTTON: return KEY_; */ + /* case VK_XBUTTON1: return KEY_; */ + /* case VK_XBUTTON2: return KEY_; */ + /* case VK_BACK: return KEY_; */ + /* case VK_TAB: return KEY_; */ + /* case VK_CLEAR: return KEY_; */ + case VK_RETURN: return KEY_ENTER; + /* case VK_SHIFT: return KEY_; */ + /* case VK_CONTROL: return KEY_; */ + /* case VK_MENU: return KEY_; */ + /* case VK_PAUSE: return KEY_; */ + /* case VK_CAPITAL: return KEY_; */ + /* case VK_KANA: return KEY_; */ + /* case VK_HANGUL: return KEY_; */ + /* case VK_IME_ON: return KEY_; */ + /* case VK_JUNJA: return KEY_; */ + /* case VK_FINAL: return KEY_; */ + /* case VK_HANJA: return KEY_; */ + /* case VK_KANJI: return KEY_; */ + /* case VK_IME_OFF: return KEY_; */ + case VK_ESCAPE: return KEY_ESCAPE; + /* case VK_CONVERT: return KEY_; */ + /* case VK_NONCONVERT: return KEY_; */ + /* case VK_ACCEPT: return KEY_; */ + /* case VK_MODECHANGE: return KEY_; */ + case VK_SPACE: return KEY_SPACE; + /* case VK_PRIOR: return KEY_; */ + /* case VK_NEXT: return KEY_; */ + /* case VK_END: return KEY_; */ + /* case VK_HOME: return KEY_; */ + case VK_LEFT: return KEY_LEFT; + case VK_UP: return KEY_UP; + case VK_RIGHT: return KEY_RIGHT; + case VK_DOWN: return KEY_DOWN; + /* case VK_SELECT: return KEY_; */ + /* case VK_PRINT: return KEY_; */ + /* case VK_EXECUTE: return KEY_; */ + /* case VK_SNAPSHOT: return KEY_; */ + /* case VK_INSERT: return KEY_; */ + /* case VK_DELETE: return KEY_; */ + /* case VK_HELP: return KEY_; */ + case '0': return KEY_ZERO; + case '1': return KEY_ONE; + case '2': return KEY_TWO; + case '3': return KEY_THREE; + case '4': return KEY_FOUR; + case '5': return KEY_FIVE; + case '6': return KEY_SIX; + case '7': return KEY_SEVEN; + case '8': return KEY_EIGHT; + case '9': return KEY_NINE; + /* case 0x3A-40: return KEY_; */ + case 'A': return KEY_A; + case 'B': return KEY_B; + case 'C': return KEY_C; + case 'D': return KEY_D; + case 'E': return KEY_E; + case 'F': return KEY_F; + case 'G': return KEY_G; + case 'H': return KEY_H; + case 'I': return KEY_I; + case 'J': return KEY_J; + case 'K': return KEY_K; + case 'L': return KEY_L; + case 'M': return KEY_M; + case 'N': return KEY_N; + case 'O': return KEY_O; + case 'P': return KEY_P; + case 'Q': return KEY_Q; + case 'R': return KEY_R; + case 'S': return KEY_S; + case 'T': return KEY_T; + case 'U': return KEY_U; + case 'V': return KEY_V; + case 'W': return KEY_W; + case 'X': return KEY_X; + case 'Y': return KEY_Y; + case 'Z': return KEY_Z; + //case VK_LWIN: return KEY_; + //case VK_RWIN: return KEY_; + //case VK_APPS: return KEY_; + //case VK_SLEEP: return KEY_; + //case VK_NUMPAD0: return KEY_; + //case VK_NUMPAD1: return KEY_; + //case VK_NUMPAD2: return KEY_; + //case VK_NUMPAD3: return KEY_; + //case VK_NUMPAD4: return KEY_; + //case VK_NUMPAD5: return KEY_; + //case VK_NUMPAD6: return KEY_; + //case VK_NUMPAD7: return KEY_; + //case VK_NUMPAD8: return KEY_; + //case VK_NUMPAD9: return KEY_; + //case VK_MULTIPLY: return KEY_; + //case VK_ADD: return KEY_; + //case VK_SEPARATOR: return KEY_; + //case VK_SUBTRACT: return KEY_; + //case VK_DECIMAL: return KEY_; + //case VK_DIVIDE: return KEY_; + //case VK_F1: return KEY_; + //case VK_F2: return KEY_; + //case VK_F3: return KEY_; + //case VK_F4: return KEY_; + //case VK_F5: return KEY_; + //case VK_F6: return KEY_; + //case VK_F7: return KEY_; + //case VK_F8: return KEY_; + //case VK_F9: return KEY_; + //case VK_F10: return KEY_; + //case VK_F11: return KEY_; + //case VK_F12: return KEY_; + //case VK_F13: return KEY_; + //case VK_F14: return KEY_; + //case VK_F15: return KEY_; + //case VK_F16: return KEY_; + //case VK_F17: return KEY_; + //case VK_F18: return KEY_; + //case VK_F19: return KEY_; + //case VK_F20: return KEY_; + //case VK_F21: return KEY_; + //case VK_F22: return KEY_; + //case VK_F23: return KEY_; + //case VK_F24: return KEY_; + //case VK_NUMLOCK: return KEY_; + //case VK_SCROLL: return KEY_; + //case VK_LSHIFT: return KEY_; + //case VK_RSHIFT: return KEY_; + //case VK_LCONTROL: return KEY_; + //case VK_RCONTROL: return KEY_; + //case VK_LMENU: return KEY_; + //case VK_RMENU: return KEY_; + //case VK_BROWSER_BACK: return KEY_; + //case VK_BROWSER_FORWARD: return KEY_; + //case VK_BROWSER_REFRESH: return KEY_; + //case VK_BROWSER_STOP: return KEY_; + //case VK_BROWSER_SEARCH: return KEY_; + //case VK_BROWSER_FAVORITES: return KEY_; + //case VK_BROWSER_HOME: return KEY_; + //case VK_VOLUME_MUTE: return KEY_; + //case VK_VOLUME_DOWN: return KEY_; + //case VK_VOLUME_UP: return KEY_; + //case VK_MEDIA_NEXT_TRACK: return KEY_; + //case VK_MEDIA_PREV_TRACK: return KEY_; + //case VK_MEDIA_STOP: return KEY_; + //case VK_MEDIA_PLAY_PAUSE: return KEY_; + //case VK_LAUNCH_MAIL: return KEY_; + //case VK_LAUNCH_MEDIA_SELECT: return KEY_; + //case VK_LAUNCH_APP1: return KEY_; + //case VK_LAUNCH_APP2: return KEY_; + //case VK_OEM_1: return KEY_; + //case VK_OEM_PLUS: return KEY_; + //case VK_OEM_COMMA: return KEY_; + //case VK_OEM_MINUS: return KEY_; + //case VK_OEM_PERIOD: return KEY_; + //case VK_OEM_2: return KEY_; + //case VK_OEM_3: return KEY_; + //case VK_OEM_4: return KEY_; + //case VK_OEM_5: return KEY_; + //case VK_OEM_6: return KEY_; + //case VK_OEM_7: return KEY_; + //case VK_OEM_8: return KEY_; + //case VK_OEM_102: return KEY_; + //case VK_PROCESSKEY: return KEY_; + //case VK_PACKET: return KEY_; + //case VK_ATTN: return KEY_; + //case VK_CRSEL: return KEY_; + //case VK_EXSEL: return KEY_; + //case VK_EREOF: return KEY_; + //case VK_PLAY: return KEY_; + //case VK_ZOOM: return KEY_; + //case VK_NONAME: return KEY_; + //case VK_PA1: return KEY_; + //case VK_OEM_CLEAR: return KEY_; + default: return KEY_NULL; + } +} + +// Get cursor name +static LPCWSTR GetCursorName(int cursor) +{ + LPCWSTR name = (LPCWSTR)IDC_ARROW; + + switch (cursor) + { + case MOUSE_CURSOR_DEFAULT: name = (LPCWSTR)IDC_ARROW; break; + case MOUSE_CURSOR_ARROW: name = (LPCWSTR)IDC_ARROW; break; + case MOUSE_CURSOR_IBEAM: name = (LPCWSTR)IDC_IBEAM; break; + case MOUSE_CURSOR_CROSSHAIR: name = (LPCWSTR)IDC_CROSS; break; + case MOUSE_CURSOR_POINTING_HAND: name = (LPCWSTR)IDC_HAND; break; + case MOUSE_CURSOR_RESIZE_EW: name = (LPCWSTR)IDC_SIZEWE; break; + case MOUSE_CURSOR_RESIZE_NS: name = (LPCWSTR)IDC_SIZENS; break; + case MOUSE_CURSOR_RESIZE_NWSE: name = (LPCWSTR)IDC_SIZENWSE; break; + case MOUSE_CURSOR_RESIZE_NESW: name = (LPCWSTR)IDC_SIZENESW; break; + case MOUSE_CURSOR_RESIZE_ALL: name = (LPCWSTR)IDC_SIZEALL; break; + case MOUSE_CURSOR_NOT_ALLOWED: name = (LPCWSTR)IDC_NO; break; + default: break; + } + + return name; +} + +// Count monitors process +// NOTE: Required by GetMonitorCount() +static BOOL CALLBACK CountMonitorsProc(HMONITOR handle, HDC hdc, LPRECT rect, LPARAM lparam) +{ + int *count = (int *)lparam; + *count += 1; + + // Always return TRUE to continue the loop, otherwise, the caller + // can't distinguish between stopping the loop and an error + return TRUE; +} + +// Find monitor process +// NOTE: Required by GetCurrentMonitor() +static BOOL CALLBACK FindMonitorProc(HMONITOR handle, HDC hdc, LPRECT rect, LPARAM lparam) +{ + MonitorInfo *monitor = (MonitorInfo *)lparam; + + if (handle == monitor->needle) + { + monitor->matchIndex = monitor->index; + monitor->rect = *rect; + } + + monitor->index += 1; + + // Always return TRUE to continue the loop, otherwise, the caller + // can't distinguish between stopping the loop and an error + return TRUE; +} + +// Get style changed required operations flags +// NOTE: Required for deferred operations +static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *style, FlagsOp *deferredFlags) +{ + // Check window resizable flag change + bool resizable = (coreWindowFlags & FLAG_WINDOW_RESIZABLE); + bool resizableOld = ((style->styleOld & STYLE_FLAGS_RESIZABLE) != 0); + bool resizableNew = ((style->styleNew & STYLE_FLAGS_RESIZABLE) != 0); + if (resizable != resizableOld) TRACELOG(LOG_ERROR, "WIN32: Expected resizable %u but got %u", resizable, resizableOld); + if (resizableOld != resizableNew) + { + if (resizableNew) deferredFlags->set |= FLAG_WINDOW_RESIZABLE; + else deferredFlags->clear |= FLAG_WINDOW_RESIZABLE; + } + + // Check window decorated flag change + bool decorated = (0 == (coreWindowFlags & FLAG_WINDOW_UNDECORATED)); + bool decoratedOld = DecoratedFromStyle(style->styleOld); + bool decoratedNew = DecoratedFromStyle(style->styleNew); + if (decorated != decoratedOld) TRACELOG(LOG_ERROR, "WIN32: Expected decorated %u but got %u", decorated, decoratedOld); + if (decoratedOld != decoratedNew) + { + if (decoratedNew) deferredFlags->clear |= FLAG_WINDOW_UNDECORATED; + else deferredFlags->set |= FLAG_WINDOW_UNDECORATED; + } + + // Check window hidden flag change + bool hidden = (coreWindowFlags & FLAG_WINDOW_HIDDEN); + bool hiddenOld = ((style->styleOld & WS_VISIBLE) == 0); + bool hiddenNew = ((style->styleNew & WS_VISIBLE) == 0); + if (hidden != hiddenOld) TRACELOG(LOG_ERROR, "WIN32: Expected hidden %u but got %u", hidden, hiddenOld); + if (hiddenOld != hiddenNew) + { + if (hiddenNew) deferredFlags->set |= FLAG_WINDOW_HIDDEN; + else deferredFlags->clear |= FLAG_WINDOW_HIDDEN; + } +} + +// Adopt window resize +// NOTE: Call when the window is rezised, returns true +// if the new window size should update the desired app size +static bool AdoptWindowResize(unsigned flags) +{ + if (flags & FLAG_WINDOW_MINIMIZED) return false; + if (flags & FLAG_WINDOW_MAXIMIZED) return false; + if (flags & FLAG_FULLSCREEN_MODE) return false; + if (flags & FLAG_BORDERLESS_WINDOWED_MODE) return false; + if (!(flags & FLAG_WINDOW_RESIZABLE)) return false; + + return true; +} + +// --------------------------------------------------------------------------------------------- +// Here's the end of the "pure function section", the rest of the file can access global state +// --------------------------------------------------------------------------------------------- + +// Unlock the ability to use CORE in the rest of the file +#undef CORE + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +void ClosePlatform(void); // Close platform + +// Win32 process messages management function +static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + +// Win32: Handle inputs functions +static void HandleKey(WPARAM wparam, LPARAM lparam, char state); +static void HandleMouseButton(int button, char state); +static void HandleRawInput(LPARAM lparam); +static void HandleWindowResize(HWND hwnd, int *width, int *height); + +static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags); +static unsigned SanitizeFlags(int mode, unsigned flags); +static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height); // Update window flags + +// Check if OpenGL extension is available +static bool IsWglExtensionAvailable(HDC hdc, const char *extension); + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +bool WindowShouldClose(void) +{ + return CORE.Window.shouldClose; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + TRACELOG(LOG_WARNING, "WIN32: Toggle full screen functionality not implemented"); +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE); + else SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE); +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + SetWindowState(FLAG_WINDOW_MAXIMIZED); +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + SetWindowState(FLAG_WINDOW_MINIMIZED); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) && + (CORE.Window.flags & FLAG_WINDOW_MINIMIZED)) ClearWindowState(FLAG_WINDOW_MINIMIZED); + else ClearWindowState(FLAG_WINDOW_MINIMIZED | FLAG_WINDOW_MAXIMIZED); +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + platform.desiredFlags = SanitizeFlags(1 /*SANITIZE_FLAGS_NORMAL*/, CORE.Window.flags | flags); + UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + platform.desiredFlags = SanitizeFlags(1 /*SANITIZE_FLAGS_NORMAL*/, CORE.Window.flags & ~flags); + UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + if (!platform.hwnd || (image.data == NULL) || (image.width <= 0) || (image.height <= 0)) return; + + HDC hdc = GetDC(platform.hwnd); + + // Create 32-bit BGRA DIB for color + BITMAPV5HEADER bi = { 0 }; + ZeroMemory(&bi, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = image.width; + bi.bV5Height = -image.height; // Negative = top-down bitmap + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x00FF0000; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x000000FF; + bi.bV5AlphaMask = 0xFF000000; + + unsigned char *targetBits = NULL; + HBITMAP hColorBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, (void **)&targetBits, NULL, 0); + if (!hColorBitmap) + { + ReleaseDC(platform.hwnd, hdc); + return; + } + + // Copy RGBA > BGRA (Win32 expects BGRA) + for (int y = 0; y < image.height; y++) + { + for (int x = 0; x < image.width; x++) + { + int i = (y*image.width + x)*4; + targetBits[i + 0] = ((unsigned char *)image.data)[i + 2]; // B + targetBits[i + 1] = ((unsigned char *)image.data)[i + 1]; // G + targetBits[i + 2] = ((unsigned char *)image.data)[i + 0]; // R + targetBits[i + 3] = ((unsigned char *)image.data)[i + 3]; // A + } + } + + // Create mask bitmap (1-bit, all opaque) + HBITMAP hMaskBitmap = CreateBitmap(image.width, image.height, 1, 1, NULL); + + // Build icon info + ICONINFO ii = { 0 }; + ZeroMemory(&ii, sizeof(ii)); + ii.fIcon = TRUE; + ii.hbmMask = hMaskBitmap; + ii.hbmColor = hColorBitmap; + + HICON hIcon = CreateIconIndirect(&ii); + + // Clean up GDI bitmaps (icon keeps copies internally) + DeleteObject(hColorBitmap); + DeleteObject(hMaskBitmap); + ReleaseDC(platform.hwnd, hdc); + + if (hIcon) + { + // Set both large and small icons + SendMessage(platform.hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + SendMessage(platform.hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); + } +} + +// Set icon for window +void SetWindowIcons(Image *images, int count) +{ + // TODO: Implement SetWindowIcons() +} + +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; + + WCHAR *titleWide = NULL; + A_TO_W_ALLOCA(titleWide, CORE.Window.title); + + int result = SetWindowTextW(platform.hwnd, titleWide); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to set window title [ERROR: %lu]", GetLastError()); +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + if (platform.hwnd != NULL) + { + RECT rect = { 0 }; + if (GetWindowRect(platform.hwnd, &rect)) + { + int width = rect.right - rect.left; + int height = rect.bottom - rect.top; + + // Move the window to the new position (keeping size and z-order) + SetWindowPos(platform.hwnd, NULL, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE); + } + } +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + TRACELOG(LOG_WARNING, "SetWindowMonitor not implemented"); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + if ((width > CORE.Window.screenMax.width) || (height > CORE.Window.screenMax.height)) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot set minimum screen size higher than the maximum"); + return; + } + + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; + + SetWindowSize(platform.appScreenWidth, platform.appScreenHeight); +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + if ((width < CORE.Window.screenMin.width) || (height < CORE.Window.screenMin.height)) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot set maximum screen size lower than the minimum"); + return; + } + + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; + + SetWindowSize(platform.appScreenWidth, platform.appScreenHeight); +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + int screenWidth = fmaxf(CORE.Window.screenMin.width, fminf(CORE.Window.screenMax.width, width)); + int screenHeight = fmaxf(CORE.Window.screenMin.height, fminf(CORE.Window.screenMax.height, height)); + + UpdateWindowSize(1, platform.hwnd, screenWidth, screenHeight, platform.desiredFlags); +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + TRACELOG(LOG_WARNING, "SetWindowOpacity not implemented"); +} + +// Set window focused +void SetWindowFocused(void) +{ + TRACELOG(LOG_WARNING, "SetWindowFocused not implemented"); +} + +// Get native window handle +void *GetWindowHandle(void) +{ + return (void *)platform.hwnd; +} + +int GetMonitorCount(void) +{ + int count = 0; + + int result = EnumDisplayMonitors(NULL, NULL, CountMonitorsProc, (LPARAM)&count); + if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError()); + + return count; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + HMONITOR monitor = MonitorFromWindow(platform.hwnd, MONITOR_DEFAULTTOPRIMARY); + if (!monitor) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "MonitorFromWindow", GetLastError()); + + MonitorInfo info = { 0 }; + info.needle = monitor; + info.index = 0; + info.matchIndex = -1; + + int result = EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&info); + if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError()); + + return info.matchIndex; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPosition not implemented"); + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + //TRACELOG(LOG_WARNING, "GetMonitorWidth not implemented"); + return 0; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + //TRACELOG(LOG_WARNING, "GetMonitorHeight not implemented"); + return 0; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth not implemented"); + return 0; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight not implemented"); + return 0; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorRefreshRate not implemented"); + return 0; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorName not implemented"); + return 0; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + //TRACELOG(LOG_WARNING, "GetWindowPosition not implemented"); + return (Vector2){ 0, 0 }; +} + +// Get window scale DPI factor for current monitor +Vector2 GetWindowScaleDPI(void) +{ + float scale = ((float)GetDpiForWindow(platform.hwnd))/96.0f; + return (Vector2){ scale, scale }; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + TRACELOG(LOG_WARNING, "SetClipboardText not implemented"); +} + +// Get clipboard text content +const char *GetClipboardText(void) +{ + TRACELOG(LOG_WARNING, "GetClipboardText not implemented"); + return NULL; +} + +#if SUPPORT_CLIPBOARD_IMAGE && SUPPORT_MODULE_RTEXTURES + #define WIN32_CLIPBOARD_IMPLEMENTATION + #define WINUSER_ALREADY_INCLUDED + #define WINBASE_ALREADY_INCLUDED + #define WINGDI_ALREADY_INCLUDED + #include "../external/win32_clipboard.h" +#endif + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + +#if SUPPORT_CLIPBOARD_IMAGE && SUPPORT_MODULE_RTEXTURES + unsigned int dataSize = 0; + void *fileData = NULL; + int width = 0; + int height = 0; + + fileData = (void *)Win32GetClipboardImageData(&width, &height, &dataSize); + + if (fileData == NULL) TRACELOG(LOG_WARNING, "Clipboard image: Couldn't get clipboard data."); + else + { + image = LoadImageFromMemory(".bmp", (const unsigned char*)fileData, (int)dataSize); + + RL_FREE(fileData); + } +#else + TRACELOG(LOG_WARNING, "Clipboard image: SUPPORT_CLIPBOARD_IMAGE requires SUPPORT_MODULE_RTEXTURES to work properly"); +#endif // SUPPORT_CLIPBOARD_IMAGE + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_ARROW)); + CORE.Input.Mouse.cursorHidden = false; +} + +// Hide mouse cursor +void HideCursor(void) +{ + // NOTE: Using SetCursor() instead of ShowCursor() because + // it makes it easy to only hide the cursor while it's inside the client area + SetCursor(NULL); + CORE.Input.Mouse.cursorHidden = true; +} + +// Enable cursor (unlock cursor) +void EnableCursor(void) +{ + if (CORE.Input.Mouse.cursorLocked) + { + if (!ClipCursor(NULL)) TRACELOG(LOG_WARNING, "WIN32: Failed to clip cursor [ERROR: %lu]", GetLastError()); + + RAWINPUTDEVICE rid = { 0 }; + rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC + rid.usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE + rid.dwFlags = RIDEV_REMOVE; // Add to this window even in background + rid.hwndTarget = NULL; + int result = RegisterRawInputDevices(&rid, 1, sizeof(rid)); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to register raw input devices [ERROR: %lu]", GetLastError()); + + ShowCursor(); + CORE.Input.Mouse.cursorLocked = false; + } +} + +// Disables cursor (lock cursor) +void DisableCursor(void) +{ + if (!CORE.Input.Mouse.cursorLocked) + { + RAWINPUTDEVICE rid = { 0 }; + rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC + rid.usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE + rid.dwFlags = RIDEV_INPUTSINK; // Add to this window even in background + rid.hwndTarget = platform.hwnd; + int result = RegisterRawInputDevices(&rid, 1, sizeof(rid)); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to register raw input devices [ERROR: %lu]", GetLastError()); + + RECT clientRect = { 0 }; + if (!GetClientRect(platform.hwnd, &clientRect)) TRACELOG(LOG_WARNING, "WIN32: Failed to get client rectangle [ERROR: %lu]", GetLastError()); + + POINT topleft = { clientRect.left, clientRect.top }; + if (!ClientToScreen(platform.hwnd, &topleft)) TRACELOG(LOG_WARNING, "WIN32: Failed to get client to screen size [ERROR: %lu]", GetLastError()); + + LONG width = clientRect.right - clientRect.left; + LONG height = clientRect.bottom - clientRect.top; + + TRACELOG(LOG_INFO, "WIN32: Clip cursor client rect: [%d,%d %d,%d], top-left: (%d,%d)", + clientRect.left, clientRect.top, clientRect.right, clientRect.bottom, topleft.x, topleft.y); + + LONG centerX = topleft.x + width/2; + LONG centerY = topleft.y + height/2; + RECT clipRect = { centerX, centerY, centerX + 1, centerY + 1 }; + if (!ClipCursor(&clipRect)) TRACELOG(LOG_WARNING, "WIN32: Failed to clip cursor [ERROR: %lu]", GetLastError()); + + CORE.Input.Mouse.previousPosition = (Vector2){ 0, 0 }; + CORE.Input.Mouse.currentPosition = (Vector2){ 0, 0 }; + HideCursor(); + + CORE.Input.Mouse.cursorLocked = true; + } +} + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ + if (!platform.hdc) abort(); + +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + // Update framebuffer + rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels); + + // Force redraw + InvalidateRect(platform.hwnd, NULL, FALSE); + UpdateWindow(platform.hwnd); +#else + if (!SwapBuffers(platform.hdc)) TRACELOG(LOG_ERROR, "WIN32: Failed to swap buffers [ERROR: %lu]", GetLastError()); + if (!ValidateRect(platform.hwnd, NULL)) TRACELOG(LOG_ERROR, "WIN32: Failed to validate screen rect [ERROR: %lu]", GetLastError()); +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds +double GetTime(void) +{ + double time = 0.0; + LARGE_INTEGER now = { 0 }; + QueryPerformanceCounter(&now); + time = (double)(now.QuadPart - CORE.Time.base)/(double)platform.timerFrequency.QuadPart; + + return time; +} + +// Open URL with default system browser (if available) +// WARNING: This function is only safe to use if you control the URL given, +// a user could craft a malicious string to perform and undesired action +// NOTE: Some safety checks have been added to mitigate security issues +void OpenURL(const char *url) +{ + // Security check to (partially) avoid malicious code + if ((strchr(url, '\'') != NULL) || (strchr(url, '\"') != NULL)) + { + // Filter characters: ' and " + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'\"] characters"); + } + else if ((strncmp(url, "http://", 7) != 0) && (strncmp(url, "https://", 8) != 0)) + { + // Only allow URL starting with "http://" or "https://" protocols + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL must start with 'http://' or 'https://' protocols"); + } + else + { + int len = strlen(url) + 16; + char *cmd = (char *)RL_CALLOC(len, sizeof(char)); + snprintf(cmd, len, "explorer \"%s\"", url); + + int result = system(cmd); + if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created"); + RL_FREE(cmd); + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + TRACELOG(LOG_WARNING, "SetGamepadMappings not implemented"); + + return -1; +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + TRACELOG(LOG_WARNING, "SetGamepadVibration not implemented"); +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + if (!CORE.Input.Mouse.cursorLocked) + { + CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; + + TRACELOG(LOG_WARNING, "SetMousePosition not implemented at platform level"); + } + else TRACELOG(LOG_WARNING, "INPUT: MOUSE: Cursor not enabled"); +} + +// Set mouse cursor +void SetMouseCursor(int cursor) +{ + LPCWSTR cursorName = GetCursorName(cursor); + HCURSOR hcursor = LoadCursorW(NULL, cursorName); + if (!hcursor) TRACELOG(LOG_ERROR, "WIN32: Failed to load requested cursor [ERROR: %lu]", GetLastError()); + + SetCursor(hcursor); + CORE.Input.Mouse.cursorHidden = false; +} + +// Get physical key name +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName not implemented"); + return NULL; +} + +// Register all input events +void PollInputEvents(void) +{ + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + + // Reset key repeats + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN + //CORE.Input.Gamepad.axisCount = 0; + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Reset touch positions + // TODO: It resets on target platform the mouse position and not filled again until a move-event, + // so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed! + //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; + + memcpy(CORE.Input.Keyboard.previousKeyState, CORE.Input.Keyboard.currentKeyState, sizeof(CORE.Input.Keyboard.previousKeyState)); + memset(CORE.Input.Keyboard.keyRepeatInFrame, 0, sizeof(CORE.Input.Keyboard.keyRepeatInFrame)); + + // Register previous mouse wheel state + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; + + // Register previous mouse position + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + + // Process windows messages + MSG msg = { 0 }; + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize modern OpenGL context +// NOTE: Creating a dummy context first to query required extensions +HGLRC InitOpenGL(HWND hwnd, HDC hdc) +{ + // First, create a dummy context to get WGL extensions + PIXELFORMATDESCRIPTOR pixelFormatDesc = { + .nSize = sizeof(PIXELFORMATDESCRIPTOR), + .nVersion = 1, + .dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + .iPixelType = PFD_TYPE_RGBA, + .cColorBits = 32, + .cAlphaBits = 8, + .cDepthBits = 24, + .iLayerType = PFD_MAIN_PLANE + }; + + int pixelFormat = ChoosePixelFormat(hdc, &pixelFormatDesc); + SetPixelFormat(hdc, pixelFormat, &pixelFormatDesc); + //int pixelFormat = ChoosePixelFormat(platform.hdc, &pixelFormatDesc); + //if (!pixelFormat) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ChoosePixelFormat", GetLastError()); return -1; } + //if (!SetPixelFormat(platform.hdc, pixelFormat, &pixelFormatDesc)) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetPixelFormat", GetLastError()); return -1; } + + HGLRC tempContext = wglCreateContext(hdc); + //if (!tempContext) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "wglCreateContext", GetLastError()); return -1; } + BOOL result = wglMakeCurrent(hdc, tempContext); + //if (!result) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "wglMakeCurrent", GetLastError()); return -1; } + + // Load WGL extension entry points + wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB"); + wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB"); + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT"); + wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB"); + + // Setup modern pixel format if extension is available + if (wglChoosePixelFormatARB) + { + int pixelFormatAttribs[] = { + WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, + WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, + WGL_SUPPORT_OPENGL_ARB, GL_TRUE, + WGL_DOUBLE_BUFFER_ARB, GL_TRUE, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_COLOR_BITS_ARB, 32, + //WGL_RED_BITS_ARB, 8, + //WGL_GREEN_BITS_ARB, 8, + //WGL_BLUE_BITS_ARB, 8, + //WGL_ALPHA_BITS_ARB, 8, + WGL_DEPTH_BITS_ARB, 24, + WGL_STENCIL_BITS_ARB, 8, + 0 // Terminator + }; + + int format = 0; + UINT numFormats = 0; + if (wglChoosePixelFormatARB(hdc, pixelFormatAttribs, NULL, 1, &format, &numFormats) && (numFormats > 0)) + { + PIXELFORMATDESCRIPTOR newPixelFormatDescriptor = { 0 }; + DescribePixelFormat(hdc, format, sizeof(newPixelFormatDescriptor), &newPixelFormatDescriptor); + SetPixelFormat(hdc, format, &newPixelFormatDescriptor); + } + } + + // Create real modern OpenGL context (3.3 core) + HGLRC realContext = NULL; + if (wglCreateContextAttribsARB) + { + int glContextVersionMajor = 1; + int glContextVersionMinor = 1; + int glContextProfile = WGL_CONTEXT_CORE_PROFILE_BIT_ARB; + + if (rlGetVersion() == RL_OPENGL_21) // Request OpenGL 2.1 context + { + glContextVersionMajor = 2; + glContextVersionMinor = 1; + } + else if (rlGetVersion() == RL_OPENGL_33) // Request OpenGL 3.3 context + { + glContextVersionMajor = 3; + glContextVersionMinor = 3; + } + else if (rlGetVersion() == RL_OPENGL_43) // Request OpenGL 4.3 context + { + glContextVersionMajor = 4; + glContextVersionMinor = 3; + } + else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context + { + if (IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es_profile") || + IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es2_profile")) + { + glContextVersionMajor = 2; + glContextVersionMinor = 0; + glContextProfile = WGL_CONTEXT_ES_PROFILE_BIT_EXT; + } + else TRACELOG(LOG_WARNING, "GL: OpenGL ES context not supported by GPU"); + } + else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context + { + if (IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es_profile") || + IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es2_profile")) + { + glContextVersionMajor = 3; + glContextVersionMinor = 0; + glContextProfile = WGL_CONTEXT_ES_PROFILE_BIT_EXT; + } + else TRACELOG(LOG_WARNING, "GL: OpenGL ES context not supported by GPU"); + } + + int contextAttribs[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, glContextVersionMajor, + WGL_CONTEXT_MINOR_VERSION_ARB, glContextVersionMinor, + WGL_CONTEXT_PROFILE_MASK_ARB, glContextProfile, // WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, WGL_CONTEXT_ES_PROFILE_BIT_EXT (if supported) + //WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB [glDebugMessageCallback()] + 0 // Terminator + }; + + // NOTE: Not sharing context resources so, second parameters is NULL + realContext = wglCreateContextAttribsARB(hdc, NULL, contextAttribs); + + // Check for error context creation errors + // ERROR_INVALID_VERSION_ARB (0x2095) + // ERROR_INVALID_PROFILE_ARB (0x2096) + if (realContext == NULL) TRACELOG(LOG_ERROR, "GL: Error creating requested context: %lu", GetLastError()); + } + + // Cleanup dummy temp context + wglMakeCurrent(NULL, NULL); + wglDeleteContext(tempContext); + + // Activate real context + if (realContext) wglMakeCurrent(hdc, realContext); + + // Once a real modern OpenGL context is created, + // required extensions can be loaded (function pointers) + rlLoadExtensions(WglGetProcAddress); + + return realContext; +} + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + int result = 0; + + platform.appScreenWidth = CORE.Window.screen.width; + platform.appScreenHeight = CORE.Window.screen.height; + platform.desiredFlags = SanitizeFlags(0 /*SANITIZE_FLAGS_FIRST*/, CORE.Window.flags); + + // NOTE: From this point CORE.Window.flags should always reflect the actual state of the window + CORE.Window.flags = FLAG_WINDOW_HIDDEN | (platform.desiredFlags & FLAG_MASK_NO_UPDATE); + CORE.Window.screenMax.width = 9999; + CORE.Window.screenMax.height = 9999; +/* + // TODO: Review SetProcessDpiAwarenessContext() + // NOTE: SetProcessDpiAwarenessContext() requires Windows 10, version 1703 and shcore.lib linkage + if (IsWindows10Version1703OrGreaterWin32()) + { + TRACELOG(LOG_INFO, "DpiAware: >=Win10Creators"); + if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) + TRACELOG(LOG_ERROR, "%s failed, error %u", "SetProcessDpiAwarenessContext", GetLastError()); + } + else + { + TRACELOG(LOG_INFO, "DpiAware: Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + + if (rlGetVersion() == RL_OPENGL_SOFTWARE) // Using software renderer + { + TRACELOG(LOG_INFO, "GL: OpenGL device information:"); + TRACELOG(LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); + TRACELOG(LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); + TRACELOG(LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); + TRACELOG(LOG_INFO, " > GLSL: %s", "NOT SUPPORTED"); + } + + // Initialize timing system + //---------------------------------------------------------------------------- + LARGE_INTEGER time = { 0 }; + QueryPerformanceCounter(&time); + QueryPerformanceFrequency(&platform.timerFrequency); + CORE.Time.base = time.QuadPart; + + InitTimer(); + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- + + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP: WIN32: Initialized successfully"); + + return 0; +} + +// Close platform +void ClosePlatform(void) +{ + if (platform.hwnd) + { + int result = DestroyWindow(platform.hwnd); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: WINDOW: Failed on window destroy [ERROR: %u]", GetLastError()); + platform.hwnd = NULL; + } +} + +// Window procedure, message processing callback +// NOTE: All window event messages are processed here +static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + LRESULT result = 0; + + // Sanity check + DWORD mask = STYLE_MASK_ALL; + if (platform.hwnd == hwnd) + { + if (msg == WM_WINDOWPOSCHANGING) mask &= ~(WS_MINIMIZE | WS_MAXIMIZE); + CheckFlags("WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask); + } + + FlagsOp flagsOp = { 0 }; + FlagsOp *deferredFlags = &flagsOp; + + // Message processing + //------------------------------------------------------------------------------------ + switch (msg) + { + case WM_CREATE: + { + // WARNING: Not recommended to do OpenGL intialization at this point + + } break; + //case WM_ACTIVATE + case WM_DESTROY: + { + // Clean up for window destruction + if (rlGetVersion() == RL_OPENGL_SOFTWARE) // Using software renderer + { + if (platform.hdcmem) + { + DeleteDC(platform.hdcmem); + platform.hdcmem = NULL; + } + + if (platform.hbitmap) + { + DeleteObject(platform.hbitmap); // Clears platform.pixels data + platform.hbitmap = NULL; + platform.pixels = NULL; // NOTE: Pointer invalid after DeleteObject() + } + } + else // OpenGL hardware renderer + { + wglMakeCurrent(platform.hdc, NULL); + if (platform.glContext) + { + if (!wglDeleteContext(platform.glContext)) abort(); + platform.glContext = NULL; + } + } + + if (platform.hdc) + { + if (!ReleaseDC(hwnd, platform.hdc)) abort(); + platform.hdc = NULL; + } + + PostQuitMessage(0); + + } break; + case WM_CLOSE: CORE.Window.shouldClose = true; break; // Window close button [x], ALT+F4 + //case WM_QUIT: // Application closing, not related to window + case WM_KILLFOCUS: + { + memset(CORE.Input.Keyboard.previousKeyState, 0, sizeof(CORE.Input.Keyboard.previousKeyState)); + memset(CORE.Input.Keyboard.currentKeyState, 0, sizeof(CORE.Input.Keyboard.currentKeyState)); + } break; + case WM_SIZING: // Sent to a window that the user is resizing + { + if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE) + { + //HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight); + } + + result = TRUE; + } break; + case WM_SIZE: + { + // WARNING: Don't trust the docs, they say this message can not be obtained if not calling DefWindowProc() + // in response to WM_WINDOWPOSCHANGED but looks like when a window is created, + // this message can be obtained without getting WM_WINDOWPOSCHANGED + +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + // WARNING: Waiting two frames before resizing because software-renderer backend is initilized with swInit() later + // than InitPlatform(), that triggers WM_SIZE, so avoid crashing + if (CORE.Time.frameCounter > 2) HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight); +#else + // NOTE: This message is only triggered on window creation + HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight); +#endif + result = 0; // If an application processes WM_SIZE message, it should return zero + } break; + case WM_GETMINMAXINFO: + { + DWORD style = MakeWindowStyle(platform.desiredFlags); + SIZE maxClientSize = { CORE.Window.screenMax.width, CORE.Window.screenMax.height }; + SIZE maxWindowSize = CalcWindowSize(96, maxClientSize, style); + SIZE minClientSize = { CORE.Window.screenMin.width, CORE.Window.screenMin.height }; + SIZE minWindowSize = CalcWindowSize(96, minClientSize, style); + + LPMINMAXINFO lpmmi = (LPMINMAXINFO) lparam; + lpmmi->ptMaxSize.x = maxWindowSize.cx; + lpmmi->ptMaxSize.y = maxWindowSize.cy; + lpmmi->ptMaxTrackSize.x = maxWindowSize.cx; + lpmmi->ptMaxTrackSize.y = maxWindowSize.cy; + lpmmi->ptMinTrackSize.x = minWindowSize.cx; + lpmmi->ptMinTrackSize.y = minWindowSize.cy; + } break; + case WM_STYLECHANGING: + { + if (wparam == GWL_STYLE) + { + STYLESTRUCT *ss = (STYLESTRUCT *)lparam; + GetStyleChangeFlagOps(CORE.Window.flags, ss, deferredFlags); + + UINT dpi = GetDpiForWindow(hwnd); + // Get client size (framebuffer inside the window) + RECT rect = { 0 }; + GetClientRect(hwnd, &rect); + SIZE clientSize = { rect.right, rect.bottom }; + SIZE oldSize = CalcWindowSize(dpi, clientSize, ss->styleOld); + SIZE newSize = CalcWindowSize(dpi, clientSize, ss->styleNew); + + if (oldSize.cx != newSize.cx || oldSize.cy != newSize.cy) + { + TRACELOG(LOG_INFO, "WIN32: WINDOW: Resize from style change [%dx%d] to [%dx%d]", oldSize.cx, oldSize.cy, newSize.cx, newSize.cy); + + if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) + { + // looks like windows will automatically "unminimize" a window + // if a style changes modifies it's size + TRACELOG(LOG_INFO, "WIN32: WINDOW: Style change modified window size, removing maximized flag"); + deferredFlags->clear |= FLAG_WINDOW_MAXIMIZED; + } + } + } + } break; + case WM_WINDOWPOSCHANGING: + { + WINDOWPOS *pos = (WINDOWPOS *)lparam; + if (pos->flags & SWP_SHOWWINDOW) deferredFlags->clear |= FLAG_WINDOW_HIDDEN; + else if (pos->flags & SWP_HIDEWINDOW) deferredFlags->set |= FLAG_WINDOW_HIDDEN; + + Mized mized = MIZED_NONE; + bool isIconic = IsIconic(hwnd); + bool styleMinimized = !!(WS_MINIMIZE & GetWindowLongPtrW(hwnd, GWL_STYLE)); + if (isIconic != styleMinimized) TRACELOG(LOG_WARNING, "WIN32: IsIconic state different from WS_MINIMIZED state"); + + if (isIconic) mized = MIZED_MIN; + else + { + WINDOWPLACEMENT placement; + placement.length = sizeof(placement); + if (!GetWindowPlacement(hwnd, &placement)) TRACELOG(LOG_ERROR, "WIN32: WINDOW: FAiled to get monitor placement [ERROR: %lu]", GetLastError()); + + if (placement.showCmd == SW_SHOWMAXIMIZED) mized = MIZED_MAX; + } + + switch (mized) + { + case MIZED_NONE: + { + deferredFlags->clear |= (FLAG_WINDOW_MINIMIZED | FLAG_WINDOW_MAXIMIZED); + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + MONITORINFO info; + info.cbSize = sizeof(info); + if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: MONITOR: Failed to get monitor info [ERROR: %lu]", GetLastError()); + + if ((pos->x == info.rcMonitor.left) && + (pos->y == info.rcMonitor.top) && + (pos->cx == (info.rcMonitor.right - info.rcMonitor.left)) && + (pos->cy == (info.rcMonitor.bottom - info.rcMonitor.top))) deferredFlags->set |= FLAG_BORDERLESS_WINDOWED_MODE; + else deferredFlags->clear |= FLAG_BORDERLESS_WINDOWED_MODE; + + } break; + case MIZED_MIN: + { + // !!! NOTE !!! Do not update the maximized/borderless + // flags because when hwnd is minimized it temporarily overrides + // the maximized state/flag which gets restored on SW_RESTORE + deferredFlags->set |= FLAG_WINDOW_MINIMIZED; + } break; + case MIZED_MAX: + { + deferredFlags->clear |= FLAG_WINDOW_MINIMIZED; + deferredFlags->set |= FLAG_WINDOW_MAXIMIZED; + } break; + default: break; + } + } break; + //case WM_MOVE: break; + case WM_WINDOWPOSCHANGED: + { + WINDOWPOS *pos = (WINDOWPOS*)lparam; + if (!(pos->flags & SWP_NOSIZE)) HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight); + + DefWindowProc(hwnd, msg, wparam, lparam); + } break; + case WM_GETDPISCALEDSIZE: + { + SIZE *inoutSize = (SIZE *)lparam; + UINT newDpi = (UINT)wparam; // TODO: WARNING: Converting from WPARAM = UINT_PTR + + // For the following flag changes, a window resize event should be posted, + // TODO: Should it be done after dpi changes? + if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return TRUE; + if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) return TRUE; + if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) return TRUE; + + float dpiScale = ((float)newDpi)/96.0f; + bool dpiScaling = CORE.Window.flags & FLAG_WINDOW_HIGHDPI; + // Get size in pixels from points + SIZE desired = { + .cx = dpiScaling? (int)((float)platform.appScreenWidth*dpiScale) : platform.appScreenWidth, + .cy = dpiScaling? (int)((float)platform.appScreenHeight*dpiScale) : platform.appScreenHeight + }; + inoutSize->cx = desired.cx; + inoutSize->cy = desired.cy; + + result = TRUE; + } break; + case WM_DPICHANGED: + { + // Get current dpi scale factor + float scalex = HIWORD(wparam)/96.0f; + float scaley = LOWORD(wparam)/96.0f; + + RECT *suggestedRect = (RECT *)lparam; + + // Never set the window size to anything other than the suggested rect here + // Doing so can cause a window to stutter between monitors when transitioning between them + int result = (int)SetWindowPos(hwnd, NULL, + suggestedRect->left, suggestedRect->top, + suggestedRect->right - suggestedRect->left, + suggestedRect->bottom - suggestedRect->top, + SWP_NOZORDER | SWP_NOACTIVATE); + + if (result == 0) TRACELOG(LOG_ERROR, "Failed to set window position [ERROR: %lu]", GetLastError()); + + // TODO: Update screen data, render size, screen scaling, viewport... + + } break; + case WM_SETCURSOR: + { + // Called when mouse moves, enters/leaves window... + if (LOWORD(lparam) == HTCLIENT) + { + SetCursor(CORE.Input.Mouse.cursorHidden? NULL : LoadCursorW(NULL, (LPCWSTR)IDC_ARROW)); + return 0; + } + + result = DefWindowProc(hwnd, msg, wparam, lparam); + } break; + case WM_PAINT: + { + if (rlGetVersion() == RL_OPENGL_SOFTWARE) // Using software renderer + { + PAINTSTRUCT ps = { 0 }; + HDC hdc = BeginPaint(hwnd, &ps); + + // Blit from memory DC to window DC + BitBlt(hdc, 0, 0, platform.appScreenWidth, platform.appScreenHeight, platform.hdcmem, 0, 0, SRCCOPY); + + EndPaint(hwnd, &ps); + } + else DefWindowProc(hwnd, msg, wparam, lparam); + } + case WM_INPUT: + { + //HandleRawInput(lparam); + } break; + case WM_MOUSEMOVE: + { + if (!CORE.Input.Mouse.cursorLocked) + { + CORE.Input.Mouse.currentPosition.x = (float)GET_X_LPARAM(lparam); + CORE.Input.Mouse.currentPosition.y = (float)GET_Y_LPARAM(lparam); + CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + } + } break; + case WM_KEYDOWN: HandleKey(wparam, lparam, 1); break; + case WM_KEYUP: HandleKey(wparam, lparam, 0); break; + case WM_LBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_LEFT, 1); break; + case WM_LBUTTONUP : HandleMouseButton(MOUSE_BUTTON_LEFT, 0); break; + case WM_RBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_RIGHT, 1); break; + case WM_RBUTTONUP : HandleMouseButton(MOUSE_BUTTON_RIGHT, 0); break; + case WM_MBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_MIDDLE, 1); break; + case WM_MBUTTONUP : HandleMouseButton(MOUSE_BUTTON_MIDDLE, 0); break; + case WM_XBUTTONDOWN: + { + switch (HIWORD(wparam)) + { + case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 1); break; + case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 1); break; + default: TRACELOG(LOG_WARNING, "TODO: handle ex mouse button DOWN wparam=%u", HIWORD(wparam)); break; + } + } break; + case WM_XBUTTONUP: + { + switch (HIWORD(wparam)) + { + case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 0); break; + case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 0); break; + default: TRACELOG(LOG_WARNING, "TODO: handle ex mouse button UP wparam=%u", HIWORD(wparam)); break; + } + } break; + case WM_MOUSEWHEEL: CORE.Input.Mouse.currentWheelMove.y = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; break; + case WM_MOUSEHWHEEL: CORE.Input.Mouse.currentWheelMove.x = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; break; + + default: result = DefWindowProcW(hwnd, msg, wparam, lparam); // Message passed directly for execution (default behaviour) + } + //------------------------------------------------------------------------------------ + + // Sanity check for flags + if (platform.hwnd == hwnd) CheckFlags("After WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask); + + // Operations to execute after the above check + if (flagsOp.set & flagsOp.clear) TRACELOG(LOG_WARNING, "WIN32: FLAGS: Flags 0x%x were both set and cleared", flagsOp.set & flagsOp.clear); + + DWORD save = CORE.Window.flags; + CORE.Window.flags |= flagsOp.set; + CORE.Window.flags &= ~flagsOp.clear; + if (save != CORE.Window.flags) TRACELOG(LOG_DEBUG, "WIN32: FLAGS: Current deferred flags: 0x%x > 0x%x (diff 0x%x)", save, CORE.Window.flags, save ^ CORE.Window.flags); + + return result; +} + +// Handle keyboard input event +static void HandleKey(WPARAM wparam, LPARAM lparam, char state) +{ + KeyboardKey key = GetKeyFromWparam(wparam); + + // TODO: Use scancode? + //BYTE scancode = lparam >> 16; + //TRACELOG(LOG_INFO, "KEY key=%d vk=%lu scan=%u = %u", key, wparam, scancode, state); + + if (key != KEY_NULL) + { + CORE.Input.Keyboard.currentKeyState[key] = state; + + if ((key == CORE.Input.Keyboard.exitKey) && (state == 1)) CORE.Window.shouldClose = true; + } + else TRACELOG(LOG_WARNING, "INPUT: Unknown (or currently unhandled) virtual keycode %d (0x%x)", wparam, wparam); + + // TODO: Add key to the queue as well? +} + +// Handle mouse button input event +static void HandleMouseButton(int button, char state) +{ + // Register current mouse button state + CORE.Input.Mouse.currentButtonState[button] = state; + CORE.Input.Touch.currentTouchState[button] = state; +} + +// Handle raw input event +static void HandleRawInput(LPARAM lparam) +{ + RAWINPUT input = { 0 }; + UINT inputSize = 0; + + if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, NULL, &inputSize, sizeof(RAWINPUTHEADER)) != 0) return; + if (inputSize > sizeof(input)) return; + + UINT size = GetRawInputData((HRAWINPUT)lparam, RID_INPUT, &input, &inputSize, sizeof(RAWINPUTHEADER)); + + if (size == (UINT)-1) TRACELOG(LOG_ERROR, "WIN32: Failed to get raw input data [ERROR: %lu]", GetLastError()); + + if (input.header.dwType != RIM_TYPEMOUSE) TRACELOG(LOG_ERROR, "WIN32: Unexpected WM_INPUT type %lu", input.header.dwType); + + if (input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) TRACELOG(LOG_ERROR, "TODO: handle absolute mouse inputs!"); + + if (input.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) TRACELOG(LOG_ERROR, "TODO: handle virtual desktop mouse inputs!"); + + // Trick to keep the mouse position at (0,0) and instead move + // the previous position so a proper mouse delta can still be retrieved + //CORE.Input.Mouse.previousPosition.x -= input.data.mouse.lLastX; + //CORE.Input.Mouse.previousPosition.y -= input.data.mouse.lLastY; + //if (CORE.Input.Mouse.currentPosition.x != 0) abort(); + //if (CORE.Input.Mouse.currentPosition.y != 0) abort(); +} + +// Handle window resizing event +static void HandleWindowResize(HWND hwnd, int *width, int *height) +{ + if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return; + + // Get client size (framebuffer inside the window) + RECT rect = { 0 }; + GetClientRect(hwnd, &rect); + SIZE clientSize = { rect.right, rect.bottom }; + + CORE.Window.currentFbo.width = (int)clientSize.cx; + CORE.Window.currentFbo.height = (int)clientSize.cy; + SetupViewport(clientSize.cx, clientSize.cy); + + CORE.Window.resizedLastFrame = true; + float dpiScale = ((float)GetDpiForWindow(hwnd))/96.0f; + bool highdpi = !!(CORE.Window.flags & FLAG_WINDOW_HIGHDPI); + unsigned int screenWidth = highdpi? (unsigned int)(((float)clientSize.cx)/dpiScale) : clientSize.cx; + unsigned int screenHeight = highdpi? (unsigned int)(((float)clientSize.cy)/dpiScale) : clientSize.cy; + CORE.Window.screen.width = screenWidth; + CORE.Window.screen.height = screenHeight; + + if (AdoptWindowResize(CORE.Window.flags)) + { + TRACELOG(LOG_DEBUG, "WIN32: WINDOW: Updating app size to [%ix%i] from window resize", screenWidth, screenHeight); + *width = screenWidth; + *height = screenHeight; + } + + CORE.Window.screenScale = MatrixScale( (float)CORE.Window.render.width/CORE.Window.screen.width, + (float)CORE.Window.render.height/CORE.Window.screen.height, 1.0f); + +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + swResize(clientSize.cx, clientSize.cy); +#endif +} + +// Update window style +static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags) +{ + DWORD current = STYLE_MASK_WRITABLE & MakeWindowStyle(CORE.Window.flags); + DWORD desired = STYLE_MASK_WRITABLE & MakeWindowStyle(desiredFlags); + + if (current != desired) + { + SetLastError(0); + DWORD previous = STYLE_MASK_WRITABLE & SetWindowLongPtrW(hwnd, GWL_STYLE, desired); + if (previous != current) + { + TRACELOG(LOG_ERROR, "WIN32: WINDOW: SetWindowLongPtr() returned writable flags 0x%x but expected 0x%x (diff=0x%x, error=%lu)", + previous, current, previous ^ current, GetLastError()); + } + + CheckFlags("UpdateWindowStyle", hwnd, desiredFlags, desired, STYLE_MASK_WRITABLE); + } + + // Minimized takes precedence over maximized + Mized currentMized = MIZED_NONE; + Mized desiredMized = MIZED_NONE; + if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) currentMized = MIZED_MIN; + else if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) currentMized = MIZED_MAX; + if (desiredFlags & FLAG_WINDOW_MINIMIZED) desiredMized = MIZED_MIN; + else if (desiredFlags & FLAG_WINDOW_MAXIMIZED) desiredMized = MIZED_MAX; + + if (currentMized != desiredMized) + { + switch (desiredMized) + { + case MIZED_NONE: ShowWindow(hwnd, SW_RESTORE); break; + case MIZED_MIN: ShowWindow(hwnd, SW_MINIMIZE); break; + case MIZED_MAX: ShowWindow(hwnd, SW_MAXIMIZE); break; + } + } +} + +// Sanitize flags +static unsigned SanitizeFlags(int mode, unsigned flags) +{ + if (flags & FLAG_WINDOW_MAXIMIZED) + { + if (flags & FLAG_BORDERLESS_WINDOWED_MODE) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Borderless windows mode overriding maximized window flag"); + flags &= ~FLAG_WINDOW_MAXIMIZED; + } + + if (~flags & FLAG_WINDOW_RESIZABLE) + { + if (!(CORE.Window.flags & FLAG_WINDOW_MAXIMIZED)) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot maximize a non-resizable window"); + flags &= ~FLAG_WINDOW_MAXIMIZED; + } + else if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot set window as non-resizable when maximized"); + flags |= FLAG_WINDOW_RESIZABLE; + } + } + else if (!(CORE.Window.flags & FLAG_WINDOW_MAXIMIZED)) + { + if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) + { + // Window needs to be unminimized before it can be maximized since minimizing takes precedence + flags &= ~FLAG_WINDOW_MINIMIZED; + } + else if ((flags & FLAG_WINDOW_MINIMIZED) && !(CORE.Window.flags & FLAG_WINDOW_MINIMIZED)) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot minimize and maximize a window in the same frame"); + flags &= ~FLAG_WINDOW_MINIMIZED; + flags &= ~FLAG_WINDOW_MAXIMIZED; + } + } + } + + if (mode == 1) + { + if ((flags & FLAG_MSAA_4X_HINT) && (!(CORE.Window.flags & FLAG_MSAA_4X_HINT))) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: MSAA can only be configured before window initialization"); + flags &= ~FLAG_MSAA_4X_HINT; + } + } + + return flags; +} + +// All window state changes from raylib flags go through this function. It performs +// whatever operations are needed to update the window state to match the desired flags +// In most cases this function should not update CORE.Window.flags directly, instead, +// the window itself should update CORE.Window.flags in response to actual state changes +// This means that CORE.Window.flags should always represent the actual state of the +// window. This function will continue to perform these update operations so long as +// the state continues to change +// +// This design takes care of many odd corner cases. For example, in case of restoring +// a window that was previously maximized AND minimized and those two flags need to be removed, +// ShowWindow with SW_RESTORE twice need to be actually calleed. Another example is +// wheen having a maximized window, if the undecorated flag is modified then the window style +// needs to be updated, but updating the style would mean the window size would change +// causing the window to lose its Maximized state which would mean the window size +// needs to be updated, followed by the update of window style, a second time, to restore that maximized +// state. This implementation is able to handle any/all of these special situations with a +// retry loop that continues until either the desired state is reached or the state stops changing +static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height) +{ + // Flags that apply immediately without needing any operations + CORE.Window.flags |= (desiredFlags & FLAG_MASK_NO_UPDATE); + + int vsync = (desiredFlags & FLAG_VSYNC_HINT)? 1 : 0; + if (wglSwapIntervalEXT) + { + wglSwapIntervalEXT(vsync); + if (vsync) CORE.Window.flags |= FLAG_VSYNC_HINT; + else CORE.Window.flags &= ~FLAG_VSYNC_HINT; + } + + // TODO: Review all this code... + DWORD previousStyle = 0; + for (unsigned attempt = 1; ; attempt++) + { + CheckFlags("UpdateFlags", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), STYLE_MASK_ALL); + + bool windowSizeUpdated = false; + if (MakeWindowStyle(CORE.Window.flags) == MakeWindowStyle(desiredFlags)) + { + windowSizeUpdated = UpdateWindowSize(1, hwnd, width, height, desiredFlags); + if ((FLAG_MASK_REQUIRED & desiredFlags) == (FLAG_MASK_REQUIRED & CORE.Window.flags)) break; + } + + + if ((attempt > 1) && (previousStyle == MakeWindowStyle(CORE.Window.flags)) && !windowSizeUpdated) + { + TRACELOG(LOG_ERROR, "WIN32: WINDOW: UpdateFlags() failed after %u attempt(s) wanted 0x%x but is 0x%x (diff=0x%x)", + attempt, desiredFlags, CORE.Window.flags, desiredFlags ^ CORE.Window.flags); + } + + previousStyle = MakeWindowStyle(CORE.Window.flags); + UpdateWindowStyle(hwnd, desiredFlags); + } +} + +// Check if OpenGL extension is available +static bool IsWglExtensionAvailable(HDC hdc, const char *extension) +{ + bool result = false; + + if (wglGetExtensionsStringARB != NULL) + { + const char *extList = wglGetExtensionsStringARB(hdc); + if (extList != NULL) + { + // Simple substring search (could use strtok or strstr) + if (strstr(extList, extension) != NULL) result = true; + } + } + + return result; +} diff --git a/src/libraries/raylib/raylib/src/platforms/rcore_drm.c b/src/libraries/raylib/raylib/src/platforms/rcore_drm.c new file mode 100644 index 0000000..9fa2ea0 --- /dev/null +++ b/src/libraries/raylib/raylib/src/platforms/rcore_drm.c @@ -0,0 +1,2754 @@ +/********************************************************************************************** +* +* rcore_drm - Functions to manage window, graphics device and inputs +* +* PLATFORM: DRM +* - Raspberry Pi 0-5 (DRM/KMS) +* - Linux DRM subsystem (KMS mode) +* +* LIMITATIONS: +* - Most of the window/monitor functions are not implemented (not required) +* +* POSSIBLE IMPROVEMENTS: +* - Improvement 01 +* - Improvement 02 +* +* CONFIGURATION: +* #define SUPPORT_SSH_KEYBOARD_RPI (Raspberry Pi only) +* Reconfigure standard input to receive key inputs, works with SSH connection +* WARNING: Reconfiguring standard input could lead to undesired effects, like breaking other +* running processes orblocking the device if not restored properly. Use with care +* +* DEPENDENCIES: +* - DRM and GLM: System libraries for display initialization and configuration +* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include // POSIX file control definitions - open(), creat(), fcntl() +#include // POSIX standard function definitions - read(), close(), STDIN_FILENO +#include // POSIX terminal control definitions - tcgetattr(), tcsetattr() +#include // POSIX threads management (inputs reading) +#include // POSIX directory browsing +#include // INT_MAX + +#include // Required for: ioctl() - UNIX System call for device-specific input/output operations +#include // Linux: KDSKBMODE, K_MEDIUMRAM constants definition +#include // Linux: Keycodes constants definition (KEY_A, ...) +#include // Linux: Joystick support library + +// WARNING: Both 'linux/input.h' and 'raylib.h' define KEY_F12 +// To avoid conflict with the capturing code in rcore.c, undefine the macro KEY_F12, +// so the enum KEY_F12 from raylib is used +#undef KEY_F12 + +#include // Direct Rendering Manager user-level library interface +#include // Direct Rendering Manager mode setting (KMS) interface + +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) + #include // Generic Buffer Management (native platform for EGL on DRM) + #include "EGL/egl.h" // Native platform windowing system interface + #include "EGL/eglext.h" // EGL extensions +#else + #include // For mmap when copying to the dumb buffer + #include // For the conversion of certain error messages +#endif + +// NOTE: DRM cache enables triple buffered DRM caching +#if defined(SUPPORT_DRM_CACHE) + #include // Required for: drmHandleEvent() poll + #include // Required for: EBUSY, EAGAIN + + // NOTE: Some drivers rotate more buffers than others, mesa uses 3 but the + // ARM Mali blob on RK3326 uses 4. A buffer beyond this count can not get a + // framebuffer and the display stops updating for good, so this must not be + // lower than any driver in use + #define MAX_DRM_CACHED_BUFFERS 4 +#endif + +#ifndef EGL_OPENGL_ES3_BIT + #define EGL_OPENGL_ES3_BIT 0x40 +#endif + +#ifndef EGL_PLATFORM_GBM_KHR + #define EGL_PLATFORM_GBM_KHR 0x31D7 +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event number + +#define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events + +// Actually biggest key is KEY_CNT but only mapping keys up to KEY_ALS_TOGGLE +#define KEYMAP_SIZE KEY_ALS_TOGGLE + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct { + // Display data + int fd; // File descriptor for /dev/dri/... + drmModeConnector *connector; // Direct Rendering Manager (DRM) mode connector + drmModeCrtc *crtc; // CRT Controller + int modeIndex; // Index of the used mode of connector->modes + uint32_t prevFB; // Previous DRM framebufer (during frame swapping) +#if defined(SUPPORT_DRM_CACHE) + bool fbCacheFullReported; // Framebuffer cache exhaustion already reported +#endif + +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) + struct gbm_device *gbmDevice; // GBM device + struct gbm_surface *gbmSurface; // GBM surface + struct gbm_bo *prevBO; // Previous GBM buffer object (during frame swapping) + EGLDisplay device; // Native display device (physical screen connection) + EGLSurface surface; // Surface to draw on, framebuffers (connected to context) + EGLContext context; // Graphic context, mode in which drawing can be done + EGLConfig config; // Graphic config +#else + uint32_t prevDumbHandle; // Handle to the previous dumb buffer (during frame swapping) +#endif + + // Keyboard data + int defaultKeyboardMode; // Default keyboard mode + bool eventKeyboardMode; // Keyboard in event mode + int defaultFileFlags; // Default IO file flags + struct termios defaultSettings; // Default keyboard settings + int keyboardFd; // File descriptor for the evdev keyboard + + // Mouse data + Vector2 eventWheelMove; // Registers the event mouse wheel variation + // NOTE: currentButtonState[] can't be written directly due to multithreading, app could miss the update + char currentButtonStateEvdev[MAX_MOUSE_BUTTONS]; // Holds the new mouse state for the next polling event to grab + bool cursorRelative; // Relative cursor mode + int mouseFd; // File descriptor for the evdev mouse/touch/gestures + bool mouseIsTouch; // Check if the current mouse device is actually a touchscreen + Rectangle absRange; // Range of values for absolute pointing devices (touchscreens) + int touchSlot; // Hold the touch slot number of the currently being sent multitouch block + bool touchActive[MAX_TOUCH_POINTS]; // Track which touch points are currently active + Vector2 touchPosition[MAX_TOUCH_POINTS]; // Track touch positions for each slot + int touchId[MAX_TOUCH_POINTS]; // Track touch IDs for each slot + + // Gamepad data + int gamepadStreamFd[MAX_GAMEPADS]; // Gamepad device file descriptor + int gamepadAbsAxisRange[MAX_GAMEPADS][MAX_GAMEPAD_AXES][2]; // [0] = min, [1] = range value of the axes + int gamepadAbsAxisMap[MAX_GAMEPADS][ABS_CNT]; // Maps the axes gamepads from the evdev api to a sequential one + int gamepadCount; // The number of gamepads registered +} PlatformData; + +#if defined(SUPPORT_DRM_CACHE) +typedef struct { + struct gbm_bo *bo; // Graphics buffer object + uint32_t fbId; // DRM framebuffer ID +} FramebufferCache; + +static FramebufferCache fbCache[MAX_DRM_CACHED_BUFFERS] = { 0 }; +static volatile int fbCacheCount = 0; +static volatile bool pendingFlip = false; +static bool crtcSet = false; +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context + +static PlatformData platform = { 0 }; // Platform specific data + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- + +// NOTE: The complete evdev EV_KEY list can be found at /usr/include/linux/input-event-codes.h +// TODO: Complete the LUT with all unicode decimal values +// TODO: Replace this with a keymap from the X11 to get the correct regional map for the keyboard: +// Currently non US keyboards will have the wrong mapping for some keys +// NOTE: Replacing this with the keymap from X11 would probably be useless, as people use the drm +// backend to *avoid* X11 +static const int evkeyToUnicodeLUT[] = { + 0, 27, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 8, 0, 113, 119, 101, 114, + 116, 121, 117, 105, 111, 112, 0, 0, 13, 0, 97, 115, 100, 102, 103, 104, 106, 107, 108, 59, + 39, 96, 0, 92, 122, 120, 99, 118, 98, 110, 109, 44, 46, 47, 0, 0, 0, 32 + // LUT currently incomplete, only mapped the most essential keys +}; + +// This is the map used to map any keycode returned from linux to a raylib code from 'raylib.h' +// NOTE: Use short here to save a little memory +static const short linuxToRaylibMap[KEYMAP_SIZE] = { + // Don't map with designated initialization, + // it will geenrate many naming conflicts + 0, 256, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 48, 45, 61, 259, 258, + 81, 87, 69, 82, 84, 89, 85, 73, + 79, 80, 91, 93, 257, 341, 65, 83, + 68, 70, 71, 72, 74, 75, 76, 59, + 39, 96, 340, 92, 90, 88, 67, 86, + 66, 78, 77, 44, 46, 47, 344, 332, + 342, 32, 280, 290, 291, 292, 293, 294, + 295, 296, 297, 298, 299, 282, 281, 327, + 328, 329, 333, 324, 325, 326, 334, 321, + 322, 323, 320, 330, 0, 85, 86, 300, + 301, 89, 90, 91, 92, 93, 94, 95, + 335, 345, 331, 283, 346, 101, 268, 265, + 266, 263, 262, 269, 264, 267, 260, 261, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 347, 127, + 128, 129, 130, 131, 132, 133, 134, 135, + 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, + 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 0, 0, 0, 0, 0, + 200, 201, 202, 203, 204, 205, 206, 207, + 208, 209, 210, 211, 212, 213, 214, 215, + 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, + 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, + 248, 0, 0, 0, 0, 0, 0, 0, + + // Gamepads are mapped according to: + // REF: https://www.kernel.org/doc/html/next/input/gamepad.html + // Those mappings are standardized, but that doesn't mean people follow + // the standards, so this is more of an approximation + [BTN_DPAD_UP] = GAMEPAD_BUTTON_LEFT_FACE_UP, + [BTN_DPAD_RIGHT] = GAMEPAD_BUTTON_LEFT_FACE_RIGHT, + [BTN_DPAD_DOWN] = GAMEPAD_BUTTON_LEFT_FACE_DOWN, + [BTN_DPAD_LEFT] = GAMEPAD_BUTTON_LEFT_FACE_LEFT, + [BTN_Y] = GAMEPAD_BUTTON_RIGHT_FACE_UP, + [BTN_B] = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, + [BTN_A] = GAMEPAD_BUTTON_RIGHT_FACE_DOWN, + [BTN_X] = GAMEPAD_BUTTON_RIGHT_FACE_LEFT, + [BTN_TL] = GAMEPAD_BUTTON_LEFT_TRIGGER_1, + [BTN_TL2] = GAMEPAD_BUTTON_LEFT_TRIGGER_2, + [BTN_TR] = GAMEPAD_BUTTON_RIGHT_TRIGGER_1, + [BTN_TR2] = GAMEPAD_BUTTON_RIGHT_TRIGGER_2, + [BTN_SELECT] = GAMEPAD_BUTTON_MIDDLE_LEFT, + [BTN_MODE] = GAMEPAD_BUTTON_MIDDLE, + [BTN_START] = GAMEPAD_BUTTON_MIDDLE_RIGHT, + [BTN_THUMBL] = GAMEPAD_BUTTON_LEFT_THUMB, + [BTN_THUMBR] = GAMEPAD_BUTTON_RIGHT_THUMB, +}; + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +void ClosePlatform(void); // Close platform + +#if SUPPORT_SSH_KEYBOARD_RPI +static void InitKeyboard(void); // Initialize raw keyboard system +static void RestoreKeyboard(void); // Restore keyboard system +static void ProcessKeyboard(void); // Process keyboard events +#endif + +// Input management functions +static void InitEvdevInput(void); // Initialize evdev inputs +static void ConfigureEvdevDevice(char *device); // Identifies a input device and configures it for use if appropriate +static void PollKeyboardEvents(void); // Process evdev keyboard events +static void PollGamepadEvents(void); // Process evdev gamepad events +static void PollMouseEvents(void); // Process evdev mouse events + +// Not used by software rendering. +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) +static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode); // Search matching DRM mode in connector's mode list +static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search exactly matching DRM connector mode in connector's list +static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search the nearest matching DRM connector mode in connector's list +#endif + +static void SetupFramebuffer(int width, int height); // Setup main framebuffer (required by InitPlatform()) + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +// NOTE: By default, if KEY_ESCAPE pressed +bool WindowShouldClose(void) +{ + if (CORE.Window.ready) return CORE.Window.shouldClose; + else return true; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + TRACELOG(LOG_WARNING, "ToggleFullscreen() not available on target platform"); +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + TRACELOG(LOG_WARNING, "ToggleBorderlessWindowed() not available on target platform"); +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MaximizeWindow() not available on target platform"); +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform"); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + TRACELOG(LOG_WARNING, "RestoreWindow() not available on target platform"); +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + TRACELOG(LOG_WARNING, "SetWindowState() not available on target platform"); +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + TRACELOG(LOG_WARNING, "ClearWindowState() not available on target platform"); +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform"); +} + +// Set icon for window +void SetWindowIcons(Image *images, int count) +{ + TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform"); +} + +// Set title for window +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform"); +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform"); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + TRACELOG(LOG_WARNING, "SetWindowSize() not available on target platform"); +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + TRACELOG(LOG_WARNING, "SetWindowOpacity() not available on target platform"); +} + +// Set window focused +void SetWindowFocused(void) +{ + TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform"); +} + +// Get native window handle +void *GetWindowHandle(void) +{ + TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform"); + return NULL; +} + +// Get number of monitors +int GetMonitorCount(void) +{ + TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform"); + return 1; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + TRACELOG(LOG_WARNING, "GetCurrentMonitor() not implemented on target platform"); + return 0; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + int width = 0; + + if (monitor != 0) + { + TRACELOG(LOG_WARNING, "GetMonitorWidth() implemented for first monitor only"); + } + else if ((platform.connector) && (platform.modeIndex >= 0)) + { + width = platform.connector->modes[platform.modeIndex].hdisplay; + } + + return width; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + int height = 0; + + if (monitor != 0) + { + TRACELOG(LOG_WARNING, "GetMonitorHeight() implemented for first monitor only"); + } + else if ((platform.connector) && (platform.modeIndex >= 0)) + { + height = platform.connector->modes[platform.modeIndex].vdisplay; + } + + return height; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + int physicalWidth = 0; + + if (monitor != 0) + { + TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth() implemented for first monitor only"); + } + else if ((platform.connector) && (platform.modeIndex >= 0)) + { + physicalWidth = platform.connector->mmWidth; + } + + return physicalWidth; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + int physicalHeight = 0; + + if (monitor != 0) + { + TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight() implemented for first monitor only"); + } + else if ((platform.connector) && (platform.modeIndex >= 0)) + { + physicalHeight = platform.connector->mmHeight; + } + + return physicalHeight; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + int refresh = 0; + + if ((platform.connector) && (platform.modeIndex >= 0)) + { + refresh = platform.connector->modes[platform.modeIndex].vrefresh; + } + + return refresh; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ + const char *name = ""; + + if (monitor != 0) + { + TRACELOG(LOG_WARNING, "GetMonitorName() implemented for first monitor only"); + } + else if ((platform.connector) && (platform.modeIndex >= 0)) + { + name = platform.connector->modes[platform.modeIndex].name; + } + + return name; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + return (Vector2){ 0, 0 }; +} + +// Get window scale DPI factor for current monitor +Vector2 GetWindowScaleDPI(void) +{ + return (Vector2){ 1.0f, 1.0f }; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + TRACELOG(LOG_WARNING, "SetClipboardText() not implemented on target platform"); +} + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ + TRACELOG(LOG_WARNING, "GetClipboardText() not implemented on target platform"); + return NULL; +} + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + + TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform"); + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + CORE.Input.Mouse.cursorHidden = false; +} + +// Hide mouse cursor +void HideCursor(void) +{ + CORE.Input.Mouse.cursorHidden = true; +} + +// Enable cursor (unlock cursor) +void EnableCursor(void) +{ + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + platform.cursorRelative = false; + CORE.Input.Mouse.cursorLocked = false; +} + +// Disable cursor (lock cursor) +void DisableCursor(void) +{ + // Set cursor position in the middle + SetMousePosition(0, 0); + + platform.cursorRelative = true; + CORE.Input.Mouse.cursorLocked = true; +} + +#if defined(SUPPORT_DRM_CACHE) + +// Destroy cached framebuffer callback, set by gbm_bo_set_user_data() +static void DestroyFrameBufferCallback(struct gbm_bo *bo, void *data) +{ + uint32_t fbId = (uintptr_t)data; + + // Remove from cache + for (int i = 0; i < fbCacheCount; i++) + { + if (fbCache[i].bo == bo) + { + TRACELOG(LOG_INFO, "DISPLAY: DRM: Framebuffer removed [%u]", (uintptr_t)fbId); + drmModeRmFB(platform.fd, fbCache[i].fbId); // Release DRM FB + + // Shift remaining entries + for (int j = i; j < fbCacheCount - 1; j++) fbCache[j] = fbCache[j + 1]; + + fbCacheCount--; + break; + } + } +} + +// Create or retrieve cached DRM FB for BO +static uint32_t GetOrCreateFbForBo(struct gbm_bo *bo) +{ + // Try to find existing cache entry + for (int i = 0; i < fbCacheCount; i++) + { + if (fbCache[i].bo == bo) return fbCache[i].fbId; + } + + // Create new entry if cache not full + if (fbCacheCount >= MAX_DRM_CACHED_BUFFERS) + { + // NOTE: Once this happens no page flip is ever scheduled again and the + // screen freezes while the game keeps running, so say it out loud once + if (!platform.fbCacheFullReported) + { + platform.fbCacheFullReported = true; + TRACELOG(LOG_WARNING, "DISPLAY: DRM: Framebuffer cache full (%i buffers), display will stop updating", MAX_DRM_CACHED_BUFFERS); + } + + return 0; // FB cache full + } + + uint32_t handle = gbm_bo_get_handle(bo).u32; + uint32_t stride = gbm_bo_get_stride(bo); + uint32_t width = gbm_bo_get_width(bo); + uint32_t height = gbm_bo_get_height(bo); + + uint32_t fbId = 0; + if (drmModeAddFB(platform.fd, width, height, 24, 32, stride, handle, &fbId)) return 0; + + // Store in cache + fbCache[fbCacheCount] = (FramebufferCache){ .bo = bo, .fbId = fbId }; + fbCacheCount++; + + // Set destroy callback to auto-cleanup + gbm_bo_set_user_data(bo, (void *)(uintptr_t)fbId, DestroyFrameBufferCallback); + + TRACELOG(LOG_INFO, "DISPLAY: DRM: Added new buffer object [%u]" , (uintptr_t)fbId); + + return fbId; +} + +// Renders a blank frame to allocate initial buffers +// TODO: WARNING: Platform backend should not include OpenGL code +void RenderBlankFrame() +{ + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + eglSwapBuffers(platform.device, platform.surface); + glFinish(); // Ensure the buffer is processed +} + +// Initialize with first buffer only +int InitSwapScreenBuffer() +{ + if (!platform.gbmSurface || (platform.fd < 0)) + { + TRACELOG(LOG_ERROR, "DISPLAY: DRM: Swap buffers can not be initialized"); + return -1; + } + + // Render a blank frame to allocate buffers + RenderBlankFrame(); + + // Get first buffer + struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface); + if (!bo) + { + TRACELOG(LOG_ERROR, "DISPLAY: DRM: Failed to lock initial swap buffer"); + return -1; + } + + // Create FB for first buffer + uint32_t fbId = GetOrCreateFbForBo(bo); + if (!fbId) + { + gbm_surface_release_buffer(platform.gbmSurface, bo); + return -1; + } + + // Initial CRTC setup + if (drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fbId, 0, 0, &platform.connector->connector_id, 1, &platform.connector->modes[platform.modeIndex])) + { + TRACELOG(LOG_ERROR, "DISPLAY: DRM: Failed to initialize CRTC setup. ERROR: %s", strerror(errno)); + gbm_surface_release_buffer(platform.gbmSurface, bo); + return -1; + } + + // Keep first buffer locked until flipped + platform.prevBO = bo; + crtcSet = true; + + return 0; +} + +// Static page flip handler +// NOTE: Called once the drmModePageFlip() finished from the drmHandleEvent() context +static void PageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) +{ + // Unused inputs + (void)fd; + (void)frame; + (void)sec; + (void)usec; + + pendingFlip = false; + struct gbm_bo *boToRelease = (struct gbm_bo *)data; + + // Buffers are released after the flip completes (via page_flip_handler), ensuring they're no longer in use + // Prevents the GPU from writing to a buffer being scanned out + if (boToRelease) gbm_surface_release_buffer(platform.gbmSurface, boToRelease); +} + +// Swap implementation with proper caching +void SwapScreenBuffer() +{ + if (!crtcSet || !platform.gbmSurface) return; + + static int loopCnt = 0; + static int errCnt[5] = { 0 }; + loopCnt++; + + // Call this only, if pendingFlip is not set + eglSwapBuffers(platform.device, platform.surface); + + // Process pending events non-blocking + drmEventContext evctx = { + .version = DRM_EVENT_CONTEXT_VERSION, + .page_flip_handler = PageFlipHandler + }; + + struct pollfd pfd = { .fd = platform.fd, .events = POLLIN }; + + // Polling for event for 0ms + while (poll(&pfd, 1, 0) > 0) drmHandleEvent(platform.fd, &evctx); + + // Skip if previous flip pending + if (pendingFlip) + { + errCnt[0]++; // Skip frame: flip pending + return; + } + + // Get new front buffer + struct gbm_bo *nextBO = gbm_surface_lock_front_buffer(platform.gbmSurface); + if (!nextBO) // Failed to lock front buffer + { + errCnt[1]++; + return; + } + + // Get FB ID (creates new one if needed) + uint32_t fbId = GetOrCreateFbForBo(nextBO); + if (!fbId) + { + gbm_surface_release_buffer(platform.gbmSurface, nextBO); + errCnt[2]++; + return; + } + + // Attempt page flip + // NOTE: rmModePageFlip() schedules a buffer-flip for the next vblank and then notifies us about it + // It takes a CRTC-id, fb-id and an arbitrary data-pointer and then schedules the page-flip + // This is fully asynchronous and when the page-flip happens, the DRM-fd will become readable and drmHandleEvent() can be called + // This will read all vblank/page-flip events and call our modeset_page_flip_event() callback with the data-pointer passed to drmModePageFlip() + // Simply call modeset_draw_dev() then so the next frame is rendered... returns immediately + if (drmModePageFlip(platform.fd, platform.crtc->crtc_id, fbId, DRM_MODE_PAGE_FLIP_EVENT, platform.prevBO)) + { + if (errno == EBUSY) errCnt[3]++; // Display busy - skip flip + else errCnt[4]++; // Page flip failed + + gbm_surface_release_buffer(platform.gbmSurface, nextBO); + return; + } + + // Success: update state + pendingFlip = true; + platform.prevBO = nextBO; + +/* + // Some benchmarking code + if (loopCnt >= 600) + { + TRACELOG(LOG_INFO, "DRM: Error counters: %d, %d, %d, %d, %d, %d", errCnt[0], errCnt[1], errCnt[2], errCnt[3], errCnt[4], loopCnt); + for (int i = 0; i < 5; i++) errCnt[i] = 0; + loopCnt = 0; + } +*/ +} + +#else // !SUPPORT_DRM_CACHE + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) + // Hardware rendering buffer swap with EGL + eglSwapBuffers(platform.device, platform.surface); + + if (!platform.gbmSurface || (-1 == platform.fd) || !platform.connector || !platform.crtc) TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap"); + + struct gbm_bo *bo = gbm_surface_lock_front_buffer(platform.gbmSurface); + if (!bo) TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer"); + + uint32_t fb = 0; + int result = drmModeAddFB(platform.fd, platform.connector->modes[platform.modeIndex].hdisplay, platform.connector->modes[platform.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb); + if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result); + + result = drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, fb, 0, 0, &platform.connector->connector_id, 1, &platform.connector->modes[platform.modeIndex]); + if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result); + + if (platform.prevFB) + { + result = drmModeRmFB(platform.fd, platform.prevFB); + if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result); + } + + platform.prevFB = fb; + + if (platform.prevBO) gbm_surface_release_buffer(platform.gbmSurface, platform.prevBO); + + platform.prevBO = bo; +#else + // Software rendering buffer swap + if ((platform.fd == -1) || !platform.connector || (platform.modeIndex < 0)) + { + TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap"); + return; + } + + // Retrieving the dimensions of the display mode used + drmModeModeInfo *mode = &platform.connector->modes[platform.modeIndex]; + uint32_t width = mode->hdisplay; + uint32_t height = mode->vdisplay; + + // Dumb buffers use a fixed format based on bpp + const uint32_t bpp = 32; // 32 bits per pixel (XRGB8888 format) + const uint32_t depth = 24; // Color depth, here only 24 bits, alpha is not used + + // Create a dumb buffer for software rendering + struct drm_mode_create_dumb creq = { 0 }; + creq.width = width; + creq.height = height; + creq.bpp = bpp; + + int result = drmIoctl(platform.fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); + if (result < 0) + { + TRACELOG(LOG_ERROR, "DISPLAY: Failed to create dumb buffer: %s", strerror(errno)); + return; + } + + // Create framebuffer with the correct format + uint32_t fb = 0; + result = drmModeAddFB(platform.fd, width, height, depth, bpp, creq.pitch, creq.handle, &fb); + if (result != 0) + { + TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d (%s)", result, strerror(errno)); + struct drm_mode_destroy_dumb dreq = { 0 }; + dreq.handle = creq.handle; + drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); + return; + } + + // Map the dumb buffer to copy our software rendered buffer + struct drm_mode_map_dumb mreq = { 0 }; + mreq.handle = creq.handle; + result = drmIoctl(platform.fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); + if (result != 0) + { + TRACELOG(LOG_ERROR, "DISPLAY: Failed to map dumb buffer: %s", strerror(errno)); + drmModeRmFB(platform.fd, fb); + struct drm_mode_destroy_dumb dreq = { 0 }; + dreq.handle = creq.handle; + drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); + return; + } + + // Map the buffer into userspace + void *dumbBuffer = mmap(0, creq.size, PROT_READ | PROT_WRITE, MAP_SHARED, platform.fd, mreq.offset); + if (dumbBuffer == MAP_FAILED) + { + TRACELOG(LOG_ERROR, "DISPLAY: Failed to mmap dumb buffer: %s", strerror(errno)); + drmModeRmFB(platform.fd, fb); + struct drm_mode_destroy_dumb dreq = { 0 }; + dreq.handle = creq.handle; + drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); + return; + } + + // Copy the software rendered buffer to the dumb buffer with scaling if needed + // NOTE: RLSW will make a simple copy if the dimensions match + swBlitPixels(0, 0, width, height, 0, 0, width, height, SW_RGBA, SW_UNSIGNED_BYTE, dumbBuffer); + + // Unmap the buffer + munmap(dumbBuffer, creq.size); + + // Find a CRTC compatible with the connector + uint32_t crtcId = 0; + if (platform.crtc) crtcId = platform.crtc->crtc_id; + else + { + // Find a CRTC that's compatible with this connector + drmModeRes *res = drmModeGetResources(platform.fd); + if (!res) + { + TRACELOG(LOG_ERROR, "DISPLAY: Failed to get DRM resources"); + drmModeRmFB(platform.fd, fb); + struct drm_mode_destroy_dumb dreq = { 0 }; + dreq.handle = creq.handle; + drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); + return; + } + + // Check which CRTCs are compatible with this connector + drmModeEncoder *encoder = NULL; + if (platform.connector->encoder_id) encoder = drmModeGetEncoder(platform.fd, platform.connector->encoder_id); + + if (encoder && encoder->crtc_id) + { + crtcId = encoder->crtc_id; + platform.crtc = drmModeGetCrtc(platform.fd, crtcId); + } + else + { + // Find a free CRTC + for (int i = 0; i < res->count_crtcs; i++) + { + drmModeCrtc *crtc = drmModeGetCrtc(platform.fd, res->crtcs[i]); + if (crtc && !crtc->buffer_id) // CRTC is free + { + crtcId = res->crtcs[i]; + if (platform.crtc) drmModeFreeCrtc(platform.crtc); + platform.crtc = crtc; + break; + } + + if (crtc) drmModeFreeCrtc(crtc); + } + } + + if (encoder) drmModeFreeEncoder(encoder); + drmModeFreeResources(res); + + if (!crtcId) + { + TRACELOG(LOG_ERROR, "DISPLAY: No compatible CRTC found"); + drmModeRmFB(platform.fd, fb); + struct drm_mode_destroy_dumb dreq = { 0 }; + dreq.handle = creq.handle; + drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); + return; + } + } + + // Set CRTC with better error handling + result = drmModeSetCrtc(platform.fd, crtcId, fb, 0, 0, &platform.connector->connector_id, 1, mode); + if (result != 0) + { + TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d (%s)", result, strerror(errno)); + TRACELOG(LOG_ERROR, "DISPLAY: CRTC ID: %u, FB ID: %u, Connector ID: %u", crtcId, fb, platform.connector->connector_id); + TRACELOG(LOG_ERROR, "DISPLAY: Mode: %dx%d@%d", mode->hdisplay, mode->vdisplay, mode->vrefresh); + + drmModeRmFB(platform.fd, fb); + struct drm_mode_destroy_dumb dreq = { 0 }; + dreq.handle = creq.handle; + drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); + return; + } + + // Clean up previous framebuffer + if (platform.prevFB) + { + result = drmModeRmFB(platform.fd, platform.prevFB); + if (result != 0) TRACELOG(LOG_WARNING, "DISPLAY: drmModeRmFB() failed with result: %d", result); + } + + platform.prevFB = fb; + + // Clean up previous dumb buffer + if (platform.prevDumbHandle) + { + struct drm_mode_destroy_dumb dreq = { 0 }; + dreq.handle = platform.prevDumbHandle; + drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); + } + + platform.prevDumbHandle = creq.handle; +#endif +} +#endif // SUPPORT_DRM_CACHE + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds since InitTimer() +double GetTime(void) +{ + double time = 0.0; + struct timespec ts = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long long nanoSeconds = (unsigned long long)ts.tv_sec*1000000000LLU + (unsigned long long)ts.tv_nsec; + + time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() + + return time; +} + +// Open URL with default system browser (if available) +// WARNING: This function is only safe to use if you control the URL given, +// a user could craft a malicious string to perform and undesired action +void OpenURL(const char *url) +{ + TRACELOG(LOG_WARNING, "OpenURL() not implemented on target platform"); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + TRACELOG(LOG_WARNING, "SetGamepadMappings() not implemented on target platform"); + return 0; +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + TRACELOG(LOG_WARNING, "SetGamepadVibration() not implemented on target platform"); +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; +} + +// Set mouse cursor +void SetMouseCursor(int cursor) +{ + TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform"); +} + +// Get physical key name +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + +// Register all input events +void PollInputEvents(void) +{ +#if SUPPORT_GESTURES_SYSTEM + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN + //CORE.Input.Gamepad.axisCount = 0; + + // Register previous keys states + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) + { + CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + PollKeyboardEvents(); + +#if SUPPORT_SSH_KEYBOARD_RPI + // NOTE: Keyboard reading could be done using input_event(s) or read from stdin, both methods are used here + // stdin reading is still used for legacy purposes, it allows keyboard input trough SSH console + if (!platform.eventKeyboardMode) ProcessKeyboard(); +#endif + + // Check exit key + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; + + // Register previous mouse position + if (platform.cursorRelative) CORE.Input.Mouse.currentPosition = (Vector2){ 0.0f, 0.0f }; + else CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + + // Register previous mouse states + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = platform.eventWheelMove; + platform.eventWheelMove = (Vector2){ 0.0f, 0.0f }; + + for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) + { + CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + CORE.Input.Mouse.currentButtonState[i] = platform.currentButtonStateEvdev[i]; + CORE.Input.Touch.currentTouchState[i] = platform.currentButtonStateEvdev[i]; + } + + // Register gamepads buttons events + PollGamepadEvents(); + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Map touch position to mouse position for convenience + // NOTE: For DRM touchscreen devices, this mapping is disabled to avoid false touch detection + // CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + + // Handle the mouse/touch/gestures events + PollMouseEvents(); +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + platform.fd = -1; + platform.connector = NULL; + platform.modeIndex = -1; + platform.crtc = NULL; + platform.prevFB = 0; + +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) + platform.gbmDevice = NULL; + platform.gbmSurface = NULL; + platform.prevBO = NULL; +#else + platform.prevDumbHandle = 0; +#endif + + // Initialize graphic device: display/window and graphic context + //---------------------------------------------------------------------------- + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + +#if defined(DEFAULT_GRAPHIC_DEVICE_DRM) + platform.fd = open(DEFAULT_GRAPHIC_DEVICE_DRM, O_RDWR); + if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: Default graphic device DRM opened successfully"); +#else + TRACELOG(LOG_WARNING, "DISPLAY: No graphic card set, trying platform-gpu-card"); + platform.fd = open("/dev/dri/by-path/platform-gpu-card", O_RDWR); // VideoCore VI (Raspberry Pi 4) + if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: platform-gpu-card opened successfully"); + + drmModeRes *res = NULL; + if ((platform.fd == -1) || ((res = drmModeGetResources(platform.fd)) == NULL)) + { + if (platform.fd != -1) close(platform.fd); + TRACELOG(LOG_WARNING, "DISPLAY: Failed to open platform-gpu-card, trying card1"); + platform.fd = open("/dev/dri/card1", O_RDWR); // Other Embedded + if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: card1 opened successfully"); + } + + if ((platform.fd == -1) || ((res = drmModeGetResources(platform.fd)) == NULL)) + { + if (platform.fd != -1) close(platform.fd); + TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card1, trying card0"); + platform.fd = open("/dev/dri/card0", O_RDWR); // VideoCore IV (Raspberry Pi 1-3) + if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: card0 opened successfully"); + } + + if ((platform.fd == -1) || ((res = drmModeGetResources(platform.fd)) == NULL)) + { + if (platform.fd != -1) close(platform.fd); + TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card0, trying card2"); + platform.fd = open("/dev/dri/card2", O_RDWR); + if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: card2 opened successfully"); + } +#endif + + if (platform.fd == -1) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card"); + return -1; + } + + if (!res) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed get DRM resources"); + close(platform.fd); + return -1; + } + + TRACELOG(LOG_TRACE, "DISPLAY: Connectors found: %i", res->count_connectors); + + // Connector detection + for (size_t i = 0; i < res->count_connectors; i++) + { + TRACELOG(LOG_TRACE, "DISPLAY: Connector index %i", i); + + drmModeConnector *con = drmModeGetConnector(platform.fd, res->connectors[i]); + if (!con) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get connector %i", i); + continue; + } + + TRACELOG(LOG_TRACE, "DISPLAY: Connector %i modes detected: %i", i, con->count_modes); + TRACELOG(LOG_TRACE, "DISPLAY: Connector %i status: %s", i, + (con->connection == DRM_MODE_CONNECTED)? "CONNECTED" : + (con->connection == DRM_MODE_DISCONNECTED)? "DISCONNECTED" : + (con->connection == DRM_MODE_UNKNOWNCONNECTION)? "UNKNOWN" : "OTHER"); + + // In certain cases the status of the conneciton is reported as UKNOWN, but it is still connected + // This might be a hardware or software limitation like on Raspberry Pi Zero with composite output + // WARNING: Accept CONNECTED, UNKNOWN and even those without encoder_id connectors for software mode + if (((con->connection == DRM_MODE_CONNECTED) || (con->connection == DRM_MODE_UNKNOWNCONNECTION)) && (con->count_modes > 0)) + { +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) + // For hardware rendering, an encoder_id is needed + if (con->encoder_id) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i connected with encoder", i); + platform.connector = con; + break; + } + else TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i connected but no encoder", i); +#else + // For software rendering, accept even without encoder_id + TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i suitable for software rendering", i); + platform.connector = con; + break; +#endif + } + + if (!platform.connector) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i NOT suitable (deleting)", i); + drmModeFreeConnector(con); + } + } + + if (!platform.connector) + { + TRACELOG(LOG_WARNING, "DISPLAY: No suitable DRM connector found"); + drmModeFreeResources(res); + close(platform.fd); + return -1; + } + +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) + drmModeEncoder *enc = drmModeGetEncoder(platform.fd, platform.connector->encoder_id); + if (!enc) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode encoder"); + drmModeFreeConnector(platform.connector); + drmModeFreeResources(res); + close(platform.fd); + return -1; + } + + platform.crtc = drmModeGetCrtc(platform.fd, enc->crtc_id); + if (!platform.crtc) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode crtc"); + drmModeFreeEncoder(enc); + drmModeFreeConnector(platform.connector); + drmModeFreeResources(res); + close(platform.fd); + return -1; + } + + // If InitWindow should use the current mode find it in the connector's mode list + if ((CORE.Window.screen.width <= 0) || (CORE.Window.screen.height <= 0)) + { + TRACELOG(LOG_TRACE, "DISPLAY: Selecting DRM connector mode for current used mode..."); + + platform.modeIndex = FindMatchingConnectorMode(platform.connector, &platform.crtc->mode); + + if (platform.modeIndex < 0) + { + TRACELOG(LOG_WARNING, "DISPLAY: No matching DRM connector mode found"); + drmModeFreeEncoder(enc); + drmModeFreeConnector(platform.connector); + drmModeFreeResources(res); + close(platform.fd); + return -1; + } + + CORE.Window.screen.width = CORE.Window.display.width; + CORE.Window.screen.height = CORE.Window.display.height; + } + + const bool allowInterlaced = FLAG_IS_SET(CORE.Window.flags, FLAG_INTERLACED_HINT); + const int fps = (CORE.Time.target > 0)? (1.0/CORE.Time.target) : 60; + + // Try to find an exact matching mode + platform.modeIndex = FindExactConnectorMode(platform.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); + + // If nothing found, try to find a nearly matching mode + if (platform.modeIndex < 0) platform.modeIndex = FindNearestConnectorMode(platform.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); + + // If nothing found, try to find an exactly matching mode including interlaced + if (platform.modeIndex < 0) platform.modeIndex = FindExactConnectorMode(platform.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); + + // If nothing found, try to find a nearly matching mode including interlaced + if (platform.modeIndex < 0) platform.modeIndex = FindNearestConnectorMode(platform.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); + + // If nothing found, there is no suitable mode + if (platform.modeIndex < 0) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable DRM connector mode"); + drmModeFreeEncoder(enc); + drmModeFreeConnector(platform.connector); + drmModeFreeResources(res); + close(platform.fd); + return -1; + } + + CORE.Window.display.width = platform.connector->modes[platform.modeIndex].hdisplay; + CORE.Window.display.height = platform.connector->modes[platform.modeIndex].vdisplay; + + TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u)", platform.connector->modes[platform.modeIndex].name, + platform.connector->modes[platform.modeIndex].hdisplay, platform.connector->modes[platform.modeIndex].vdisplay, + FLAG_IS_SET(platform.connector->modes[platform.modeIndex].flags, DRM_MODE_FLAG_INTERLACE)? 'i' : 'p', + platform.connector->modes[platform.modeIndex].vrefresh); + + drmModeFreeEncoder(enc); + enc = NULL; +#else + // For software rendering, the first available mode can be used + if (platform.connector->count_modes > 0) + { + platform.modeIndex = 0; + CORE.Window.display.width = platform.connector->modes[0].hdisplay; + CORE.Window.display.height = platform.connector->modes[0].vdisplay; + + TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u) for software rendering", + platform.connector->modes[0].name, + platform.connector->modes[0].hdisplay, + platform.connector->modes[0].vdisplay, + (platform.connector->modes[0].flags & DRM_MODE_FLAG_INTERLACE)? 'i' : 'p', + platform.connector->modes[0].vrefresh); + } + else + { + TRACELOG(LOG_WARNING, "DISPLAY: No modes available for connector"); + drmModeFreeConnector(platform.connector); + drmModeFreeResources(res); + close(platform.fd); + return -1; + } +#endif + + // Use the width and height of the surface for render + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + + drmModeFreeResources(res); + res = NULL; + +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) + // Hardware rendering initialization with EGL + platform.gbmDevice = gbm_create_device(platform.fd); + if (!platform.gbmDevice) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM device"); + return -1; + } + + platform.gbmSurface = gbm_surface_create(platform.gbmDevice, platform.connector->modes[platform.modeIndex].hdisplay, + platform.connector->modes[platform.modeIndex].vdisplay, GBM_FORMAT_ARGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + if (!platform.gbmSurface) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM surface"); + return -1; + } + + EGLint samples = 0; + EGLint sampleBuffer = 0; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) + { + samples = 4; + sampleBuffer = 1; + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); + } + + const EGLint framebufferAttribs[] = { + EGL_RENDERABLE_TYPE, (rlGetVersion() == RL_OPENGL_ES_30)? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT, // Type of context support + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Don't use it on Android! + EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5) + EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6) + EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) + EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer) + //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) + EGL_DEPTH_SIZE, 24, // Depth buffer size (Required to use Depth testing!) + //EGL_STENCIL_SIZE, 8, // Stencil buffer size + EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA + EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) + EGL_NONE + }; + + const EGLint contextAttribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + EGLint numConfigs = 0; + const char *eglClientExtensions = NULL; + + // Get an EGL device connection + // NOTE: eglGetPlatformDisplay() is preferred over eglGetDisplay() legacy call + platform.device = EGL_NO_DISPLAY; +#if defined(EGL_VERSION_1_5) + platform.device = eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); +#else + // Check if extension is available for eglGetPlatformDisplayEXT() + // NOTE: Better compatibility with some drivers (e.g. Mali Midgard) + eglClientExtensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if ((eglClientExtensions != NULL) && (strstr(eglClientExtensions, "EGL_EXT_platform_base") != NULL)) + { + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); + + if (eglGetPlatformDisplayEXT != NULL) platform.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); + } + + // In case extension not found or display could not be retrieved, try using legacy version + if (platform.device == EGL_NO_DISPLAY) platform.device = eglGetDisplay((EGLNativeDisplayType)platform.gbmDevice); +#endif + if (platform.device == EGL_NO_DISPLAY) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return -1; + } + + // Initialize the EGL device connection + if (eglInitialize(platform.device, NULL, NULL) == EGL_FALSE) + { + // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred + TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); + return -1; + } + + // WARNING: Providing framebufferAttribs is not logically necessary, + // but it may prevent segfaults on some nvidia drivers + if (!eglChooseConfig(platform.device, framebufferAttribs, NULL, 0, &numConfigs)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config count: 0x%x", eglGetError()); + return -1; + } + + TRACELOG(LOG_TRACE, "DISPLAY: EGL configs available: %d", numConfigs); + + EGLConfig *configs = (EGLConfig *)RL_CALLOC(numConfigs, sizeof(*configs)); + if (!configs) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get memory for EGL configs"); + return -1; + } + + EGLint matchingNumConfigs = 0; + if (!eglChooseConfig(platform.device, framebufferAttribs, configs, numConfigs, &matchingNumConfigs)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose EGL config: 0x%x", eglGetError()); + RL_FREE(configs); + return -1; + } + + TRACELOG(LOG_TRACE, "DISPLAY: EGL matching configs available: %d", matchingNumConfigs); + + // find the EGL config that matches the previously setup GBM format + int found = 0; + for (EGLint i = 0; i < matchingNumConfigs; i++) + { + EGLint id = 0; + if (!eglGetConfigAttrib(platform.device, configs[i], EGL_NATIVE_VISUAL_ID, &id)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config attribute: 0x%x", eglGetError()); + continue; + } + + if (GBM_FORMAT_ARGB8888 == id) + { + TRACELOG(LOG_TRACE, "DISPLAY: Using EGL config: %d", i); + platform.config = configs[i]; + found = 1; + break; + } + } + + RL_FREE(configs); + + if (!found) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable EGL config"); + return -1; + } + + // Set rendering API + eglBindAPI(EGL_OPENGL_ES_API); + + // Create an EGL rendering context + platform.context = eglCreateContext(platform.device, platform.config, EGL_NO_CONTEXT, contextAttribs); + if (platform.context == EGL_NO_CONTEXT) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context"); + return -1; + } + + // Create an EGL window surface + platform.surface = EGL_NO_SURFACE; + + if ((eglClientExtensions != NULL) && (strstr(eglClientExtensions, "EGL_EXT_platform_base") != NULL)) + { + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); + + if (eglCreatePlatformWindowSurfaceEXT != NULL) platform.surface = eglCreatePlatformWindowSurfaceEXT(platform.device, platform.config, platform.gbmSurface, NULL); + } + + if (platform.surface == EGL_NO_SURFACE) + { + platform.surface = eglCreateWindowSurface(platform.device, platform.config, (EGLNativeWindowType)platform.gbmSurface, NULL); + } + + if (platform.surface == EGL_NO_SURFACE) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL window surface: 0x%04x", eglGetError()); + return -1; + } + + // At this point, manage render size vs screen size + // NOTE: This function use and modify global module variables: + // -> CORE.Window.screen.width/CORE.Window.screen.height + // -> CORE.Window.render.width/CORE.Window.render.height + // -> CORE.Window.screenScale + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); + + // There must be at least one frame displayed before the buffers are swapped + //eglSwapInterval(platform.device, 1); + + EGLBoolean result = eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context); + + // Check surface and context activation + if (result != EGL_FALSE) + { + CORE.Window.ready = true; + + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + } + else + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); + return -1; + } + + // Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions + rlLoadExtensions(eglGetProcAddress); +#else + // At this point, manage render size vs screen size + // NOTE: This function use and modify global module variables: + // -> CORE.Window.screen.width/CORE.Window.screen.height + // -> CORE.Window.render.width/CORE.Window.render.height + // -> CORE.Window.screenScale + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); + + // Setup window ready state for software rendering + CORE.Window.ready = true; + + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; +#endif + + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully %s", + FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)? "(HighDPI)" : ""); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); + + // If graphic device is no properly initialized, end program + if (!CORE.Window.ready) + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); + return -1; + } + else SetWindowPosition(GetMonitorWidth(GetCurrentMonitor())/2 - CORE.Window.screen.width/2, GetMonitorHeight(GetCurrentMonitor())/2 - CORE.Window.screen.height/2); + + // Set some default window flags + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // false + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // false + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // true + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // false + + //---------------------------------------------------------------------------- + // Initialize timing system + //---------------------------------------------------------------------------- + // NOTE: timing system must be initialized before the input events system + InitTimer(); + //---------------------------------------------------------------------------- + + // Initialize input events system + //---------------------------------------------------------------------------- + InitEvdevInput(); // Evdev inputs initialization + +#if SUPPORT_SSH_KEYBOARD_RPI + InitKeyboard(); // Keyboard init (stdin) +#endif + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- + +#if defined(SUPPORT_DRM_CACHE) + if (InitSwapScreenBuffer() == 0) + { + TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully"); + return 0; + } + else + { + TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized failed"); + return -1; + } +#else // !SUPPORT_DRM_CACHE + TRACELOG(LOG_INFO, "PLATFORM: DRM: Initialized successfully"); + return 0; +#endif +} + +// Close platform +void ClosePlatform(void) +{ + if (platform.prevFB) + { + drmModeRmFB(platform.fd, platform.prevFB); + platform.prevFB = 0; + } + +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) + if (platform.prevBO) + { + gbm_surface_release_buffer(platform.gbmSurface, platform.prevBO); + platform.prevBO = NULL; + } + + if (platform.gbmSurface) + { + gbm_surface_destroy(platform.gbmSurface); + platform.gbmSurface = NULL; + } + + if (platform.gbmDevice) + { + gbm_device_destroy(platform.gbmDevice); + platform.gbmDevice = NULL; + } +#endif + + if (platform.crtc) + { + if (platform.connector) + { + drmModeSetCrtc(platform.fd, platform.crtc->crtc_id, platform.crtc->buffer_id, + platform.crtc->x, platform.crtc->y, &platform.connector->connector_id, 1, &platform.crtc->mode); + drmModeFreeConnector(platform.connector); + platform.connector = NULL; + } + + drmModeFreeCrtc(platform.crtc); + platform.crtc = NULL; + } + + if (platform.fd != -1) + { + close(platform.fd); + platform.fd = -1; + } + +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) + // Close surface, context and display + if (platform.device != EGL_NO_DISPLAY) + { + if (platform.surface != EGL_NO_SURFACE) + { + eglDestroySurface(platform.device, platform.surface); + platform.surface = EGL_NO_SURFACE; + } + + if (platform.context != EGL_NO_CONTEXT) + { + eglDestroyContext(platform.device, platform.context); + platform.context = EGL_NO_CONTEXT; + } + + eglTerminate(platform.device); + platform.device = EGL_NO_DISPLAY; + } +#endif + + CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called + + // Close the evdev devices + + if (platform.mouseFd != -1) + { + close(platform.mouseFd); + platform.mouseFd = -1; + } + + for (int i = 0; i < platform.gamepadCount; i++) + { + close(platform.gamepadStreamFd[i]); + platform.gamepadStreamFd[i] = -1; + } + + if (platform.keyboardFd != -1) + { + close(platform.keyboardFd); + platform.keyboardFd = -1; + } +} + +#if SUPPORT_SSH_KEYBOARD_RPI +// Initialize Keyboard system (using standard input) +static void InitKeyboard(void) +{ + // NOTE: Read directly from Standard Input (stdin) - STDIN_FILENO file descriptor, + // Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE + + // Save terminal keyboard settings + tcgetattr(STDIN_FILENO, &platform.defaultSettings); + + // Reconfigure terminal with new settings + struct termios keyboardNewSettings = { 0 }; + keyboardNewSettings = platform.defaultSettings; + + // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo and key processing + // NOTE: ISIG controls if ^C and ^Z generate break signals or not + FLAG_CLEAR(keyboardNewSettings.c_lflag, ICANON | ECHO | ISIG); + //FLAG_CLEAR(keyboardNewSettings.c_iflag, ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF); + keyboardNewSettings.c_cc[VMIN] = 1; + keyboardNewSettings.c_cc[VTIME] = 0; + + // Set new keyboard settings (change occurs immediately) + tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); + + // Save old keyboard mode to restore it at the end + platform.defaultFileFlags = fcntl(STDIN_FILENO, F_GETFL, 0); // F_GETFL: Get the file access mode and the file status flags + fcntl(STDIN_FILENO, F_SETFL, platform.defaultFileFlags | O_NONBLOCK); // F_SETFL: Set the file status flags to the value specified + + // NOTE: If ioctl() returns -1, it means the call failed for some reason (error code set in errno) + int result = ioctl(STDIN_FILENO, KDGKBMODE, &platform.defaultKeyboardMode); + + // In case of failure, it could mean a remote keyboard is used (SSH) + if (result < 0) TRACELOG(LOG_WARNING, "DRM: Failed to change keyboard mode, an SSH keyboard is probably used"); + else + { + // Reconfigure keyboard mode to get: + // - scancodes (K_RAW) + // - keycodes (K_MEDIUMRAW) + // - ASCII chars (K_XLATE) + // - UNICODE chars (K_UNICODE) + ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); // ASCII chars + } + + // Register keyboard restore when program finishes + atexit(RestoreKeyboard); +} + +// Restore default keyboard input +static void RestoreKeyboard(void) +{ + // Reset to default keyboard settings + tcsetattr(STDIN_FILENO, TCSANOW, &platform.defaultSettings); + + // Reconfigure keyboard to default mode + fcntl(STDIN_FILENO, F_SETFL, platform.defaultFileFlags); + ioctl(STDIN_FILENO, KDSKBMODE, platform.defaultKeyboardMode); +} + +// Process keyboard inputs +static void ProcessKeyboard(void) +{ + #define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read + + // Keyboard input polling (fill keys[256] array with status) + int bufferByteCount = 0; // Bytes available on the buffer + char keysBuffer[MAX_KEYBUFFER_SIZE] = { 0 }; // Max keys to be read at a time + + // Read availables keycodes from stdin + bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call + + // Reset pressed keys array (it will be filled below) + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) + { + CORE.Input.Keyboard.currentKeyState[i] = 0; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // Fill all read bytes (looking for keys) + for (int i = 0; i < bufferByteCount; i++) + { + // NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code! + // Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42 + if (keysBuffer[i] == 0x1b) + { + // Check if ESCAPE key has been pressed to stop program + if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1; + else + { + if (keysBuffer[i + 1] == 0x5b) // Special function key + { + if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) + { + // Process special function keys (F1 - F12) + switch (keysBuffer[i + 3]) + { + case 0x41: CORE.Input.Keyboard.currentKeyState[290] = 1; break; // raylib KEY_F1 + case 0x42: CORE.Input.Keyboard.currentKeyState[291] = 1; break; // raylib KEY_F2 + case 0x43: CORE.Input.Keyboard.currentKeyState[292] = 1; break; // raylib KEY_F3 + case 0x44: CORE.Input.Keyboard.currentKeyState[293] = 1; break; // raylib KEY_F4 + case 0x45: CORE.Input.Keyboard.currentKeyState[294] = 1; break; // raylib KEY_F5 + case 0x37: CORE.Input.Keyboard.currentKeyState[295] = 1; break; // raylib KEY_F6 + case 0x38: CORE.Input.Keyboard.currentKeyState[296] = 1; break; // raylib KEY_F7 + case 0x39: CORE.Input.Keyboard.currentKeyState[297] = 1; break; // raylib KEY_F8 + case 0x30: CORE.Input.Keyboard.currentKeyState[298] = 1; break; // raylib KEY_F9 + case 0x31: CORE.Input.Keyboard.currentKeyState[299] = 1; break; // raylib KEY_F10 + case 0x33: CORE.Input.Keyboard.currentKeyState[300] = 1; break; // raylib KEY_F11 + case 0x34: CORE.Input.Keyboard.currentKeyState[301] = 1; break; // raylib KEY_F12 + default: break; + } + + if (keysBuffer[i + 2] == 0x5b) i += 4; + else if ((keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) i += 5; + } + else + { + switch (keysBuffer[i + 2]) + { + case 0x41: CORE.Input.Keyboard.currentKeyState[265] = 1; break; // raylib KEY_UP + case 0x42: CORE.Input.Keyboard.currentKeyState[264] = 1; break; // raylib KEY_DOWN + case 0x43: CORE.Input.Keyboard.currentKeyState[262] = 1; break; // raylib KEY_RIGHT + case 0x44: CORE.Input.Keyboard.currentKeyState[263] = 1; break; // raylib KEY_LEFT + default: break; + } + + i += 3; // Jump to next key + } + + // NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT) + } + } + } + else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with KEY_*) + { + CORE.Input.Keyboard.currentKeyState[257] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE + { + CORE.Input.Keyboard.currentKeyState[259] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 259; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + else + { + // Translate lowercase a-z letters to A-Z + if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122)) + { + CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i] - 32] = 1; + } + else CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i]] = 1; + + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue + CORE.Input.Keyboard.keyPressedQueueCount++; + } + } +} +#endif // SUPPORT_SSH_KEYBOARD_RPI + +// Initialize user input from evdev(/dev/input/event) +// this means mouse, keyboard or gamepad devices +static void InitEvdevInput(void) +{ + char path[MAX_FILEPATH_LENGTH] = { 0 }; + DIR *directory = NULL; + struct dirent *entity = NULL; + + // Initialize keyboard file descriptor + platform.keyboardFd = -1; + platform.mouseFd = -1; + + // Reset variables + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { + CORE.Input.Touch.position[i].x = -1; + CORE.Input.Touch.position[i].y = -1; + platform.touchActive[i] = false; + platform.touchPosition[i].x = -1; + platform.touchPosition[i].y = -1; + platform.touchId[i] = -1; + } + + // Initialize touch slot + platform.touchSlot = 0; + + // Reset keyboard key state + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) + { + CORE.Input.Keyboard.currentKeyState[i] = 0; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // Open the linux directory of "/dev/input" + directory = opendir(DEFAULT_EVDEV_PATH); + + if (directory) + { + while ((entity = readdir(directory)) != NULL) + { + if ((strncmp("event", entity->d_name, strlen("event")) == 0) || // Search for devices named "event*" + (strncmp("mouse", entity->d_name, strlen("mouse")) == 0)) // Search for devices named "mouse*" + { + snprintf(path, MAX_FILEPATH_LENGTH, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); + + ConfigureEvdevDevice(path); // Configure the device if appropriate + } + } + + closedir(directory); + } + else TRACELOG(LOG_WARNING, "INPUT: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH); +} + +// Identifies a input device and configures it for use if appropriate +static void ConfigureEvdevDevice(char *device) +{ + #define BITS_PER_LONG (8*sizeof(long)) + #define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1) + #define OFF(x) ((x)%BITS_PER_LONG) + #define BIT(x) (1UL<> OFF(bit)) & 1) + + unsigned long evBits[NBITS(EV_MAX)] = { 0 }; + unsigned long absBits[NBITS(ABS_MAX)] = { 0 }; + unsigned long relBits[NBITS(REL_MAX)] = { 0 }; + unsigned long keyBits[NBITS(KEY_MAX)] = { 0 }; + + // Open the device + int fd = open(device, O_RDONLY | O_NONBLOCK); + if (fd < 0) + { + TRACELOG(LOG_WARNING, "SYSTEM: Failed to open input device: %s", device); + return; + } + + // At this point, a connection to the device has been stablished, but still left to know what the device is, + // it could be many things, even as simple as a power button... + //------------------------------------------------------------------------------------------------------- + + // Identify the device + //------------------------------------------------------------------------------------------------------- + struct { + bool exist; + struct input_absinfo info; + } absinfo[ABS_CNT] = { 0 }; + + // These flags aren't really a one of + // Some devices could have properties associated with keyboards + // as well as properties associated with mice + bool isKeyboard = false; + bool isMouse = false; + bool isTouch = false; + bool isGamepad = false; + + int absAxisCount = 0; + + ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); + ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits); + + if (TEST_BIT(evBits, EV_ABS)) + { + ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits); + + // If the device has an X an Y axis it's either a touch device, a special mouse or a gamepad + bool hasAbsXY = TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y); + + if (hasAbsXY) + { + absAxisCount += 2; + + absinfo[ABS_X].exist = true; + absinfo[ABS_Y].exist = true; + + ioctl(fd, EVIOCGABS(ABS_X), &absinfo[ABS_X].info); + ioctl(fd, EVIOCGABS(ABS_Y), &absinfo[ABS_Y].info); + } + + // If it has any of these buttons it's a touch device + if (hasAbsXY && + (TEST_BIT(keyBits, BTN_STYLUS) || + TEST_BIT(keyBits, BTN_TOOL_PEN) || + TEST_BIT(keyBits, BTN_TOOL_FINGER) || + TEST_BIT(keyBits, BTN_TOUCH))) isTouch = true; + + // Absolute mice should really only exist with VMWare + else if (hasAbsXY && TEST_BIT(keyBits, BTN_MOUSE)) isMouse = true; + + // If any of the common joystick axes are present, assume it's a gamepad + else + { + for (int axis = (hasAbsXY? ABS_Z : ABS_X); axis < ABS_PRESSURE; axis++) + { + if (TEST_BIT(absBits, axis)) + { + absinfo[axis].exist = true; + isGamepad = true; + absAxisCount++; + + ioctl(fd, EVIOCGABS(axis), &absinfo[axis].info); + } + } + } + + // If the device has multitouch axes, it's a touch device + if (TEST_BIT(absBits, ABS_MT_POSITION_X) && + TEST_BIT(absBits, ABS_MT_POSITION_Y)) isTouch = true; + } + + if (TEST_BIT(evBits, EV_REL)) + { + ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits); + + // If it has any of the gamepad or touch features tested so far, it's not a mouse + if (!isTouch && + !isGamepad && + TEST_BIT(relBits, REL_X) && + TEST_BIT(relBits, REL_Y) && + TEST_BIT(keyBits, BTN_MOUSE)) isMouse = true; + } + + if (TEST_BIT(evBits, EV_KEY)) + { + // The first 32 keys as defined in input-event-codes.h are pretty much + // exclusive to keyboards, so they can be tested using a mask + // Leave out the first bit to not test KEY_RESERVED + const unsigned long mask = 0xFFFFFFFE; + if ((keyBits[0] & mask) == mask) isKeyboard = true; + + // If any of the common gamepad buttons is found, assume it's a gamepad + else + { + for (int button = BTN_JOYSTICK; button < BTN_DIGI; ++button) + { + if (TEST_BIT(keyBits, button)) isGamepad = true; + } + + for (int button = BTN_TRIGGER_HAPPY1; button <= BTN_TRIGGER_HAPPY40; button++) + { + if (TEST_BIT(keyBits, button)) isGamepad = true; + } + } + } + + const char *deviceKindStr = "unknown"; + if (isMouse || isTouch) + { + bool prioritize = false; + + // Priority logic: touchscreens override Mice + // 1. No device set yet? Take it + if (platform.mouseFd == -1) prioritize = true; + // 2. Current is mouse, new is touch? Upgrade to touch + else if (isTouch && !platform.mouseIsTouch) prioritize = true; + // 3. Current is touch, new is touch? Use the new one (last one found wins, standard behavior) + else if (isTouch && platform.mouseIsTouch) prioritize = true; + // 4. Current is mouse, new is mouse? Use the new one + else if (!isTouch && !platform.mouseIsTouch) prioritize = true; + // 5. Current is touch, new is mouse? Ignore the mouse, keep the touchscreen + else prioritize = false; + + if (prioritize) + { + deviceKindStr = isTouch? "touchscreen" : "mouse"; + + if (platform.mouseFd != -1) + { + TRACELOG(LOG_INFO, "INPUT: Overwriting previous input device with new %s", deviceKindStr); + close(platform.mouseFd); + } + + platform.mouseFd = fd; + platform.mouseIsTouch = isTouch; + + if (absAxisCount > 0) + { + platform.absRange.x = absinfo[ABS_X].info.minimum; + platform.absRange.width = absinfo[ABS_X].info.maximum - absinfo[ABS_X].info.minimum; + + platform.absRange.y = absinfo[ABS_Y].info.minimum; + platform.absRange.height = absinfo[ABS_Y].info.maximum - absinfo[ABS_Y].info.minimum; + } + + TRACELOG(LOG_INFO, "INPUT: Initialized input device %s as %s", device, deviceKindStr); + } + else + { + TRACELOG(LOG_INFO, "INPUT: Ignoring device %s (keeping higher priority %s device)", device, platform.mouseIsTouch ? "touchscreen" : "mouse"); + close(fd); + return; + } + } + else if (isGamepad && !isMouse && !isKeyboard && (platform.gamepadCount < MAX_GAMEPADS)) + { + deviceKindStr = "gamepad"; + int index = platform.gamepadCount++; + + platform.gamepadStreamFd[index] = fd; + CORE.Input.Gamepad.ready[index] = true; + + ioctl(platform.gamepadStreamFd[index], EVIOCGNAME(64), &CORE.Input.Gamepad.name[index]); + CORE.Input.Gamepad.axisCount[index] = absAxisCount; + + if (absAxisCount > 0) + { + // TODO: Review GamepadAxis enum matching + // So gamepad axes (as in the actual linux joydev.c) are simply enumerated + // and (at least for some input drivers like xpat) it's convention to use + // ABS_X, ABX_Y for one joystick ABS_RX, ABS_RY for the other and the Z axes for the shoulder buttons + // If these are now enumerated, it results to LJOY_X, LJOY_Y, LEFT_SHOULDERB, RJOY_X, ... + int axisIndex = 0; + for (int axis = ABS_X; axis < ABS_PRESSURE; axis++) + { + if (absinfo[axis].exist) + { + platform.gamepadAbsAxisRange[index][axisIndex][0] = absinfo[axisIndex].info.minimum; + platform.gamepadAbsAxisRange[index][axisIndex][1] = absinfo[axisIndex].info.maximum - absinfo[axisIndex].info.minimum; + + platform.gamepadAbsAxisMap[index][axis] = axisIndex; + axisIndex++; + } + } + } + } + else if (isKeyboard && (platform.keyboardFd == -1)) + { + deviceKindStr = "keyboard"; + platform.keyboardFd = fd; + } + else + { + close(fd); + return; + } + + TRACELOG(LOG_INFO, "INPUT: Initialized input device %s as %s", device, deviceKindStr); +} + +// Poll and process evdev keyboard events +static void PollKeyboardEvents(void) +{ + int fd = platform.keyboardFd; + if (fd == -1) return; + + struct input_event event = { 0 }; + int keycode = -1; + + // Try to read data from the keyboard and only continue if successful + while (read(fd, &event, sizeof(event)) == (int)sizeof(event)) + { + // Check if the event is a key event + if (event.type != EV_KEY) continue; + +#if SUPPORT_SSH_KEYBOARD_RPI + // If the event was a key, assume a working keyboard is connected, so disable the SSH keyboard + platform.eventKeyboardMode = true; +#endif + // Keyboard keys appear for codes 1 to 255, ignore everthing else + if ((event.code >= 1) && (event.code <= 255)) + { + // Lookup the scancode in the keymap to get a keycode + keycode = linuxToRaylibMap[event.code]; + + // Make sure a valid keycode is obtained + if ((keycode > 0) && (keycode < MAX_KEYBOARD_KEYS)) + { + // WARNING: https://www.kernel.org/doc/Documentation/input/input.txt + // Event interface: 'value' is the value the event carries. Either a relative change for EV_REL, + // absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat + CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1); + CORE.Input.Keyboard.keyRepeatInFrame[keycode] = (event.value == 2); + + // If the key is pressed add it to the queues + if (event.value == 1) + { + if (CORE.Input.Keyboard.keyPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) + { + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + + if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) + { + // TODO/FIXME: This is not actually converting to unicode properly because it's not taking things like shift into account + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = evkeyToUnicodeLUT[event.code]; + CORE.Input.Keyboard.charPressedQueueCount++; + } + } + + TRACELOG(LOG_DEBUG, "INPUT: KEY_%s Keycode(linux): %4i KeyCode(raylib): %4i", (event.value == 0)? "UP " : "DOWN", event.code, keycode); + } + } + } +} + +// Poll gamepad input events +static void PollGamepadEvents(void) +{ + // Read gamepad event + struct input_event event = { 0 }; + + for (int i = 0; i < platform.gamepadCount; i++) + { + if (!CORE.Input.Gamepad.ready[i]) continue; + + // Register previous gamepad states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; + + while (read(platform.gamepadStreamFd[i], &event, sizeof(event)) == (int)sizeof(event)) + { + if (event.type == EV_KEY) + { + if (event.code < KEYMAP_SIZE) + { + short keycode = linuxToRaylibMap[event.code]; // raylib keycode + + TRACELOG(LOG_DEBUG, "INPUT: Gamepad %2i: KEY_%s Keycode(linux): %4i Keycode(raylib): %4i", i, (event.value == 0)? "UP" : "DOWN", event.code, keycode); + + if ((keycode != 0) && (keycode < MAX_GAMEPAD_BUTTONS)) + { + // 1 - button pressed, 0 - button released + CORE.Input.Gamepad.currentButtonState[i][keycode] = event.value; + CORE.Input.Gamepad.lastButtonPressed = (event.value == 1)? keycode : GAMEPAD_BUTTON_UNKNOWN; + } + } + } + else if (event.type == EV_ABS) + { + if (event.code < ABS_CNT) + { + int axisRaylib = platform.gamepadAbsAxisMap[i][event.code]; + + TRACELOG(LOG_DEBUG, "INPUT: Gamepad %2i: Axis: %2i Value: %i", i, axisRaylib, event.value); + + if (axisRaylib < MAX_GAMEPAD_AXES) + { + int min = platform.gamepadAbsAxisRange[i][event.code][0]; + int range = platform.gamepadAbsAxisRange[i][event.code][1]; + + // NOTE: Scaling of event.value to get values between -1..1 + CORE.Input.Gamepad.axisState[i][axisRaylib] = (2*(float)(event.value - min)/range) - 1; + } + } + } + } + } +} + +// Poll mouse input events +static void PollMouseEvents(void) +{ + int fd = platform.mouseFd; + if (fd == -1) return; + + struct input_event event = { 0 }; + int touchAction = -1; // 0-TOUCH_ACTION_UP, 1-TOUCH_ACTION_DOWN, 2-TOUCH_ACTION_MOVE + static bool isMultitouch = false; // Detect if device supports MT events + + // Try to read data from the mouse/touch/gesture and only continue if successful + while (read(fd, &event, sizeof(event)) == (int)sizeof(event)) + { + // Relative movement parsing + if (event.type == EV_REL) + { + if (event.code == REL_X) + { + if (platform.cursorRelative) + { + CORE.Input.Mouse.currentPosition.x = event.value; + CORE.Input.Mouse.previousPosition.x = 0.0f; + } + else CORE.Input.Mouse.currentPosition.x += event.value; + + // NOTE: For DRM touchscreen, do not simulate touch from mouse movement + // CORE.Input.Touch.position[0].x = CORE.Input.Mouse.currentPosition.x; + touchAction = 2; // TOUCH_ACTION_MOVE + } + + if (event.code == REL_Y) + { + if (platform.cursorRelative) + { + CORE.Input.Mouse.currentPosition.y = event.value; + CORE.Input.Mouse.previousPosition.y = 0.0f; + } + else CORE.Input.Mouse.currentPosition.y += event.value; + + // NOTE: For DRM touchscreen, do not simulate touch from mouse movement + // CORE.Input.Touch.position[0].y = CORE.Input.Mouse.currentPosition.y; + touchAction = 2; // TOUCH_ACTION_MOVE + } + + if (event.code == REL_WHEEL) platform.eventWheelMove.y += event.value; + } + + // Absolute movement parsing + if (event.type == EV_ABS) + { + // Basic movement + if (event.code == ABS_X) + { + CORE.Input.Mouse.currentPosition.x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; // Scale according to absRange + + // Update single touch position only if it's active and no MT events are being used + if (platform.touchActive[0] && !isMultitouch) + { + platform.touchPosition[0].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; + if (touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE + } + } + + if (event.code == ABS_Y) + { + CORE.Input.Mouse.currentPosition.y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; // Scale according to absRange + + // Update single touch position only if it's active and no MT events are being used + if (platform.touchActive[0] && !isMultitouch) + { + platform.touchPosition[0].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; + if (touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE + } + } + + // Multitouch movement + if (event.code == ABS_MT_SLOT) + { + platform.touchSlot = event.value; + isMultitouch = true; + } + + if (event.code == ABS_MT_POSITION_X) + { + isMultitouch = true; + if (platform.touchSlot < MAX_TOUCH_POINTS) + { + platform.touchPosition[platform.touchSlot].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; + + // If this slot is active, it's a move; If not, update the buffer for when it becomes active + // Only set to MOVE if a DOWN or UP event has not been detected this frame + if (platform.touchActive[platform.touchSlot] && touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE + } + } + + if (event.code == ABS_MT_POSITION_Y) + { + if (platform.touchSlot < MAX_TOUCH_POINTS) + { + platform.touchPosition[platform.touchSlot].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; + + // If this slot is active, it's a move; If not, update the buffer for when it becomes active + // Only set to MOVE if a DOWN or UP event have not been detected this frame + if (platform.touchActive[platform.touchSlot] && touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE + } + } + + if (event.code == ABS_MT_TRACKING_ID) + { + if (platform.touchSlot < MAX_TOUCH_POINTS) + { + if (event.value >= 0) + { + + platform.touchActive[platform.touchSlot] = true; + platform.touchId[platform.touchSlot] = event.value; // Use Tracking ID for unique IDs + + touchAction = 1; // TOUCH_ACTION_DOWN + } + else + { + // Touch has ended for this point + platform.touchActive[platform.touchSlot] = false; + platform.touchPosition[platform.touchSlot].x = -1; + platform.touchPosition[platform.touchSlot].y = -1; + platform.touchId[platform.touchSlot] = -1; + + // Force UP action if DOWN action has not already been set + // (DOWN takes priority over UP if both happen in one frame, though rare) + if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP + } + } + } + + // Handle ABS_MT_PRESSURE (0x3a) if available, as some devices use it for lift-off + #ifndef ABS_MT_PRESSURE + #define ABS_MT_PRESSURE 0x3a + #endif + if (event.code == ABS_MT_PRESSURE) + { + if (platform.touchSlot < MAX_TOUCH_POINTS) + { + if (event.value <= 0) // Pressure 0 means lift + { + platform.touchActive[platform.touchSlot] = false; + platform.touchPosition[platform.touchSlot].x = -1; + platform.touchPosition[platform.touchSlot].y = -1; + platform.touchId[platform.touchSlot] = -1; + if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP + } + } + } + + // Touchscreen tap + if (event.code == ABS_PRESSURE) + { + int previousMouseLeftButtonState = platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT]; + + if (!event.value && previousMouseLeftButtonState) + { + platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 0; + if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP + } + + if (event.value && !previousMouseLeftButtonState) + { + platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 1; + touchAction = 1; // TOUCH_ACTION_DOWN + } + } + } + + // Button parsing + if (event.type == EV_KEY) + { + // Mouse button parsing + if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) + { + platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = event.value; + + if (event.value > 0) + { + bool activateSlot0 = false; + + if (event.code == BTN_LEFT) activateSlot0 = true; // Mouse click always activates + else if (event.code == BTN_TOUCH) + { + bool anyActive = false; + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { + if (platform.touchActive[i]) { anyActive = true; break; } + } + + if (!anyActive) activateSlot0 = true; + } + + if (activateSlot0) + { + platform.touchActive[0] = true; + platform.touchId[0] = 0; + } + + touchAction = 1; // TOUCH_ACTION_DOWN + } + else + { + // Only clear touch 0 for actual mouse clicks (BTN_LEFT) + if (event.code == BTN_LEFT) + { + platform.touchActive[0] = false; + platform.touchPosition[0].x = -1; + platform.touchPosition[0].y = -1; + } + else if (event.code == BTN_TOUCH) platform.touchSlot = 0; // Reset slot index to 0 + + touchAction = 0; // TOUCH_ACTION_UP + } + } + + if (event.code == BTN_RIGHT) platform.currentButtonStateEvdev[MOUSE_BUTTON_RIGHT] = event.value; + if (event.code == BTN_MIDDLE) platform.currentButtonStateEvdev[MOUSE_BUTTON_MIDDLE] = event.value; + if (event.code == BTN_SIDE) platform.currentButtonStateEvdev[MOUSE_BUTTON_SIDE] = event.value; + if (event.code == BTN_EXTRA) platform.currentButtonStateEvdev[MOUSE_BUTTON_EXTRA] = event.value; + if (event.code == BTN_FORWARD) platform.currentButtonStateEvdev[MOUSE_BUTTON_FORWARD] = event.value; + if (event.code == BTN_BACK) platform.currentButtonStateEvdev[MOUSE_BUTTON_BACK] = event.value; + } + + // Screen confinement + if (!CORE.Input.Mouse.cursorLocked) + { + if (CORE.Input.Mouse.currentPosition.x < 0) CORE.Input.Mouse.currentPosition.x = 0; + if (CORE.Input.Mouse.currentPosition.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) + CORE.Input.Mouse.currentPosition.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; + + if (CORE.Input.Mouse.currentPosition.y < 0) CORE.Input.Mouse.currentPosition.y = 0; + if (CORE.Input.Mouse.currentPosition.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) + CORE.Input.Mouse.currentPosition.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; + } + + // Repack active touches into CORE.Input.Touch + int k = 0; + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { + if (platform.touchActive[i]) + { + CORE.Input.Touch.position[k] = platform.touchPosition[i]; + CORE.Input.Touch.pointId[k] = platform.touchId[i]; + k++; + } + } + + CORE.Input.Touch.pointCount = k; + + // Clear remaining slots + for (int i = k; i < MAX_TOUCH_POINTS; i++) + { + CORE.Input.Touch.position[i].x = -1; + CORE.Input.Touch.position[i].y = -1; + CORE.Input.Touch.pointId[i] = -1; + } + +#if SUPPORT_GESTURES_SYSTEM + if (touchAction > -1) + { + GestureEvent gestureEvent = { 0 }; + gestureEvent.touchAction = touchAction; + gestureEvent.pointCount = CORE.Input.Touch.pointCount; + + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { + gestureEvent.pointId[i] = i; + gestureEvent.position[i] = CORE.Input.Touch.position[i]; + } + + ProcessGestureEvent(gestureEvent); + touchAction = -1; + } +#endif + } +} + +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) +// Search matching DRM mode in connector's mode list +static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode) +{ + if (NULL == connector) return -1; + if (NULL == mode) return -1; + + // safe bitwise comparison of two modes + #define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b))? sizeof(a) : sizeof(b)) + + for (size_t i = 0; i < connector->count_modes; i++) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, connector->modes[i].hdisplay, connector->modes[i].vdisplay, + connector->modes[i].vrefresh, (FLAG_IS_SET(connector->modes[i].flags, DRM_MODE_FLAG_INTERLACE) > 0)? "interlaced" : "progressive"); + + if (0 == BINCMP(&platform.crtc->mode, &platform.connector->modes[i])) return i; + } + + return -1; + + #undef BINCMP +} + +// Search exactly matching DRM connector mode in connector's list +static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) +{ + TRACELOG(LOG_TRACE, "DISPLAY: Searching exact connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced? "yes" : "no"); + + if (NULL == connector) return -1; + + for (int i = 0; i < platform.connector->count_modes; i++) + { + const drmModeModeInfo *const mode = &platform.connector->modes[i]; + + TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0)? "interlaced" : "progressive"); + + if ((FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0) && !allowInterlaced) continue; + + if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i; + } + + TRACELOG(LOG_TRACE, "DISPLAY: No DRM exact matching mode found"); + return -1; +} + +// Search the nearest matching DRM connector mode in connector's list +static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) +{ + TRACELOG(LOG_TRACE, "DISPLAY: Searching nearest connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced? "yes" : "no"); + + if (NULL == connector) return -1; + + int nearestIndex = -1; + int minUnusedPixels = INT_MAX; + int minFpsDiff = INT_MAX; + for (int i = 0; i < platform.connector->count_modes; i++) + { + const drmModeModeInfo *const mode = &platform.connector->modes[i]; + + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, + (FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0)? "interlaced" : "progressive"); + + if ((mode->hdisplay < width) || (mode->vdisplay < height)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small"); + continue; + } + + if ((FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0) && !allowInterlaced) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM shouldn't choose an interlaced mode"); + continue; + } + + const int unusedPixels = (mode->hdisplay - width)*(mode->vdisplay - height); + const int fpsDiff = mode->vrefresh - fps; + + if ((unusedPixels < minUnusedPixels) || + ((unusedPixels == minUnusedPixels) && (abs(fpsDiff) < abs(minFpsDiff))) || + ((unusedPixels == minUnusedPixels) && (abs(fpsDiff) == abs(minFpsDiff)) && (fpsDiff > 0))) + { + nearestIndex = i; + minUnusedPixels = unusedPixels; + minFpsDiff = fpsDiff; + } + } + + return nearestIndex; +} +#endif + +// Compute framebuffer size relative to screen size and display size +// NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified +static void SetupFramebuffer(int width, int height) +{ + // Calculate CORE.Window.render.width and CORE.Window.render.height, using the display size (input params) and the desired screen size (global var) + if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) + { + 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); + + // Downscaling to fit display with border-bars + float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width; + float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height; + + if (widthRatio <= heightRatio) + { + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio); + CORE.Window.render.height = CORE.Window.display.height; + CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width); + CORE.Window.renderOffset.y = 0; + } + + // Screen scaling required + float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width; + CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f); + + // NOTE: Rendering to full display resolution, + // calculate above parameters for downscale matrix and offsets + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = CORE.Window.display.height; + + TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height); + } + else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height)) + { + // Required screen size is smaller than display size + 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); + + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) + { + CORE.Window.screen.width = CORE.Window.display.width; + CORE.Window.screen.height = CORE.Window.display.height; + } + + // Upscaling to fit display with border-bars + float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; + float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; + + if (displayRatio <= screenRatio) + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio); + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width); + CORE.Window.renderOffset.y = 0; + } + } + else + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = 0; + } +} + +// EOF diff --git a/src/libraries/raylib/raylib/src/platforms/rcore_memory.c b/src/libraries/raylib/raylib/src/platforms/rcore_memory.c new file mode 100644 index 0000000..5d9461c --- /dev/null +++ b/src/libraries/raylib/raylib/src/platforms/rcore_memory.c @@ -0,0 +1,594 @@ +/********************************************************************************************** +* +* rcore_memory - Functions to manage window, graphics device and inputs +* +* PLATFORM: MEMORY (No OS) +* - Memory framebuffer output (no os) +* +* LIMITATIONS: +* - Software renderer (rlsw) +* - No input system +* +* POSSIBLE IMPROVEMENTS: +* - Improvement 01 +* - Improvement 02 +* +* CONFIGURATION: +* #define RCORE_PLATFORM_CUSTOM_FLAG +* Custom flag for rcore on target platform -not used- +* +* DEPENDENCIES: +* - rlsw: Software renderer +* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2025-2026 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#if defined(_WIN32) + #include // Required for: kbhit() +#else + // Provide kbhit() function in non-Windows platforms + #include + #include + #include +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Platform-specific required data for timing (Win32) +#if defined(_WIN32) +typedef struct _LARGE_INTEGER { int64_t QuadPart; } LARGE_INTEGER; +__declspec(dllimport) int __stdcall QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount); +__declspec(dllimport) int __stdcall QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency); +#endif + +typedef struct { + unsigned int *pixels; // Pointer to pixel data buffer (RGBA8888 format) +#if defined(_WIN32) + LARGE_INTEGER timerFrequency; +#endif +} PlatformData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context + +static PlatformData platform = { 0 }; // Platform specific data + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +bool InitGraphicsDevice(void); // Initialize graphics device + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +#if !defined(_WIN32) +static int kbhit(void); // Check if key has been pressed +static char getch(void) { return getchar(); } // Get pressed character +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +bool WindowShouldClose(void) +{ + if (CORE.Window.ready) return CORE.Window.shouldClose; + else return true; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + TRACELOG(LOG_WARNING, "ToggleFullscreen() not available on target platform"); +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + TRACELOG(LOG_WARNING, "ToggleBorderlessWindowed() not available on target platform"); +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MaximizeWindow() not available on target platform"); +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform"); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + TRACELOG(LOG_WARNING, "RestoreWindow() not available on target platform"); +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + TRACELOG(LOG_WARNING, "SetWindowState() not available on target platform"); +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + TRACELOG(LOG_WARNING, "ClearWindowState() not available on target platform"); +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform"); +} + +// Set icon for window +void SetWindowIcons(Image *images, int count) +{ + TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform"); +} + +// Set title for window +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform"); +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform"); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + TRACELOG(LOG_WARNING, "SetWindowSize() not available on target platform"); +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + TRACELOG(LOG_WARNING, "SetWindowOpacity() not available on target platform"); +} + +// Set window focused +void SetWindowFocused(void) +{ + TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform"); +} + +// Get native window handle +void *GetWindowHandle(void) +{ + TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform"); + return NULL; +} + +// Get number of monitors +int GetMonitorCount(void) +{ + TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform"); + return 1; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + TRACELOG(LOG_WARNING, "GetCurrentMonitor() not implemented on target platform"); + return 0; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorWidth() not implemented on target platform"); + return 0; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorHeight() not implemented on target platform"); + return 0; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth() not implemented on target platform"); + return 0; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight() not implemented on target platform"); + return 0; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorRefreshRate() not implemented on target platform"); + return 0; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorName() not implemented on target platform"); + return ""; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + TRACELOG(LOG_WARNING, "GetWindowPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get window scale DPI factor for current monitor +Vector2 GetWindowScaleDPI(void) +{ + TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform"); + return (Vector2){ 1.0f, 1.0f }; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + TRACELOG(LOG_WARNING, "SetClipboardText() not implemented on target platform"); +} + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ + TRACELOG(LOG_WARNING, "GetClipboardText() not implemented on target platform"); + return NULL; +} + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + + TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform"); + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + CORE.Input.Mouse.cursorHidden = false; +} + +// Hide mouse cursor +void HideCursor(void) +{ + CORE.Input.Mouse.cursorHidden = true; +} + +// Enable cursor (unlock cursor) +void EnableCursor(void) +{ + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + CORE.Input.Mouse.cursorHidden = false; +} + +// Disable cursor (lock cursor) +void DisableCursor(void) +{ + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + CORE.Input.Mouse.cursorHidden = true; +} + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ + // Update framebuffer + rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds since InitTimer() +double GetTime(void) +{ + double time = 0.0; +#if defined(_WIN32) + LARGE_INTEGER currentTicks = { 0 }; + QueryPerformanceCounter(¤tTicks); + + time = (double)(currentTicks.QuadPart - CORE.Time.base)/(double)platform.timerFrequency.QuadPart; +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) + struct timespec ts = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long long nanoSeconds = (unsigned long long)ts.tv_sec*1000000000LLU + (unsigned long long)ts.tv_nsec; + + time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() +#elif defined(PICO_RP2350) + time = (double)to_ms_since_boot(get_absolute_time())/1000.0; +#endif + return time; +} + +// Open URL with default system browser (if available) +// NOTE: This function is only safe to use if you control the URL given. +// A user could craft a malicious string performing another action. +// Only call this function yourself not with user input or make sure to check the string yourself. +// REF: https://github.com/raysan5/raylib/issues/686 +void OpenURL(const char *url) +{ + // Security check to (partially) avoid malicious code on target platform + if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character"); + else + { + char *cmd = (char *)RL_CALLOC(strlen(url) + 32, sizeof(char)); + sprintf(cmd, "explorer \"%s\"", url); + int result = system(cmd); + if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created"); + RL_FREE(cmd); + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + TRACELOG(LOG_WARNING, "SetGamepadMappings() not implemented on target platform"); + return 0; +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + TRACELOG(LOG_WARNING, "SetGamepadVibration() not implemented on target platform"); +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; +} + +// Set mouse cursor +void SetMouseCursor(int cursor) +{ + TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform"); +} + +// Get physical key name +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + +// Register all input events +void PollInputEvents(void) +{ +#if SUPPORT_GESTURES_SYSTEM + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + + // Reset key repeats + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN + //CORE.Input.Gamepad.axisCount = 0; + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Reset touch positions + // TODO: It resets on target platform the mouse position and not filled again until a move-event, + // so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed! + //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; + + // Register previous keys states + // NOTE: Android supports up to 260 keys + for (int i = 0; i < 260; i++) + { + CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // TODO: Poll input events for current platform + + // Check for key pressed to exit + if (kbhit()) + { + int key = getch(); + if (key == 27) CORE.Window.shouldClose = true; // KEY_ESCAPE + } +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + // Memory framebuffer can only work with software renderer + if (rlGetVersion() != RL_OPENGL_SOFTWARE) + { + TRACELOG(LOG_WARNING, "DISPLAY: Memory platform requires software renderer (GRAPHICS_API_OPENGL_SOFTWARE)"); + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); + return -1; + } + else + { + // Load memory framebuffer with desired screen size + platform.pixels = (unsigned int *)RL_CALLOC(CORE.Window.screen.width*CORE.Window.screen.height, sizeof(int)); + } + //---------------------------------------------------------------------------- + + // If everything worked as expected, continue + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + + CORE.Window.ready = true; + + // TODO: Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions + //---------------------------------------------------------------------------- + // ... + //---------------------------------------------------------------------------- + + // TODO: Initialize input events system + // It could imply keyboard, mouse, gamepad, touch... + // Depending on the platform libraries/SDK it could use a callback mechanism + // For system events and inputs evens polling on a per-frame basis, use PollInputEvents() + //---------------------------------------------------------------------------- + // ... + //---------------------------------------------------------------------------- + + // Initialize timing system + //---------------------------------------------------------------------------- +#if defined(_WIN32) + LARGE_INTEGER time = { 0 }; + QueryPerformanceCounter(&time); + QueryPerformanceFrequency(&platform.timerFrequency); + CORE.Time.base = time.QuadPart; +#endif + InitTimer(); + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- + + TRACELOG(LOG_INFO, "PLATFORM: MEMORY: Initialized successfully"); + + return 0; +} + +// Close platform +void ClosePlatform(void) +{ + RL_FREE(platform.pixels); +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +#if !defined(_WIN32) +// Check if key has been pressed +static int kbhit(void) +{ + struct termios oldt = { 0 }; + struct termios newt = { 0 }; + int ch = 0; + int oldf = 0; + + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + oldf = fcntl(STDIN_FILENO, F_GETFL, 0); + fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); + + ch = getchar(); + + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + fcntl(STDIN_FILENO, F_SETFL, oldf); + + if (ch != EOF) + { + ungetc(ch, stdin); + return 1; + } + + return 0; +} +#endif + +// EOF diff --git a/src/libraries/raylib/raylib/src/platforms/rcore_template.c b/src/libraries/raylib/raylib/src/platforms/rcore_template.c new file mode 100644 index 0000000..1866059 --- /dev/null +++ b/src/libraries/raylib/raylib/src/platforms/rcore_template.c @@ -0,0 +1,542 @@ +/********************************************************************************************** +* +* rcore_ template - Functions to manage window, graphics device and inputs +* +* PLATFORM: +* - TODO: Define the target platform for the core +* +* LIMITATIONS: +* - Limitation 01 +* - Limitation 02 +* +* POSSIBLE IMPROVEMENTS: +* - Improvement 01 +* - Improvement 02 +* +* ADDITIONAL NOTES: +* - TRACELOG() function is located in raylib [utils] module +* +* CONFIGURATION: +* #define RCORE_PLATFORM_CUSTOM_FLAG +* Custom flag for rcore on target platform -not used- +* +* DEPENDENCIES: +* - +* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +// TODO: Include the platform specific libraries + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct { + // TODO: Define the platform specific variables required + +} PlatformData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context + +static PlatformData platform = { 0 }; // Platform specific data + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +bool InitGraphicsDevice(void); // Initialize graphics device + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +bool WindowShouldClose(void) +{ + if (CORE.Window.ready) return CORE.Window.shouldClose; + else return true; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + TRACELOG(LOG_WARNING, "ToggleFullscreen() not available on target platform"); +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + TRACELOG(LOG_WARNING, "ToggleBorderlessWindowed() not available on target platform"); +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MaximizeWindow() not available on target platform"); +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform"); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + TRACELOG(LOG_WARNING, "RestoreWindow() not available on target platform"); +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + TRACELOG(LOG_WARNING, "SetWindowState() not available on target platform"); +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + TRACELOG(LOG_WARNING, "ClearWindowState() not available on target platform"); +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform"); +} + +// Set icon for window +void SetWindowIcons(Image *images, int count) +{ + TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform"); +} + +// Set title for window +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform"); +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform"); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + TRACELOG(LOG_WARNING, "SetWindowSize() not available on target platform"); +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + TRACELOG(LOG_WARNING, "SetWindowOpacity() not available on target platform"); +} + +// Set window focused +void SetWindowFocused(void) +{ + TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform"); +} + +// Get native window handle +void *GetWindowHandle(void) +{ + TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform"); + return NULL; +} + +// Get number of monitors +int GetMonitorCount(void) +{ + TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform"); + return 1; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + TRACELOG(LOG_WARNING, "GetCurrentMonitor() not implemented on target platform"); + return 0; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorWidth() not implemented on target platform"); + return 0; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorHeight() not implemented on target platform"); + return 0; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth() not implemented on target platform"); + return 0; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight() not implemented on target platform"); + return 0; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorRefreshRate() not implemented on target platform"); + return 0; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorName() not implemented on target platform"); + return ""; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + TRACELOG(LOG_WARNING, "GetWindowPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get window scale DPI factor for current monitor +Vector2 GetWindowScaleDPI(void) +{ + TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform"); + return (Vector2){ 1.0f, 1.0f }; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + TRACELOG(LOG_WARNING, "SetClipboardText() not implemented on target platform"); +} + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ + TRACELOG(LOG_WARNING, "GetClipboardText() not implemented on target platform"); + return NULL; +} + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + + TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform"); + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + CORE.Input.Mouse.cursorHidden = false; +} + +// Hide mouse cursor +void HideCursor(void) +{ + CORE.Input.Mouse.cursorHidden = true; +} + +// Enable cursor (unlock cursor) +void EnableCursor(void) +{ + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + CORE.Input.Mouse.cursorHidden = false; +} + +// Disable cursor (lock cursor) +void DisableCursor(void) +{ + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + CORE.Input.Mouse.cursorHidden = true; +} + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ + eglSwapBuffers(platform.device, platform.surface); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds since InitTimer() +double GetTime(void) +{ + double time = 0.0; + + struct timespec ts = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long long nanoSeconds = (unsigned long long)ts.tv_sec*1000000000LLU + (unsigned long long)ts.tv_nsec; + + time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() + + return time; +} + +// Open URL with default system browser (if available) +// WARNING: This function is only safe to use if you control the URL given, +// a user could craft a malicious string to perform and undesired action +// NOTE: Some safety checks have been added to mitigate security issues +void OpenURL(const char *url) +{ + // Security check to (partially) avoid malicious code + if ((strchr(url, '\'') != NULL) || (strchr(url, '\"') != NULL)) + { + // Filter characters: ' and " + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'\"] characters"); + } + else if ((strncmp(url, "http://", 7) != 0) && (strncmp(url, "https://", 8) != 0)) + { + // Only allow URL starting with "http://" or "https://" protocols + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL must start with 'http://' or 'https://' protocols"); + } + else + { + // TODO: Load url using default browser + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + TRACELOG(LOG_WARNING, "SetGamepadMappings() not implemented on target platform"); + return 0; +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + TRACELOG(LOG_WARNING, "SetGamepadVibration() not implemented on target platform"); +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; +} + +// Set mouse cursor +void SetMouseCursor(int cursor) +{ + TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform"); +} + +// Get physical key name. +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + +// Register all input events +void PollInputEvents(void) +{ +#if SUPPORT_GESTURES_SYSTEM + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + + // Reset key repeats + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN + //CORE.Input.Gamepad.axisCount = 0; + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Reset touch positions + // TODO: It resets on target platform the mouse position and not filled again until a move-event, + // so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed! + //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; + + // Register previous keys states + // NOTE: Android supports up to 260 keys + for (int i = 0; i < 260; i++) + { + CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // TODO: Poll input events for current platform +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + // TODO: Initialize graphic device: display/window + // It usually requires setting up the platform display system configuration + // and connexion with the GPU through some system graphic API + // raylib uses OpenGL so, platform should create that kind of connection + // Below example illustrates that process using EGL library + //---------------------------------------------------------------------------- + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) + { + // TODO: Enable MSAA + + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); + } + + // TODO: Init display and graphic device + + // TODO: Check display, device and context activation + bool result = true; + if (result) + { + CORE.Window.ready = true; + + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + } + else + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); + return -1; + } + //---------------------------------------------------------------------------- + + // If everything worked as expected, continue + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully %s", + FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)? "(HighDPI)" : ""); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + + // TODO: Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions + //---------------------------------------------------------------------------- + rlLoadExtensions(eglGetProcAddress); + //---------------------------------------------------------------------------- + + // TODO: Initialize input events system + // It could imply keyboard, mouse, gamepad, touch... + // Depending on the platform libraries/SDK it could use a callback mechanism + // For system events and inputs evens polling on a per-frame basis, use PollInputEvents() + //---------------------------------------------------------------------------- + // ... + //---------------------------------------------------------------------------- + + // TODO: Initialize timing system + //---------------------------------------------------------------------------- + InitTimer(); + //---------------------------------------------------------------------------- + + // TODO: Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- + + TRACELOG(LOG_INFO, "PLATFORM: CUSTOM: Initialized successfully"); + + return 0; +} + +// Close platform +void ClosePlatform(void) +{ + // TODO: De-initialize graphics, inputs and more +} + +// EOF diff --git a/src/libraries/raylib/raylib/src/platforms/rcore_web.c b/src/libraries/raylib/raylib/src/platforms/rcore_web.c new file mode 100644 index 0000000..a507583 --- /dev/null +++ b/src/libraries/raylib/raylib/src/platforms/rcore_web.c @@ -0,0 +1,1948 @@ +/********************************************************************************************** +* +* rcore_web - Functions to manage window, graphics device and inputs +* +* PLATFORM: WEB +* - HTML5 (WebAssembly) +* +* LIMITATIONS: +* - Limitation 01 +* - Limitation 02 +* +* POSSIBLE IMPROVEMENTS: +* - Replace glfw3 dependency by direct browser API calls (same as library_glfw3.js) +* +* CONFIGURATION: +* #define RCORE_PLATFORM_CUSTOM_FLAG +* Custom flag for rcore on target platform -not used- +* +* DEPENDENCIES: +* - emscripten: Allow interaction between browser API and C +* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 + // NOTE: Already provided by rlgl implementation (on glad.h) +#include "GLFW/glfw3.h" // GLFW3: Windows, OpenGL context and Input management + +#include // Emscripten functionality for C +#include // Emscripten HTML5 library + +#include // Required for: timespec, nanosleep(), select() - POSIX + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// TODO: HACK: Added flag if not provided by GLFW when using external library +// Latest GLFW release (GLFW 3.3.8) does not implement this flag, it was added for 3.4.0-dev +#if !defined(GLFW_MOUSE_PASSTHROUGH) + #define GLFW_MOUSE_PASSTHROUGH 0x0002000D +#endif + +#if (_POSIX_C_SOURCE < 199309L) + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext. +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct { + GLFWwindow *handle; // GLFW window handle (graphic device) + bool ourFullscreen; // Internal var to filter our handling of fullscreen vs the user handling of fullscreen + int unmaximizedWidth; // Internal var to store the unmaximized window (canvas) width + int unmaximizedHeight; // Internal var to store the unmaximized window (canvas) height + + char canvasId[64]; // Keep current canvas id where wasm app is running + // NOTE: Useful when trying to run multiple wasms in different canvases in same webpage +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + unsigned int *pixels; // Pointer to pixel data buffer (RGBA 32bit format) +#endif +} PlatformData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context + +static PlatformData platform = { 0 }; // Platform specific data + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static const char cursorLUT[11][12] = { + "default", // 0 MOUSE_CURSOR_DEFAULT + "default", // 1 MOUSE_CURSOR_ARROW + "text", // 2 MOUSE_CURSOR_IBEAM + "crosshair", // 3 MOUSE_CURSOR_CROSSHAIR + "pointer", // 4 MOUSE_CURSOR_POINTING_HAND + "ew-resize", // 5 MOUSE_CURSOR_RESIZE_EW + "ns-resize", // 6 MOUSE_CURSOR_RESIZE_NS + "nwse-resize", // 7 MOUSE_CURSOR_RESIZE_NWSE + "nesw-resize", // 8 MOUSE_CURSOR_RESIZE_NESW + "move", // 9 MOUSE_CURSOR_RESIZE_ALL + "not-allowed" // 10 MOUSE_CURSOR_NOT_ALLOWED +}; + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +void ClosePlatform(void); // Close platform + +// Error callback event +static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error + +// Window callbacks events +static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 Window Size Callback, runs when window is resized +static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 Window Iconify Callback, runs when window is minimized/restored +//static void WindowMaximizeCallback(GLFWwindow *window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized +static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 Window Focus Callback, runs when window get/lose focus +static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window +static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley); // GLFW3 Window Content Scale Callback, runs when window changes scale + +// Input callbacks events +static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed +static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value) +static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed +static void MouseMoveCallback(GLFWwindow *window, double x, double y); // GLFW3 Mouse Move Callback, runs on mouse move +static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Mouse Scrolling Callback, runs on mouse wheel +static void MouseEnterCallback(GLFWwindow *window, int enter); // GLFW3 Mouse Enter Callback, cursor enters client area + +// Emscripten window callback events +static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); +//static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData); +static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData); +static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData); +static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData); + +// Emscripten input callback events +//static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyboardEvent, void *userData); +static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); +static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); +static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPointerlockChangeEvent *pointerlockChangeEvent, void *userData); +static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); + +// JS: Set the canvas id provided by the module configuration +EM_JS(void, SetCanvasIdJs, (char *out, int outSize), { + var canvasId = "#" + Module.canvas.id; + stringToUTF8(canvasId, out, outSize); +}); + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +// This will always return false on a web-build as web builds have no control over this functionality +// Sleep is handled in EndDrawing() for synchronous code +bool WindowShouldClose(void) +{ + // Emscripten Asyncify is required to run synchronous code in asynchronous JS + // REF: https://emscripten.org/docs/porting/asyncify.html + + // WindowShouldClose() is not called on a web-ready raylib application if using emscripten_set_main_loop() + // and encapsulating one frame execution on a UpdateDrawFrame() function, + // allowing the browser to manage execution asynchronously + + // Optionally, time to give-control-back-to-browser can be managed here, + // but it seems below line could generate stuttering on some browsers + emscripten_sleep(12); + + return false; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + platform.ourFullscreen = true; + bool enterFullscreen = false; + + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) enterFullscreen = false; + else if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) enterFullscreen = true; + else + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0); + if (canvasStyleWidth > canvasWidth) enterFullscreen = false; + else enterFullscreen = true; + } + + EM_ASM(document.exitFullscreen();); + + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + else enterFullscreen = true; + + if (enterFullscreen) + { + // NOTE: The setTimeouts handle the browser mode change delay + EM_ASM( + setTimeout(function(){ + Module.requestFullscreen(false, false); + }, 100); + ); + + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + + // NOTE: Old notes below: + /* + EM_ASM + ( + // This strategy works well while using raylib minimal web shell for emscripten, + // it re-scales the canvas to fullscreen using monitor resolution, for tools this + // is a good strategy but maybe games prefer to keep current canvas resolution and + // display it in fullscreen, adjusting monitor resolution if possible + if (document.fullscreenElement) document.exitFullscreen(); + else Module.requestFullscreen(true, true); //false, true); + ); + */ + // EM_ASM(Module.requestFullscreen(false, false);); + /* + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + { + // Option 1: Request fullscreen for the canvas element + // This option does not seem to work at all: + // emscripten_request_pointerlock() and emscripten_request_fullscreen() are affected by web security, + // the user must click once on the canvas to hide the pointer or transition to full screen + //emscripten_request_fullscreen(platform.canvasId, false); + + // Option 2: Request fullscreen for the canvas element with strategy + // This option does not seem to work at all + // REF: https://github.com/emscripten-core/emscripten/issues/5124 + // EmscriptenFullscreenStrategy strategy = { + // .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + // .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, + // .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, + // .canvasResizedCallback = EmscriptenWindowResizedCallback, + // .canvasResizedCallbackUserData = NULL + // }; + //emscripten_request_fullscreen_strategy(platform.canvasId, EM_FALSE, &strategy); + + // Option 3: Request fullscreen for the canvas element with strategy + // It works as expected but only inside the browser (client area) + EmscriptenFullscreenStrategy strategy = { + .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, + .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, + .canvasResizedCallback = EmscriptenWindowResizedCallback, + .canvasResizedCallbackUserData = NULL + }; + emscripten_enter_soft_fullscreen(platform.canvasId, &strategy); + + int width = 0; + int height = 0; + emscripten_get_canvas_element_size(platform.canvasId, &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); + + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + else + { + //emscripten_exit_fullscreen(); + //emscripten_exit_soft_fullscreen(); + + int width, height; + emscripten_get_canvas_element_size(platform.canvasId, &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); + + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + */ +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + platform.ourFullscreen = true; + bool enterBorderless = false; + + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) enterBorderless = false; + else if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) enterBorderless = true; + else + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int screenWidth = EM_ASM_INT( { return screen.width; }, 0); + if (screenWidth == canvasWidth) enterBorderless = false; + else enterBorderless = true; + } + + EM_ASM(document.exitFullscreen();); + + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + else enterBorderless = true; + + if (enterBorderless) + { + // 1. The setTimeouts handle the browser mode change delay + // 2. The style unset handles the possibility of a width="value%" like on the default shell.html file + EM_ASM + ( + const canvasId = UTF8ToString($0); + setTimeout(function() + { + Module.requestFullscreen(false, true); + setTimeout(function() + { + document.querySelector(canvasId).style.width="unset"; + }, 100); + }, 100); + , platform.canvasId); + FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + if ((glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) && !(FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED))) + { + platform.unmaximizedWidth = CORE.Window.screen.width; + platform.unmaximizedHeight = CORE.Window.screen.height; + + const int tabWidth = EM_ASM_INT( return window.innerWidth; ); + const int tabHeight = EM_ASM_INT( return window.innerHeight; ); + + if (tabWidth && tabHeight) glfwSetWindowSize(platform.handle, tabWidth, tabHeight); + + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform"); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + if ((glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) && (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED))) + { + if (platform.unmaximizedWidth && platform.unmaximizedHeight) glfwSetWindowSize(platform.handle, platform.unmaximizedWidth, platform.unmaximizedHeight); + + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); + + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_VSYNC_HINT) not available on target platform"); + } + + // State change: FLAG_BORDERLESS_WINDOWED_MODE + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + // NOTE: Window state flag updated inside ToggleBorderlessWindowed() function + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0); + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) || canvasStyleWidth > canvasWidth) ToggleBorderlessWindowed(); + } + else ToggleBorderlessWindowed(); + } + + // State change: FLAG_FULLSCREEN_MODE + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + // NOTE: Window state flag updated inside ToggleFullscreen() function + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int screenWidth = EM_ASM_INT( { return screen.width; }, 0); + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) || screenWidth == canvasWidth ) ToggleFullscreen(); + } + else ToggleFullscreen(); + } + + // State change: FLAG_WINDOW_RESIZABLE + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) && (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE))) + { + glfwSetWindowAttrib(platform.handle, GLFW_RESIZABLE, GLFW_TRUE); + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); + } + + // State change: FLAG_WINDOW_UNDECORATED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); + } + + // State change: FLAG_WINDOW_HIDDEN + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); + } + + // State change: FLAG_WINDOW_MINIMIZED + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); + } + + // State change: FLAG_WINDOW_MAXIMIZED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED) != FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED))) + { + if (glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) + { + platform.unmaximizedWidth = CORE.Window.screen.width; + platform.unmaximizedHeight = CORE.Window.screen.height; + + const int tabWidth = EM_ASM_INT( return window.innerWidth; ); + const int tabHeight = EM_ASM_INT( return window.innerHeight; ); + + if (tabWidth && tabHeight) glfwSetWindowSize(platform.handle, tabWidth, tabHeight); + + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNFOCUSED) not available on target platform"); + } + + // State change: FLAG_WINDOW_TOPMOST + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TOPMOST) not available on target platform"); + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_ALWAYS_RUN) not available on target platform"); + } + + // The following states can not be changed after window creation + // NOTE: Review for PLATFORM_WEB + + // State change: FLAG_WINDOW_TRANSPARENT + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); + } + + // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); + } + + // State change: FLAG_MSAA_4X_HINT + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); + } + + // State change: FLAG_INTERLACED_HINT + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_INTERLACED_HINT) not available on target platform"); + } +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_VSYNC_HINT) not available on target platform"); + } + + // State change: FLAG_BORDERLESS_WINDOWED_MODE + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int screenWidth = EM_ASM_INT( { return screen.width; }, 0); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE) || (screenWidth == canvasWidth)) EM_ASM(document.exitFullscreen();); + } + + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + + // State change: FLAG_FULLSCREEN_MODE + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE) || (canvasStyleWidth > canvasWidth)) EM_ASM(document.exitFullscreen();); + } + + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + + // State change: FLAG_WINDOW_RESIZABLE + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) && (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE))) + { + glfwSetWindowAttrib(platform.handle, GLFW_RESIZABLE, GLFW_FALSE); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); + } + + // State change: FLAG_WINDOW_HIDDEN + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); + } + + // State change: FLAG_WINDOW_MINIMIZED + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); + } + + // State change: FLAG_WINDOW_MAXIMIZED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED))) + { + if (glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) + { + if (platform.unmaximizedWidth && platform.unmaximizedHeight) glfwSetWindowSize(platform.handle, platform.unmaximizedWidth, platform.unmaximizedHeight); + + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } + } + + // State change: FLAG_WINDOW_UNDECORATED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNFOCUSED) not available on target platform"); + } + + // State change: FLAG_WINDOW_TOPMOST + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TOPMOST) not available on target platform"); + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_ALWAYS_RUN) not available on target platform"); + } + + // The following states can not be changed after window creation + // NOTE: Review for PLATFORM_WEB + + // State change: FLAG_WINDOW_TRANSPARENT + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); + } + + // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); + } + + // State change: FLAG_MSAA_4X_HINT + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); + } + + // State change: FLAG_INTERLACED_HINT + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_INTERLACED_HINT) not available on target platform"); + } +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform"); +} + +// Set icon for window, multiple images +void SetWindowIcons(Image *images, int count) +{ + TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform"); +} + +// Set title for window +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; + emscripten_set_window_title(title); +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform"); +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform"); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; + + // Trigger the resize event once to update the window minimum width and height + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; + + // Trigger the resize event once to update the window maximum width and height + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + glfwSetWindowSize(platform.handle, width, height); +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + if (opacity >= 1.0f) opacity = 1.0f; + else if (opacity <= 0.0f) opacity = 0.0f; + EM_ASM({ Module.canvas.style.opacity = $0; }, opacity); +} + +// Set window focused +void SetWindowFocused(void) +{ + TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform"); +} + +// Get native window handle +void *GetWindowHandle(void) +{ + TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform"); + return NULL; +} + +// Get number of monitors +int GetMonitorCount(void) +{ + TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform"); + return 1; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + TRACELOG(LOG_WARNING, "GetCurrentMonitor() not implemented on target platform"); + return 0; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + // NOTE: Returned value is limited to the current monitor where the browser window is located + int width = 0; + width = EM_ASM_INT( { return screen.width; }, 0); + return width; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + // NOTE: Returned value is limited to the current monitor where the browser window is located + int height = 0; + height = EM_ASM_INT( { return screen.height; }, 0); + return height; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth() not implemented on target platform"); + return 0; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight() not implemented on target platform"); + return 0; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorRefreshRate() not implemented on target platform"); + return 0; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorName() not implemented on target platform"); + return ""; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + // NOTE: Returned position is relative to the current monitor where the browser window is located + Vector2 position = { 0, 0 }; + position.x = (float)EM_ASM_INT( { return window.screenX; }, 0); + position.y = (float)EM_ASM_INT( { return window.screenY; }, 0); + return position; +} + +// Get window scale DPI factor for current monitor +Vector2 GetWindowScaleDPI(void) +{ + // NOTE: Returned scale is relative to the current monitor where the browser window is located + Vector2 scale = { 1.0f, 1.0f }; + scale.x = (float)EM_ASM_DOUBLE( { return window.devicePixelRatio; } ); + scale.y = scale.x; + return scale; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + // Security check to (partially) avoid malicious code + if (strchr(text, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided Clipboard could be potentially malicious, avoid [\'] character"); + else EM_ASM({ navigator.clipboard.writeText(UTF8ToString($0)); }, text); +} + +// Async EM_JS to be able to await clickboard read asynchronous function +EM_ASYNC_JS(void, RequestClipboardData, (void), { + if (navigator.clipboard && window.isSecureContext) + { + let items = await navigator.clipboard.read(); + for (const item of items) + { + // Check if this item contains plain text or image + if (item.types.includes("text/plain")) + { + const blob = await item.getType("text/plain"); + const text = await blob.text(); + window._lastClipboardString = text; + } + else if (item.types.find(t => t.startsWith("image/"))) + { + const blob = await item.getType(item.types.find(t => t.startsWith("image/"))); + const bitmap = await createImageBitmap(blob); + + const canvas = document.createElement('canvas'); + canvas.width = bitmap.width; + canvas.height = bitmap.height; + const ctx = canvas.getContext('2d'); + ctx.drawImage(bitmap, 0, 0); + + const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + + // Store image and data for the Fetch function + window._lastImgWidth = canvas.width; + window._lastImgHeight = canvas.height; + window._lastImgData = imgData; + } + } + } + else console.warn("Clipboard read() requires HTTPS/Localhost"); +}); + +// Returns the string created by RequestClipboardData from JS memory to Emscripten C memory +EM_JS(char *, GetLastPastedText, (void), { + var str = window._lastClipboardString || ""; + var length = lengthBytesUTF8(str) + 1; + if (length > 1) + { + var ptr = _malloc(length); + stringToUTF8(str, ptr, length); + return ptr; + } + return 0; +}); + +// Returns the image created by RequestClipboardData from JS memory to Emscripten C memory +EM_JS(unsigned char *, GetLastPastedImage, (int *width, int *height), { + if (window._lastImgData) + { + const data = window._lastImgData; + if (data.length > 0) + { + const ptr = _malloc(data.length); + HEAPU8.set(data, ptr); + + // Set the width and height via the pointers passed from C + // HEAP32 handles the 4-byte integers + if (width) setValue(width, window._lastImgWidth, 'i32'); + if (height) setValue(height, window._lastImgHeight, 'i32'); + + // Clear the JS buffer so there is no need to fetch the same image twice + window._lastImgData = null; + + return ptr; + } + } + + return 0; +}); + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ + RequestClipboardData(); + return GetLastPastedText(); +} + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + int w = 0, h = 0; + RequestClipboardData(); + unsigned char* data = GetLastPastedImage(&w, &h); + if (data != NULL) { + image.data = data; + image.width = w; + image.height = h; + image.mipmaps = 1; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + } + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + if (CORE.Input.Mouse.cursorHidden) + { + EM_ASM( { Module.canvas.style.cursor = UTF8ToString($0); }, cursorLUT[CORE.Input.Mouse.cursor]); + + CORE.Input.Mouse.cursorHidden = false; + } +} + +// Hide mouse cursor +void HideCursor(void) +{ + if (!CORE.Input.Mouse.cursorHidden) + { + EM_ASM(Module.canvas.style.cursor = 'none';); + + CORE.Input.Mouse.cursorHidden = true; + } +} + +// Enable cursor (unlock cursor) +void EnableCursor(void) +{ + emscripten_exit_pointerlock(); + + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + // NOTE: CORE.Input.Mouse.cursorLocked handled by EmscriptenPointerlockCallback() +} + +// Disable cursor (lock cursor) +void DisableCursor(void) +{ + emscripten_request_pointerlock(platform.canvasId, 1); + + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + // NOTE: CORE.Input.Mouse.cursorLocked handled by EmscriptenPointerlockCallback() +} + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + // Update framebuffer + rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels); + + // Copy framebuffer data into canvas + EM_ASM({ + const width = $0; + const height = $1; + const ptr = $2; + + // Get canvas and 2d context created + const canvas = Module.canvas; + const ctx = canvas.getContext('2d'); + + if (!Module.__img || (Module.__img.width !== width) || (Module.__img.height !== height)) + { + Module.__img = ctx.createImageData(width, height); + } + + const src = HEAPU8.subarray(ptr, ptr + width*height*4); // RGBA (4 bytes) + Module.__img.data.set(src); + ctx.putImageData(Module.__img, 0, 0); + + }, CORE.Window.screen.width, CORE.Window.screen.height, platform.pixels); +#else + glfwSwapBuffers(platform.handle); +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds since InitTimer() +double GetTime(void) +{ + double time = glfwGetTime(); // Elapsed time since glfwInit() + + return time; +} + +// Open URL with default system browser (if available) +// NOTE: This function is only safe to use if the provided URL is safe +// A user could craft a malicious string performing another action +// Avoid calling this function with user input non-validated strings +void OpenURL(const char *url) +{ + // Security check to (partially) avoid malicious code on target platform + if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character"); + else emscripten_run_script(TextFormat("window.open('%s', '_blank')", url)); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + TRACELOG(LOG_INFO, "SetGamepadMappings not implemented in rcore_web.c"); + + return 0; +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (duration > 0.0f)) + { + if (leftMotor < 0.0f) leftMotor = 0.0f; + if (leftMotor > 1.0f) leftMotor = 1.0f; + if (rightMotor < 0.0f) rightMotor = 0.0f; + if (rightMotor > 1.0f) rightMotor = 1.0f; + if (duration > MAX_GAMEPAD_VIBRATION_TIME) duration = MAX_GAMEPAD_VIBRATION_TIME; + duration *= 1000.0f; // Convert duration to ms + + // NOTE: At the moment (2024.10.21) Chrome, Edge, Opera, Safari, Android Chrome, Android Webview only support the vibrationActuator API, + // and Firefox only supports the hapticActuators API + EM_ASM({ + try + { + navigator.getGamepads()[$0].vibrationActuator.playEffect('dual-rumble', { startDelay: 0, duration: $3, weakMagnitude: $1, strongMagnitude: $2 }); + } + catch (e) + { + try + { + navigator.getGamepads()[$0].hapticActuators[0].pulse($2, $3); + } + catch (e) { } + } + }, gamepad, leftMotor, rightMotor, duration); + } +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; + + if (CORE.Input.Mouse.cursorLocked) CORE.Input.Mouse.lockedPosition = CORE.Input.Mouse.currentPosition; + + // NOTE: emscripten not implemented + glfwSetCursorPos(platform.handle, CORE.Input.Mouse.currentPosition.x, CORE.Input.Mouse.currentPosition.y); +} + +// Set mouse cursor +void SetMouseCursor(int cursor) +{ + if (CORE.Input.Mouse.cursor != cursor) + { + if (!CORE.Input.Mouse.cursorLocked) EM_ASM( { Module.canvas.style.cursor = UTF8ToString($0); }, cursorLUT[cursor]); + + CORE.Input.Mouse.cursor = cursor; + } +} + +// Get physical key name +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + +// Register all input events +void PollInputEvents(void) +{ +#if SUPPORT_GESTURES_SYSTEM + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN + //CORE.Input.Gamepad.axisCount = 0; + + // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) + + // Register previous keys states + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) + { + CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // Register previous mouse states + for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + + // Register previous mouse wheel state + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; + + // Register previous mouse position + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Reset touch positions + // TODO: It resets on target platform the mouse position and not filled again until a move-event, + // so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed! + //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; + + // Gamepad support using emscripten API + // NOTE: GLFW3 joystick functionality not available in web + + // Get number of gamepads connected + int numGamepads = 0; + if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) numGamepads = emscripten_get_num_gamepads(); + + for (int i = 0; (i < numGamepads) && (i < MAX_GAMEPADS); i++) + { + // Register previous gamepad button states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; + + EmscriptenGamepadEvent gamepadState = { 0 }; + + int result = emscripten_get_gamepad_status(i, &gamepadState); + + if (result == EMSCRIPTEN_RESULT_SUCCESS) + { + // Register buttons data for every connected gamepad + for (int j = 0; (j < gamepadState.numButtons) && (j < MAX_GAMEPAD_BUTTONS); j++) + { + GamepadButton button = -1; + + // Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface + switch (j) + { + case 0: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case 1: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case 2: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + case 3: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case 4: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case 5: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + case 6: button = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break; + case 7: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break; + case 8: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case 9: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + case 10: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case 11: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + case 12: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case 13: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case 14: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + case 15: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + default: break; + } + + if (button + 1 != 0) // Check for valid button + { + if (gamepadState.digitalButton[j] == 1) + { + CORE.Input.Gamepad.currentButtonState[i][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + } + else CORE.Input.Gamepad.currentButtonState[i][button] = 0; + } + + //TRACELOG(LOG_DEBUG, "INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); + } + + // Register axis data for every connected gamepad + for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXES); j++) + { + CORE.Input.Gamepad.axisState[i][j] = gamepadState.axis[j]; + } + + CORE.Input.Gamepad.axisCount[i] = gamepadState.numAxes; + } + } + + CORE.Window.resizedLastFrame = false; +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + SetCanvasIdJs(platform.canvasId, 64); // Get the current canvas id + + glfwSetErrorCallback(ErrorCallback); + + // Initialize GLFW internal global state + int result = glfwInit(); + if (result == GLFW_FALSE) { TRACELOG(LOG_WARNING, "GLFW: Failed to initialize GLFW"); return -1; } + + // Initialize graphic device: display/window and graphic context + //---------------------------------------------------------------------------- + glfwDefaultWindowHints(); // Set default windows hints + // glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits + // glfwWindowHint(GLFW_GREEN_BITS, 8); // Framebuffer green color component bits + // glfwWindowHint(GLFW_BLUE_BITS, 8); // Framebuffer blue color component bits + // glfwWindowHint(GLFW_ALPHA_BITS, 8); // Framebuffer alpha color component bits + // glfwWindowHint(GLFW_DEPTH_BITS, 24); // Depthbuffer bits + // glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window + // glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API + // glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers + + // Check window creation flags + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window + else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window + else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window + else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable + + // Disable FLAG_WINDOW_MINIMIZED, not supported on initialization + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); + + // Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); + else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); + else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); + + // NOTE: Some GLFW flags are not supported on HTML5 + // e.g.: GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_MOUSE_PASSTHROUGH + + // Scale content area based on the monitor content scale where window is placed on + // NOTE: This feature requires emscripten 3.1.51 + //if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); + //else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) + { + // NOTE: MSAA is only enabled for main framebuffer, not user-created FBOs + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); + glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0 + } + + // NOTE: When asking for an OpenGL context version, most drivers provide the highest supported version + // with backward compatibility to older OpenGL versions + // For example, if using OpenGL 1.1, driver can provide a 4.3 backwards compatible context + + // Check selection OpenGL version + if (rlGetVersion() == RL_OPENGL_21) + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint) + } + else if (rlGetVersion() == RL_OPENGL_33) + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) + // Profiles Hint, only OpenGL 3.3 and above + // Possible values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Forward Compatibility Hint: Only 3.3 and above! + // glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context + } + else if (rlGetVersion() == RL_OPENGL_43) + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); +#if RLGL_ENABLE_OPENGL_DEBUG_CONTEXT + glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Enable OpenGL Debug Context +#endif + } + else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); + } + else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context + { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); + } + + // NOTE: Getting video modes is not implemented in emscripten GLFW3 version + CORE.Window.display.width = CORE.Window.screen.width; + CORE.Window.display.height = CORE.Window.screen.height; + + // Init fullscreen toggle required var: + platform.ourFullscreen = false; + +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + // Avoid creating a WebGL canvas, avoid calling glfwCreateWindow() + emscripten_set_canvas_element_size(platform.canvasId, CORE.Window.screen.width, CORE.Window.screen.height); + EM_ASM({ + const canvas = document.querySelector(UTF8ToString($0)); + Module.canvas = canvas; + }, platform.canvasId); + + // Load memory framebuffer with desired screen size + // NOTE: Despite using a software framebuffer for blitting, GLFW still creates a WebGL canvas, + // but it is not being used, on SwapScreenBuffer() the pure software renderer is used + // TODO: Consider requesting another type of canvas, not a WebGL one --> Replace GLFW-web by Emscripten? + platform.pixels = (unsigned int *)RL_CALLOC(CORE.Window.screen.width*CORE.Window.screen.height, sizeof(unsigned int)); +#else + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + { + // Remember center for switchinging from fullscreen to window + if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) + { + // If screen width/height equal to the display, it's not possible to + // calculate the window position for toggling full-screened/windowed + CORE.Window.position.x = CORE.Window.display.width/4; + CORE.Window.position.y = CORE.Window.display.height/4; + } + else + { + CORE.Window.position.x = CORE.Window.display.width/2 - CORE.Window.screen.width/2; + CORE.Window.position.y = CORE.Window.display.height/2 - CORE.Window.screen.height/2; + } + + if (CORE.Window.position.x < 0) CORE.Window.position.x = 0; + if (CORE.Window.position.y < 0) CORE.Window.position.y = 0; + + // Obtain recommended CORE.Window.display.width/CORE.Window.display.height from a valid videomode for the monitor + int count = 0; + const GLFWvidmode *modes = glfwGetVideoModes(glfwGetPrimaryMonitor(), &count); + + // Get closest video mode to desired CORE.Window.screen.width/CORE.Window.screen.height + for (int i = 0; i < count; i++) + { + if ((unsigned int)modes[i].width >= CORE.Window.screen.width) + { + if ((unsigned int)modes[i].height >= CORE.Window.screen.height) + { + CORE.Window.display.width = modes[i].width; + CORE.Window.display.height = modes[i].height; + break; + } + } + } + + TRACELOG(LOG_WARNING, "SYSTEM: Closest fullscreen videomode: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + + platform.handle = glfwCreateWindow(CORE.Window.display.width, CORE.Window.display.height, (CORE.Window.title != 0)? CORE.Window.title : " ", glfwGetPrimaryMonitor(), NULL); + + // NOTE: Full-screen change, not working properly... + // glfwSetWindowMonitor(platform.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + } + else + { + // No-fullscreen window creation + platform.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL); + + if (platform.handle) + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + } + } + + if (!platform.handle) + { + glfwTerminate(); + TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window"); + return -1; + } +#endif + + // WARNING: glfwCreateWindow() title doesn't work with emscripten + emscripten_set_window_title((CORE.Window.title != 0)? CORE.Window.title : " "); + + // Set window callback events + glfwSetWindowSizeCallback(platform.handle, WindowSizeCallback); + glfwSetWindowIconifyCallback(platform.handle, WindowIconifyCallback); + glfwSetWindowFocusCallback(platform.handle, WindowFocusCallback); + glfwSetDropCallback(platform.handle, WindowDropCallback); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + // Window content (framebuffer) scale callback + glfwSetWindowContentScaleCallback(platform.handle, WindowContentScaleCallback); + } + + // Set input callback events + glfwSetKeyCallback(platform.handle, KeyCallback); + glfwSetCharCallback(platform.handle, CharCallback); + glfwSetMouseButtonCallback(platform.handle, MouseButtonCallback); + glfwSetCursorPosCallback(platform.handle, MouseMoveCallback); + glfwSetScrollCallback(platform.handle, MouseScrollCallback); + glfwSetCursorEnterCallback(platform.handle, MouseEnterCallback); + + glfwMakeContextCurrent(platform.handle); + result = true; // TODO: WARNING: glfwGetError(NULL); symbol can not be found in Web + + // Check context activation + if (result == true) //(result != GLFW_NO_WINDOW_CONTEXT) && (result != GLFW_PLATFORM_ERROR)) + { + CORE.Window.ready = true; + + int fbWidth = CORE.Window.screen.width; + int fbHeight = CORE.Window.screen.height; + + CORE.Window.render.width = fbWidth; + CORE.Window.render.height = fbHeight; + CORE.Window.currentFbo.width = fbWidth; + CORE.Window.currentFbo.height = fbHeight; + + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully %s", + FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)? "(HighDPI)" : ""); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + } + else + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); + return -1; + } + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); + + // If graphic device is no properly initialized, end program + if (!CORE.Window.ready) { TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); return -1; } + + // Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions + rlLoadExtensions(glfwGetProcAddress); + //---------------------------------------------------------------------------- + + // Initialize events callbacks + //---------------------------------------------------------------------------- + // Setup window events callbacks + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenFullscreenChangeCallback); + emscripten_set_blur_callback(platform.canvasId, platform.handle, 1, EmscriptenFocusCallback); + emscripten_set_focus_callback(platform.canvasId, platform.handle, 1, EmscriptenFocusCallback); + emscripten_set_visibilitychange_callback(NULL, 1, EmscriptenVisibilityChangeCallback); + + // WARNING: Below resize code was breaking fullscreen mode for sample games and examples, it needs review + // Check fullscreen change events(note this is done on the window since most browsers don't support this on canvas) + // emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); + // Check Resize event (note this is done on the window since most browsers don't support this on canvas) + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); + + // Trigger resize callback to force initial size + EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); + + // Setup input events + // NOTE: Keyboard callbacks only used to consume some events, libglfw.js takes care of the actual input + //emscripten_set_keypress_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); // WRNING: Breaks input + //emscripten_set_keydown_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); + emscripten_set_click_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); + emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenPointerlockCallback); + emscripten_set_mousemove_callback(platform.canvasId, NULL, 1, EmscriptenMouseMoveCallback); + emscripten_set_touchstart_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchend_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchmove_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchcancel_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); + emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback); + //---------------------------------------------------------------------------- + + // Initialize timing system + //---------------------------------------------------------------------------- + InitTimer(); + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- + + TRACELOG(LOG_INFO, "PLATFORM: WEB: Initialized successfully"); + + return 0; +} + +// Close platform +void ClosePlatform(void) +{ + glfwDestroyWindow(platform.handle); + glfwTerminate(); +} + +// GLFW3 callback functions, called on GLFW registered events +//------------------------------------------------------------------------------------------------------- +// GLFW3: Called on errors +static void ErrorCallback(int error, const char *description) +{ + TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s", error, description); +} + +// GLFW3: Called on window resizing, runs when window is resizedLastFrame +static void WindowSizeCallback(GLFWwindow *window, int width, int height) +{ + // Reset viewport and projection matrix for new size + SetupViewport(width, height); + + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + CORE.Window.resizedLastFrame = true; + + if (IsWindowFullscreen()) return; + + // Set current screen size + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + Vector2 windowScaleDPI = GetWindowScaleDPI(); + + CORE.Window.screen.width = (unsigned int)(width/windowScaleDPI.x); + CORE.Window.screen.height = (unsigned int)(height/windowScaleDPI.y); + } + else + { + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + } + + // NOTE: Postprocessing texture is not scaled to new size +} + +// GLFW3: Called on window content (framebuffer) scaled +static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley) +{ + CORE.Window.screenScale = MatrixScale(scalex, scaley, 1.0f); +} + +// GLFW3: Called on windows minimized/restored +static void WindowIconifyCallback(GLFWwindow *window, int iconified) +{ + if (iconified) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was iconified + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was restored +} + +// GLFW3: Called on windows get/lose focus +static void WindowFocusCallback(GLFWwindow *window, int focused) +{ + if (focused) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was focused + else FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window lost focus +} + +// GLFW3: Called on file-drop over the window +static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) +{ + if (count > 0) + { + // In case previous dropped filepaths have not been freed, free them + if (CORE.Window.dropFileCount > 0) + { + for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]); + + RL_FREE(CORE.Window.dropFilepaths); + + CORE.Window.dropFileCount = 0; + CORE.Window.dropFilepaths = NULL; + } + + // WARNING: Paths are freed by GLFW when the callback returns, an internal copy must freed + CORE.Window.dropFileCount = count; + CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *)); + + for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) + { + CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + snprintf(CORE.Window.dropFilepaths[i], MAX_FILEPATH_LENGTH, "%s", paths[i]); + } + } +} + +// GLFW3: Called on keyboard interaction +static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) +{ + if (key < 0) return; // Security check, macOS fn key generates -1 + + // WARNING: GLFW could return GLFW_REPEAT, it needs to be considered as 1 + // to work properly with our implementation (IsKeyDown/IsKeyUp checks) + if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0; + else if (action == GLFW_PRESS) CORE.Input.Keyboard.currentKeyState[key] = 1; + else if (action == GLFW_REPEAT) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1; + + // Check if there is space available in the key queue + if ((CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) && (action == GLFW_PRESS)) + { + // Add character to the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + + // Check the exit key to set close window + if ((key == CORE.Input.Keyboard.exitKey) && (action == GLFW_PRESS)) glfwSetWindowShouldClose(platform.handle, GLFW_TRUE); +} + +// GLFW3: Called on key down interaction, gets equivalent unicode char value for the key +static void CharCallback(GLFWwindow *window, unsigned int key) +{ + //TRACELOG(LOG_DEBUG, "Char Callback: KEY:%i(%c)", key, key); + + // NOTE: Registers any key down considering OS keyboard layout but + // does not detect action events, those should be managed by user... + // REF: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 + // REF: https://www.glfw.org/docs/latest/input_guide.html#input_char + + // Check if there is space available in the queue + if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) + { + // Add character to the queue + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = key; + CORE.Input.Keyboard.charPressedQueueCount++; + } +} + +// GLFW3: Called on mouse button interaction +static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods) +{ + // WARNING: GLFW could only return GLFW_PRESS (1) or GLFW_RELEASE (0) for now, + // but future releases may add more actions (i.e. GLFW_REPEAT) + CORE.Input.Mouse.currentButtonState[button] = action; + CORE.Input.Touch.currentTouchState[button] = action; + +#if SUPPORT_GESTURES_SYSTEM && SUPPORT_MOUSE_GESTURES + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_ACTION_DOWN; + else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_ACTION_UP; + + // NOTE: TOUCH_ACTION_MOVE event is registered in MouseMoveCallback() + + // Assign a pointer ID + gestureEvent.pointId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = GetMousePosition(); + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures-system for processing + // Prevent calling ProcessGestureEvent() when Emscripten is present and there's a touch gesture, so EmscriptenTouchCallback() can handle it itself + if (GetMouseX() != 0 || GetMouseY() != 0) ProcessGestureEvent(gestureEvent); + +#endif +} + +// GLFW3: Called on mouse move +static void MouseMoveCallback(GLFWwindow *window, double x, double y) +{ + // If the pointer is not locked, follow the position + if (!CORE.Input.Mouse.cursorLocked) + { + CORE.Input.Mouse.currentPosition.x = (float)x; + CORE.Input.Mouse.currentPosition.y = (float)y; + CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + } + +#if SUPPORT_GESTURES_SYSTEM && SUPPORT_MOUSE_GESTURES + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + gestureEvent.touchAction = TOUCH_ACTION_MOVE; + + // Assign a pointer ID + gestureEvent.pointId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = CORE.Input.Touch.position[0]; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures-system for processing + ProcessGestureEvent(gestureEvent); +#endif +} + +// GLFW3: Called on mouse wheel scrolling +static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset) +{ + CORE.Input.Mouse.currentWheelMove = (Vector2){ (float)xoffset, (float)yoffset }; +} + +// GLFW3: Called on mouse entering the window +static void MouseEnterCallback(GLFWwindow *window, int enter) +{ + if (enter) CORE.Input.Mouse.cursorOnScreen = true; + else CORE.Input.Mouse.cursorOnScreen = false; +} +//------------------------------------------------------------------------------------------------------- + +// Emscripten callback functions, called on specific browser events +//------------------------------------------------------------------------------------------------------- +/* +// Emscripten: Called on key events +static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyboardEvent, void *userData) +{ + // WARNING: Keyboard inputs already processed through GLFW callback + + return 1; // The event was consumed by the callback handler +} +*/ + +// Emscripten: Called on mouse input events +static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) +{ + // This is only for registering mouse click events with emscripten and doesn't need to do anything + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on mouse move events +static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) +{ + // To emulate the GLFW_RAW_MOUSE_MOTION property + if (CORE.Input.Mouse.cursorLocked) + { + CORE.Input.Mouse.previousPosition.x = CORE.Input.Mouse.lockedPosition.x - mouseEvent->movementX; + CORE.Input.Mouse.previousPosition.y = CORE.Input.Mouse.lockedPosition.y - mouseEvent->movementY; + } + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on pointer lock events +static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPointerlockChangeEvent *pointerlockChangeEvent, void *userData) +{ + CORE.Input.Mouse.cursorLocked = EM_ASM_INT( { if (document.pointerLockElement) return 1; }, 0); + + if (CORE.Input.Mouse.cursorLocked) + { + CORE.Input.Mouse.lockedPosition = CORE.Input.Mouse.currentPosition; + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.lockedPosition; + } + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on connect/disconnect gamepads events +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) +{ + /* + TRACELOG(LOG_DEBUG, "%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", + eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", + gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); + + for (int i = 0; i < gamepadEvent->numAxes; i++) TRACELOG(LOG_DEBUG, "Axis %d: %g", i, gamepadEvent->axis[i]); + for (int i = 0; i < gamepadEvent->numButtons; i++) TRACELOG(LOG_DEBUG, "Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); + */ + + if (gamepadEvent->connected && (gamepadEvent->index < MAX_GAMEPADS)) + { + CORE.Input.Gamepad.ready[gamepadEvent->index] = true; + snprintf(CORE.Input.Gamepad.name[gamepadEvent->index], MAX_GAMEPAD_NAME_LENGTH, "%s", gamepadEvent->id); + } + else CORE.Input.Gamepad.ready[gamepadEvent->index] = false; + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on touch input events +static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) +{ + // Register touch points count + CORE.Input.Touch.pointCount = touchEvent->numTouches; + + double canvasWidth = 0.0; + double canvasHeight = 0.0; + // NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but + // actual CSS size needs to be considered: canvas.style.width and canvas.style.height + // EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); + emscripten_get_element_css_size(platform.canvasId, &canvasWidth, &canvasHeight); + + for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++) + { + // Register touch points id + CORE.Input.Touch.pointId[i] = touchEvent->touches[i].identifier; + + // Register touch points position + CORE.Input.Touch.position[i] = (Vector2){touchEvent->touches[i].targetX, touchEvent->touches[i].targetY}; + + // Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height + CORE.Input.Touch.position[i].x *= ((float)GetScreenWidth()/(float)canvasWidth); + CORE.Input.Touch.position[i].y *= ((float)GetScreenHeight()/(float)canvasHeight); + + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) CORE.Input.Touch.currentTouchState[i] = 1; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0; + } + + // Update mouse position when single touch detected + if (CORE.Input.Touch.pointCount == 1) + { + CORE.Input.Mouse.currentPosition.x = CORE.Input.Touch.position[0].x; + CORE.Input.Mouse.currentPosition.y = CORE.Input.Touch.position[0].y; + } + +#if SUPPORT_GESTURES_SYSTEM + GestureEvent gestureEvent = { 0 }; + gestureEvent.pointCount = CORE.Input.Touch.pointCount; + + // Register touch actions + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_ACTION_DOWN; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_ACTION_UP; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; + + for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++) + { + gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i]; + gestureEvent.position[i] = CORE.Input.Touch.position[i]; + + // Normalize gestureEvent.position[i] + gestureEvent.position[i].x /= (float)GetScreenWidth(); + gestureEvent.position[i].y /= (float)GetScreenHeight(); + } + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif + + if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) + { + // Identify the EMSCRIPTEN_EVENT_TOUCHEND and remove it from the list + for (int i = 0; i < CORE.Input.Touch.pointCount; i++) + { + if (touchEvent->touches[i].isChanged) + { + // Move all touch points one position up + for (int j = i; j < CORE.Input.Touch.pointCount - 1; j++) + { + CORE.Input.Touch.pointId[j] = CORE.Input.Touch.pointId[j + 1]; + CORE.Input.Touch.position[j] = CORE.Input.Touch.position[j + 1]; + } + // Decrease touch points count to remove the last one + CORE.Input.Touch.pointCount--; + break; + } + } + // Clamp pointCount to avoid negative values + if (CORE.Input.Touch.pointCount < 0) CORE.Input.Touch.pointCount = 0; + } + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on fullscreen change events +static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) +{ + // NOTE 1: Reset the fullscreen flags if the user left fullscreen manually by pressing the Escape key + // NOTE 2: Which is a necessary safeguard because that case will bypass the toggles CORE.Window.flags resets + if (platform.ourFullscreen) platform.ourFullscreen = false; + else + { + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (!wasFullscreen) + { + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + } + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on resize event +static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData) +{ + // Don't resize non-resizeable windows + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) return 1; + + // This event is called whenever the window changes sizes, + // so the size of the canvas object is explicitly retrieved below + int width = EM_ASM_INT( return window.innerWidth; ); + int height = EM_ASM_INT( return window.innerHeight; ); + + if (width < (int)CORE.Window.screenMin.width) width = CORE.Window.screenMin.width; + else if ((width > (int)CORE.Window.screenMax.width) && (CORE.Window.screenMax.width > 0)) width = CORE.Window.screenMax.width; + + if (height < (int)CORE.Window.screenMin.height) height = CORE.Window.screenMin.height; + else if ((height > (int)CORE.Window.screenMax.height) && (CORE.Window.screenMax.height > 0)) height = CORE.Window.screenMax.height; + + emscripten_set_canvas_element_size(platform.canvasId, width, height); + + SetupViewport(width, height); // Reset viewport and projection matrix for new size + + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + CORE.Window.resizedLastFrame = true; + + if (IsWindowFullscreen()) return 1; + + // Set current screen size + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + + glfwSetWindowSize(platform.handle, CORE.Window.screen.width, CORE.Window.screen.height); + + // NOTE: Postprocessing texture is not scaled to new size + + return 0; +} + +// Emscripten: Called on windows focus change events +static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData) +{ + EM_BOOL consumed = 1; + switch (eventType) + { + case EMSCRIPTEN_EVENT_BLUR: WindowFocusCallback(userData, 0); break; + case EMSCRIPTEN_EVENT_FOCUS: WindowFocusCallback(userData, 1); break; + default: consumed = 0; break; + } + return consumed; +} + +// Emscripten: Called on visibility change events +static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData) +{ + if (visibilityChangeEvent->hidden) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was hidden + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was restored + return 1; // The event was consumed by the callback handler +} +//------------------------------------------------------------------------------------------------------- + +// EOF diff --git a/src/libraries/raylib/raylib/src/platforms/rcore_web_emscripten.c b/src/libraries/raylib/raylib/src/platforms/rcore_web_emscripten.c new file mode 100644 index 0000000..6e7d40e --- /dev/null +++ b/src/libraries/raylib/raylib/src/platforms/rcore_web_emscripten.c @@ -0,0 +1,1902 @@ +/********************************************************************************************** +* +* rcore_web_emscripten - Functions to manage window, graphics device and inputs +* +* PLATFORM: WEB - EMSCRIPTEN +* - HTML5 (WebAssembly) +* +* LIMITATIONS: +* - TBD +* +* POSSIBLE IMPROVEMENTS: +* - TBD +* +* CONFIGURATION: +* #define RCORE_PLATFORM_CUSTOM_FLAG +* Custom flag for rcore on target platform -not used- +* +* DEPENDENCIES: +* - emscripten: Allow interaction between browser API and C +* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2025-2026 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include // Emscripten functionality for C +#include // Emscripten physical keyboard codes +#include // Emscripten HTML5 library + +#include // Required for: timespec, nanosleep(), select() - POSIX + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#if (_POSIX_C_SOURCE < 199309L) + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext. +#endif + +#define DOM_PK_CODE_MAPPED_NUM 128 +#define DOM_PK_CODE_EXTENDED_OFFSET 0xE000 + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct { + char canvasId[64]; // Current canvas id + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE glContext; // OpenGL context + unsigned int *pixels; // Pointer to pixel data buffer (RGBA 32bit format) +} PlatformData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context + +static PlatformData platform = { 0 }; // Platform specific data + +static const KeyboardKey mapDomCodeToKey[DOM_PK_CODE_MAPPED_NUM] = { + [DOM_PK_ESCAPE] = KEY_ESCAPE, + [DOM_PK_0] = KEY_ZERO, + [DOM_PK_1] = KEY_ONE, + [DOM_PK_2] = KEY_TWO, + [DOM_PK_3] = KEY_THREE, + [DOM_PK_4] = KEY_FOUR, + [DOM_PK_5] = KEY_FIVE, + [DOM_PK_6] = KEY_SIX, + [DOM_PK_7] = KEY_SEVEN, + [DOM_PK_8] = KEY_EIGHT, + [DOM_PK_9] = KEY_NINE, + [DOM_PK_MINUS] = KEY_MINUS, + [DOM_PK_EQUAL] = KEY_EQUAL, + [DOM_PK_BACKSPACE] = KEY_BACKSPACE, + [DOM_PK_TAB] = KEY_TAB, + [DOM_PK_Q] = KEY_Q, + [DOM_PK_W] = KEY_W, + [DOM_PK_E] = KEY_E, + [DOM_PK_R] = KEY_R, + [DOM_PK_T] = KEY_T, + [DOM_PK_Y] = KEY_Y, + [DOM_PK_U] = KEY_U, + [DOM_PK_I] = KEY_I, + [DOM_PK_O] = KEY_O, + [DOM_PK_P] = KEY_P, + [DOM_PK_BRACKET_LEFT] = KEY_LEFT_BRACKET, + [DOM_PK_BRACKET_RIGHT] = KEY_RIGHT_BRACKET, + [DOM_PK_ENTER] = KEY_ENTER, + [DOM_PK_CONTROL_LEFT] = KEY_LEFT_CONTROL, + [DOM_PK_A] = KEY_A, + [DOM_PK_S] = KEY_S, + [DOM_PK_D] = KEY_D, + [DOM_PK_F] = KEY_F, + [DOM_PK_G] = KEY_G, + [DOM_PK_H] = KEY_H, + [DOM_PK_J] = KEY_J, + [DOM_PK_K] = KEY_K, + [DOM_PK_L] = KEY_L, + [DOM_PK_SEMICOLON] = KEY_SEMICOLON, + [DOM_PK_QUOTE] = KEY_APOSTROPHE, + [DOM_PK_BACKQUOTE] = KEY_GRAVE, + [DOM_PK_SHIFT_LEFT] = KEY_LEFT_SHIFT, + [DOM_PK_BACKSLASH] = KEY_BACKSLASH, + [DOM_PK_Z] = KEY_Z, + [DOM_PK_X] = KEY_X, + [DOM_PK_C] = KEY_C, + [DOM_PK_V] = KEY_V, + [DOM_PK_B] = KEY_B, + [DOM_PK_N] = KEY_N, + [DOM_PK_M] = KEY_M, + [DOM_PK_COMMA] = KEY_COMMA, + [DOM_PK_PERIOD] = KEY_PERIOD, + [DOM_PK_SLASH] = KEY_SLASH, + [DOM_PK_SHIFT_RIGHT] = KEY_RIGHT_SHIFT, + [DOM_PK_NUMPAD_MULTIPLY] = KEY_KP_MULTIPLY, + [DOM_PK_ALT_LEFT] = KEY_LEFT_ALT, + [DOM_PK_SPACE] = KEY_SPACE, + [DOM_PK_CAPS_LOCK] = KEY_CAPS_LOCK, + [DOM_PK_F1] = KEY_F1, + [DOM_PK_F2] = KEY_F2, + [DOM_PK_F3] = KEY_F3, + [DOM_PK_F4] = KEY_F4, + [DOM_PK_F5] = KEY_F5, + [DOM_PK_F6] = KEY_F6, + [DOM_PK_F7] = KEY_F7, + [DOM_PK_F8] = KEY_F8, + [DOM_PK_F9] = KEY_F9, + [DOM_PK_F10] = KEY_F10, + [DOM_PK_PAUSE] = KEY_PAUSE, + [DOM_PK_SCROLL_LOCK] = KEY_SCROLL_LOCK, + [DOM_PK_NUMPAD_7] = KEY_KP_7, + [DOM_PK_NUMPAD_8] = KEY_KP_8, + [DOM_PK_NUMPAD_9] = KEY_KP_9, + [DOM_PK_NUMPAD_SUBTRACT] = KEY_KP_SUBTRACT, + [DOM_PK_NUMPAD_4] = KEY_KP_4, + [DOM_PK_NUMPAD_5] = KEY_KP_5, + [DOM_PK_NUMPAD_6] = KEY_KP_6, + [DOM_PK_NUMPAD_ADD] = KEY_KP_ADD, + [DOM_PK_NUMPAD_1] = KEY_KP_1, + [DOM_PK_NUMPAD_2] = KEY_KP_2, + [DOM_PK_NUMPAD_3] = KEY_KP_3, + [DOM_PK_NUMPAD_0] = KEY_KP_0, + [DOM_PK_NUMPAD_DECIMAL] = KEY_KP_DECIMAL, + [DOM_PK_PRINT_SCREEN] = KEY_PRINT_SCREEN, + [DOM_PK_F11] = KEY_F11, + [DOM_PK_F12] = KEY_F12, + [DOM_PK_NUMPAD_EQUAL] = KEY_KP_EQUAL, + [DOM_PK_NUMPAD_COMMA] = KEY_KP_DECIMAL +}; + +// Emscripten places keys with extended scan codes (right-side modifiers, navigation and some keypad keys) in the 0xE0xx range +static const KeyboardKey mapDomCodeToKeyExtended[DOM_PK_CODE_MAPPED_NUM] = { + [DOM_PK_NUMPAD_ENTER - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_KP_ENTER, + [DOM_PK_CONTROL_RIGHT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_RIGHT_CONTROL, + [DOM_PK_AUDIO_VOLUME_DOWN - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_VOLUME_DOWN, + [DOM_PK_AUDIO_VOLUME_UP - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_VOLUME_UP, + [DOM_PK_NUMPAD_DIVIDE - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_KP_DIVIDE, + [DOM_PK_ALT_RIGHT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_RIGHT_ALT, + [DOM_PK_NUM_LOCK - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_NUM_LOCK, + [DOM_PK_HOME - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_HOME, + [DOM_PK_ARROW_UP - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_UP, + [DOM_PK_PAGE_UP - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_PAGE_UP, + [DOM_PK_ARROW_LEFT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_LEFT, + [DOM_PK_ARROW_RIGHT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_RIGHT, + [DOM_PK_END - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_END, + [DOM_PK_ARROW_DOWN - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_DOWN, + [DOM_PK_PAGE_DOWN - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_PAGE_DOWN, + [DOM_PK_INSERT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_INSERT, + [DOM_PK_DELETE - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_DELETE, + [DOM_PK_META_LEFT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_LEFT_SUPER, + [DOM_PK_META_RIGHT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_RIGHT_SUPER, + [DOM_PK_CONTEXT_MENU - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_KB_MENU +}; + +static const char cursorLUT[11][12] = { + "default", // 0 MOUSE_CURSOR_DEFAULT + "default", // 1 MOUSE_CURSOR_ARROW + "text", // 2 MOUSE_CURSOR_IBEAM + "crosshair", // 3 MOUSE_CURSOR_CROSSHAIR + "pointer", // 4 MOUSE_CURSOR_POINTING_HAND + "ew-resize", // 5 MOUSE_CURSOR_RESIZE_EW + "ns-resize", // 6 MOUSE_CURSOR_RESIZE_NS + "nwse-resize", // 7 MOUSE_CURSOR_RESIZE_NWSE + "nesw-resize", // 8 MOUSE_CURSOR_RESIZE_NESW + "move", // 9 MOUSE_CURSOR_RESIZE_ALL + "not-allowed" // 10 MOUSE_CURSOR_NOT_ALLOWED +}; + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +void ClosePlatform(void); // Close platform + +// Emscripten window callback events +static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData); +static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData); +static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData); +static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); +// TODO: Implement GLFW3 alternative for drop callback, runs when drop files into browser/canvas +//static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); + +// Emscripten input callback events +static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyboardEvent, void *userData); +static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); +static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); +static EM_BOOL EmscriptenMouseWheelCallback(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData); +static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPointerlockChangeEvent *pointerlockChangeEvent, void *userData); +static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); + +static KeyboardKey ConvertKeyboardEventToKey(const EmscriptenKeyboardEvent *keyboardEvent); + +// JS: Set the canvas id provided by the module configuration +EM_JS(void, SetCanvasIdJs, (char *out, int outSize), { + var canvasId = "#" + Module.canvas.id; + stringToUTF8(canvasId, out, outSize); +}); + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +// This will always return false on a web-build as web builds have no control over this functionality +// Sleep is handled in EndDrawing() for synchronous code +bool WindowShouldClose(void) +{ + // Emscripten Asyncify is required to run synchronous code in asynchronous JS + // REF: https://emscripten.org/docs/porting/asyncify.html + + // WindowShouldClose() is not called on a web-ready raylib application if using emscripten_set_main_loop() + // and encapsulating one frame execution on a UpdateDrawFrame() function, + // allowing the browser to manage execution asynchronously + + // NOTE: Optionally, time can be managed, giving control back-to-browser as required, + // but it seems below line could generate stuttering on some browsers + emscripten_sleep(12); + + return false; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + bool enterFullscreen = false; + + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) enterFullscreen = false; + else if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) enterFullscreen = true; + else + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0); + if (canvasStyleWidth > canvasWidth) enterFullscreen = false; + else enterFullscreen = true; + } + + EM_ASM(document.exitFullscreen();); + + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + else enterFullscreen = true; + + if (enterFullscreen) + { + // NOTE: The setTimeouts handle the browser mode change delay + EM_ASM + ( + setTimeout(function() + { + Module.requestFullscreen(false, false); + }, 100); + ); + + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + + // NOTE: Old notes below: + /* + EM_ASM + ( + // This strategy works well while using raylib minimal web shell for emscripten, + // it re-scales the canvas to fullscreen using monitor resolution, for tools this + // is a good strategy but maybe games prefer to keep current canvas resolution and + // display it in fullscreen, adjusting monitor resolution if possible + if (document.fullscreenElement) document.exitFullscreen(); + else Module.requestFullscreen(true, true); //false, true); + ); + */ + // EM_ASM(Module.requestFullscreen(false, false);); + /* + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + { + // Option 1: Request fullscreen for the canvas element + // This option does not seem to work at all: + // emscripten_request_pointerlock() and emscripten_request_fullscreen() are affected by web security, + // the user must click once on the canvas to hide the pointer or transition to full screen + //emscripten_request_fullscreen("#canvas", false); + + // Option 2: Request fullscreen for the canvas element with strategy + // This option does not seem to work at all + // REF: https://github.com/emscripten-core/emscripten/issues/5124 + // EmscriptenFullscreenStrategy strategy = { + // .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + // .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, + // .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, + // .canvasResizedCallback = EmscriptenWindowResizedCallback, + // .canvasResizedCallbackUserData = NULL + // }; + //emscripten_request_fullscreen_strategy("#canvas", EM_FALSE, &strategy); + + // Option 3: Request fullscreen for the canvas element with strategy + // It works as expected but only inside the browser (client area) + EmscriptenFullscreenStrategy strategy = { + .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, + .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, + .canvasResizedCallback = EmscriptenWindowResizedCallback, + .canvasResizedCallbackUserData = NULL + }; + emscripten_enter_soft_fullscreen("#canvas", &strategy); + + int width = 0; + int height = 0; + emscripten_get_canvas_element_size("#canvas", &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); + + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + else + { + //emscripten_exit_fullscreen(); + //emscripten_exit_soft_fullscreen(); + + int width, height; + emscripten_get_canvas_element_size("#canvas", &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); + + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + */ +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + bool enterBorderless = false; + + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) enterBorderless = false; + else if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) enterBorderless = true; + else + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int screenWidth = EM_ASM_INT( { return screen.width; }, 0); + if (screenWidth == canvasWidth) enterBorderless = false; + else enterBorderless = true; + } + + EM_ASM(document.exitFullscreen();); + + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + else enterBorderless = true; + + if (enterBorderless) + { + // 1. The setTimeouts handle the browser mode change delay + // 2. The style unset handles the possibility of a width="value%" like on the default shell.html file + EM_ASM + ( + const canvasId = UTF8ToString($0); + setTimeout(function() + { + Module.requestFullscreen(false, true); + setTimeout(function() + { + document.querySelector(canvasId).style.width="unset"; + }, 100); + }, 100); + , platform.canvasId); + FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) && !FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) + { + const int tabWidth = EM_ASM_INT( return window.innerWidth; ); + const int tabHeight = EM_ASM_INT( return window.innerHeight; ); + + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform"); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) && FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) + { + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); + + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_VSYNC_HINT) not available on target platform"); + } + + // State change: FLAG_BORDERLESS_WINDOWED_MODE + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + // NOTE: Window state flag updated inside ToggleBorderlessWindowed() function + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0); + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) || canvasStyleWidth > canvasWidth) ToggleBorderlessWindowed(); + } + else ToggleBorderlessWindowed(); + } + + // State change: FLAG_FULLSCREEN_MODE + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + // NOTE: Window state flag updated inside ToggleFullscreen() function + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int screenWidth = EM_ASM_INT( { return screen.width; }, 0); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE) || (screenWidth == canvasWidth)) ToggleFullscreen(); + } + else ToggleFullscreen(); + } + + // State change: FLAG_WINDOW_RESIZABLE + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) && FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) + { + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); + } + + // State change: FLAG_WINDOW_UNDECORATED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); + } + + // State change: FLAG_WINDOW_HIDDEN + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); + } + + // State change: FLAG_WINDOW_MINIMIZED + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); + } + + // State change: FLAG_WINDOW_MAXIMIZED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED) != FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) && FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) + { + const int tabWidth = EM_ASM_INT( return window.innerWidth; ); + const int tabHeight = EM_ASM_INT( return window.innerHeight; ); + + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNFOCUSED) not available on target platform"); + } + + // State change: FLAG_WINDOW_TOPMOST + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TOPMOST) not available on target platform"); + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_ALWAYS_RUN) not available on target platform"); + } + + // The following states can not be changed after window creation + // NOTE: Review for PLATFORM_WEB + + // State change: FLAG_WINDOW_TRANSPARENT + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); + } + + // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); + } + + // State change: FLAG_MSAA_4X_HINT + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); + } + + // State change: FLAG_INTERLACED_HINT + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_INTERLACED_HINT) not available on target platform"); + } +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_VSYNC_HINT) not available on target platform"); + } + + // State change: FLAG_BORDERLESS_WINDOWED_MODE + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int screenWidth = EM_ASM_INT( { return screen.width; }, 0); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE) || (screenWidth == canvasWidth)) EM_ASM(document.exitFullscreen();); + } + + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + + // State change: FLAG_FULLSCREEN_MODE + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE) || (canvasStyleWidth > canvasWidth)) EM_ASM(document.exitFullscreen();); + } + + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + + // State change: FLAG_WINDOW_RESIZABLE + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) && FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) + { + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); + } + + // State change: FLAG_WINDOW_HIDDEN + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); + } + + // State change: FLAG_WINDOW_MINIMIZED + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); + } + + // State change: FLAG_WINDOW_MAXIMIZED + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED) && FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) + { + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } + } + + // State change: FLAG_WINDOW_UNDECORATED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNFOCUSED) not available on target platform"); + } + + // State change: FLAG_WINDOW_TOPMOST + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TOPMOST) not available on target platform"); + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_ALWAYS_RUN) not available on target platform"); + } + + // The following states can not be changed after window creation + // NOTE: Review for PLATFORM_WEB + + // State change: FLAG_WINDOW_TRANSPARENT + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); + } + + // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); + } + + // State change: FLAG_MSAA_4X_HINT + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); + } + + // State change: FLAG_INTERLACED_HINT + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_INTERLACED_HINT) not available on target platform"); + } +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform"); +} + +// Set icon for window, multiple images +void SetWindowIcons(Image *images, int count) +{ + TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform"); +} + +// Set title for window +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; + emscripten_set_window_title(title); +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform"); +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform"); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; + + // Trigger the resize event once to update the window minimum width and height + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; + + // Trigger the resize event once to update the window maximum width and height + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + // When resizing the canvas, several elements must be considered: + // - CSS canvas size: Web layout size, logical pixels + // - Canvas contained framebuffer resolution + // - Browser monitor, device pixel ratio (HighDPI) + + double canvasCssWidth = 0.0; + double canvasCssHeight = 0.0; + emscripten_get_element_css_size(platform.canvasId, &canvasCssWidth, &canvasCssHeight); + + // NOTE: emscripten_get_canvas_element_size() returns canvas framebuffer size, not CSS canvas size + + // Get device pixel ratio + // TODO: Should DPI be considered at this point? + double dpr = emscripten_get_device_pixel_ratio(); + + // Set canvas framebuffer size + emscripten_set_canvas_element_size(platform.canvasId, width*dpr, height*dpr); + + // Set canvas CSS size + // TODO: Consider canvas CSS style if already scaled 100% + EM_ASM({ Module.canvas.style.width = $0; }, width*dpr); + EM_ASM({ Module.canvas.style.height = $0; }, height*dpr); + + SetupViewport(width*dpr, height*dpr); // Reset viewport and projection matrix for new size +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + if (opacity >= 1.0f) opacity = 1.0f; + else if (opacity <= 0.0f) opacity = 0.0f; + + EM_ASM({ Module.canvas.style.opacity = $0; }, opacity); +} + +// Set window focused +void SetWindowFocused(void) +{ + TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform"); +} + +// Get native window handle +void *GetWindowHandle(void) +{ + TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform"); + return NULL; +} + +// Get number of monitors +int GetMonitorCount(void) +{ + TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform"); + return 1; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + TRACELOG(LOG_WARNING, "GetCurrentMonitor() not implemented on target platform"); + return 0; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + // Get the width of the user's entire screen in CSS logical pixels, + // no physical pixels, it would require multiplying by device pixel ratio + // NOTE: Returned value is limited to the current monitor where the browser window is located + int width = 0; + width = EM_ASM_INT( { return window.screen.width; }, 0); + return width; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + // Get the height of the user's entire screen in CSS logical pixels, + // no physical pixels, it would require multiplying by device pixel ratio + // NOTE: Returned value is limited to the current monitor where the browser window is located + int height = 0; + height = EM_ASM_INT( { return window.screen.height; }, 0); + return height; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth() not implemented on target platform"); + return 0; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight() not implemented on target platform"); + return 0; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorRefreshRate() not implemented on target platform"); + return 0; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorName() not implemented on target platform"); + return ""; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + // Browser window position, top-left corner relative to the physical screen origin, expressed in CSS logical pixels + // NOTE: Returned position is relative to the current monitor where the browser window is located + Vector2 position = { 0, 0 }; + position.x = (float)EM_ASM_INT( { return window.screenX; }, 0); + position.y = (float)EM_ASM_INT( { return window.screenY; }, 0); + return position; +} + +// Get current monitor device pixel ratio +Vector2 GetWindowScaleDPI(void) +{ + // Get device pixel ratio + // NOTE: Returned scale is relative to the current monitor where the browser window is located + Vector2 scale = { 1.0f, 1.0f }; + scale.x = (float)EM_ASM_DOUBLE( { return window.devicePixelRatio; } ); + scale.y = scale.x; + return scale; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + // Security check to (partially) avoid malicious code + if (strchr(text, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided Clipboard could be potentially malicious, avoid [\'] character"); + else EM_ASM({ navigator.clipboard.writeText(UTF8ToString($0)); }, text); +} + +// Async EM_JS to be able to await clickboard read asynchronous function +EM_ASYNC_JS(void, RequestClipboardData, (void), { + if (navigator.clipboard && window.isSecureContext) + { + let items = await navigator.clipboard.read(); + for (const item of items) + { + // Check if this item contains plain text or image + if (item.types.includes("text/plain")) + { + const blob = await item.getType("text/plain"); + const text = await blob.text(); + window._lastClipboardString = text; + } + else if (item.types.find(t => t.startsWith("image/"))) + { + const blob = await item.getType(item.types.find(t => t.startsWith("image/"))); + const bitmap = await createImageBitmap(blob); + + const canvas = document.createElement('canvas'); + canvas.width = bitmap.width; + canvas.height = bitmap.height; + const ctx = canvas.getContext('2d'); + ctx.drawImage(bitmap, 0, 0); + + const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + + // Store image and data for the Fetch function + window._lastImgWidth = canvas.width; + window._lastImgHeight = canvas.height; + window._lastImgData = imgData; + } + } + } + else console.warn("Clipboard read() requires HTTPS/Localhost"); +}); + +// Returns the string created by RequestClipboardData from JS memory to Emscripten C memory +EM_JS(char *, GetLastPastedText, (void), { + var str = window._lastClipboardString || ""; + var length = lengthBytesUTF8(str) + 1; + if (length > 1) + { + var ptr = _malloc(length); + stringToUTF8(str, ptr, length); + return ptr; + } + return 0; +}); + +// Returns the image created by RequestClipboardData from JS memory to Emscripten C memory +EM_JS(unsigned char *, GetLastPastedImage, (int *width, int *height), { + if (window._lastImgData) + { + const data = window._lastImgData; + if (data.length > 0) + { + const ptr = _malloc(data.length); + HEAPU8.set(data, ptr); + + // Set the width and height via the pointers passed from C + // HEAP32 handles the 4-byte integers + if (width) setValue(width, window._lastImgWidth, 'i32'); + if (height) setValue(height, window._lastImgHeight, 'i32'); + + // Clear the JS buffer so there is no need to fetch the same image twice + window._lastImgData = null; + + return ptr; + } + } + + return 0; +}); + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ + RequestClipboardData(); + return GetLastPastedText(); +} + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + int w = 0, h = 0; + RequestClipboardData(); + unsigned char* data = GetLastPastedImage(&w, &h); + if (data != NULL) { + image.data = data; + image.width = w; + image.height = h; + image.mipmaps = 1; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + } + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + if (CORE.Input.Mouse.cursorHidden) + { + EM_ASM( { Module.canvas.style.cursor = UTF8ToString($0); }, cursorLUT[CORE.Input.Mouse.cursor]); + + CORE.Input.Mouse.cursorHidden = false; + } +} + +// Hide mouse cursor +void HideCursor(void) +{ + if (!CORE.Input.Mouse.cursorHidden) + { + EM_ASM(Module.canvas.style.cursor = 'none';); + + CORE.Input.Mouse.cursorHidden = true; + } +} + +// Enable cursor (unlock cursor) +void EnableCursor(void) +{ + emscripten_exit_pointerlock(); + + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + // NOTE: CORE.Input.Mouse.cursorLocked handled by EmscriptenPointerlockCallback() +} + +// Disable cursor (lock cursor) +void DisableCursor(void) +{ + emscripten_request_pointerlock(platform.canvasId, 1); + + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + // NOTE: CORE.Input.Mouse.cursorLocked handled by EmscriptenPointerlockCallback() +} + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + // Update framebuffer + rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels); + + // Copy framebuffer data into canvas + EM_ASM({ + const width = $0; + const height = $1; + const ptr = $2; + + // Get canvas and 2d context created + const canvas = Module.canvas; + //const canvas = Module['canvas']; + const ctx = canvas.getContext('2d'); + + if (!Module.__img || (Module.__img.width !== width) || (Module.__img.height !== height)) + { + Module.__img = ctx.createImageData(width, height); + } + + const src = HEAPU8.subarray(ptr, ptr + width*height*4); // RGBA (4 bytes) + Module.__img.data.set(src); + ctx.putImageData(Module.__img, 0, 0); + + }, CORE.Window.screen.width, CORE.Window.screen.height, platform.pixels); +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds since InitTimer() +double GetTime(void) +{ + double time = emscripten_get_now()/1000.0; + + return time; +} + +// Open URL with default system browser (if available) +// WARNING: This function is only safe to use if you control the URL given, +// a user could craft a malicious string to perform and undesired action +// NOTE: Some safety checks have been added to mitigate security issues +void OpenURL(const char *url) +{ + // Security check to (partially) avoid malicious code + if ((strchr(url, '\'') != NULL) || (strchr(url, '\"') != NULL)) + { + // Filter characters: ' and " + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'\"] characters"); + } + else if ((strncmp(url, "http://", 7) != 0) && (strncmp(url, "https://", 8) != 0)) + { + // Only allow URL starting with "http://" or "https://" protocols + TRACELOG(LOG_WARNING, "SYSTEM: Provided URL must start with 'http://' or 'https://' protocols"); + } + else emscripten_run_script(TextFormat("window.open('%s', '_blank')", url)); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + TRACELOG(LOG_INFO, "SetGamepadMappings not implemented in rcore_web.c"); + + return 0; +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (duration > 0.0f)) + { + if (leftMotor < 0.0f) leftMotor = 0.0f; + if (leftMotor > 1.0f) leftMotor = 1.0f; + if (rightMotor < 0.0f) rightMotor = 0.0f; + if (rightMotor > 1.0f) rightMotor = 1.0f; + if (duration > MAX_GAMEPAD_VIBRATION_TIME) duration = MAX_GAMEPAD_VIBRATION_TIME; + duration *= 1000.0f; // Convert duration to ms + + // NOTE: [2024.10.21] Current browser support: + // - vibrationActuator API: Chrome, Edge, Opera, Safari, Android Chrome, Android Webview + // - hapticActuators API: Firefox + EM_ASM({ + try { navigator.getGamepads()[$0].vibrationActuator.playEffect('dual-rumble', { startDelay: 0, duration: $3, weakMagnitude: $1, strongMagnitude: $2 }); } + catch (e) + { + try { navigator.getGamepads()[$0].hapticActuators[0].pulse($2, $3); } + catch (e) { } + } + }, gamepad, leftMotor, rightMotor, duration); + } +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + // WARNING: Not supported by browser for security reasons +} + +// Set mouse cursor +void SetMouseCursor(int cursor) +{ + if (CORE.Input.Mouse.cursor != cursor) + { + if (!CORE.Input.Mouse.cursorLocked) EM_ASM( { Module.canvas.style.cursor = UTF8ToString($0); }, cursorLUT[cursor]); + CORE.Input.Mouse.cursor = cursor; + } +} + +// Get physical key name +const char *GetKeyName(int key) +{ + // TODO: Browser can definitely provide a key name e->key + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + +// Register all input events +void PollInputEvents(void) +{ +#if SUPPORT_GESTURES_SYSTEM + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN + //CORE.Input.Gamepad.axisCount = 0; + + // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) + + // Register previous keys states + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) + { + CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // Register previous mouse states + for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + + // Register previous mouse wheel state + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; + + // Register previous mouse position + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Reset touch positions + // TODO: It resets on target platform the mouse position and not filled again until a move-event, + // so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed! + //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; + + // Get number of gamepads connected + int numGamepads = 0; + if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) numGamepads = emscripten_get_num_gamepads(); + + for (int i = 0; (i < numGamepads) && (i < MAX_GAMEPADS); i++) + { + // Register previous gamepad button states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; + + EmscriptenGamepadEvent gamepadState = { 0 }; + int result = emscripten_get_gamepad_status(i, &gamepadState); + + if (result == EMSCRIPTEN_RESULT_SUCCESS) + { + // Register buttons data for every connected gamepad + for (int j = 0; (j < gamepadState.numButtons) && (j < MAX_GAMEPAD_BUTTONS); j++) + { + GamepadButton button = -1; + + // Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface + switch (j) + { + case 0: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case 1: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case 2: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + case 3: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case 4: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case 5: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + case 6: button = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break; + case 7: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break; + case 8: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case 9: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + case 10: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case 11: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + case 12: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case 13: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case 14: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + case 15: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + default: break; + } + + if (button + 1 != 0) // Check for valid button + { + if (gamepadState.digitalButton[j] == 1) + { + CORE.Input.Gamepad.currentButtonState[i][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + } + else CORE.Input.Gamepad.currentButtonState[i][button] = 0; + } + + //TRACELOG(LOG_DEBUG, "INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); + } + + // Register axis data for every connected gamepad + for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXES); j++) + { + CORE.Input.Gamepad.axisState[i][j] = gamepadState.axis[j]; + } + + CORE.Input.Gamepad.axisCount[i] = gamepadState.numAxes; + } + } + + CORE.Window.resizedLastFrame = false; +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + SetCanvasIdJs(platform.canvasId, 64); // Get the current canvas id + + // Initialize graphic device: display/window and graphic context + //---------------------------------------------------------------------------- + emscripten_set_canvas_element_size(platform.canvasId, CORE.Window.screen.width, CORE.Window.screen.height); + EmscriptenWebGLContextAttributes attribs = { 0 }; + emscripten_webgl_init_context_attributes(&attribs); + attribs.alpha = EM_TRUE; + attribs.depth = EM_TRUE; + attribs.stencil = EM_FALSE; + attribs.antialias = EM_FALSE; + + // Check window creation flags + // Disable FLAG_WINDOW_MINIMIZED, not supported + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); + + // Disable FLAG_WINDOW_MAXIMIZED, not supported + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + + // Disable FLAG_WINDOW_TOPMOST, not supported + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_TOPMOST); + + // NOTE: Some other flags are not supported on HTML5 + + // TODO: Scale content area based on the monitor content scale where window is placed on + + // Request MSAA (usually x4 on WebGL 1.0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) attribs.antialias = EM_TRUE; + + // Check selection OpenGL version + if (rlGetVersion() == RL_OPENGL_SOFTWARE) + { + // Avoid creating a WebGL canvas, create 2d canvas for software rendering + emscripten_set_canvas_element_size(platform.canvasId, CORE.Window.screen.width, CORE.Window.screen.height); + EM_ASM({ + const canvas = document.getElementById(platform.canvasId); + Module.canvas = canvas; + }); + + // Load memory framebuffer with desired screen size + platform.pixels = (unsigned int *)RL_CALLOC(CORE.Window.screen.width*CORE.Window.screen.height, sizeof(unsigned int)); + } + else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context --> WebGL 1.0 + { + attribs.majorVersion = 1; // WebGL 1.0 requested + attribs.minorVersion = 0; + + // Create WebGL context + platform.glContext = emscripten_webgl_create_context(platform.canvasId, &attribs); + if (platform.glContext == 0) return 0; + + emscripten_webgl_make_context_current(platform.glContext); + } + else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context --> WebGL 2.0 + { + attribs.majorVersion = 2; // WebGL 2.0 requested + attribs.minorVersion = 0; + + // Create WebGL context + platform.glContext = emscripten_webgl_create_context(platform.canvasId, &attribs); + if (platform.glContext == 0) return 0; + + emscripten_webgl_make_context_current(platform.glContext); + } + + // NOTE: Getting video modes is not implemented in emscripten GLFW3 version + CORE.Window.display.width = CORE.Window.screen.width; + CORE.Window.display.height = CORE.Window.screen.height; + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + + // Set default window title + emscripten_set_window_title((CORE.Window.title != 0)? CORE.Window.title : " "); + + // Check context activation + if ((platform.glContext != 0) || (platform.pixels != NULL)) + { + CORE.Window.ready = true; + + int fbWidth = CORE.Window.screen.width; + int fbHeight = CORE.Window.screen.height; + + CORE.Window.render.width = fbWidth; + CORE.Window.render.height = fbHeight; + CORE.Window.currentFbo.width = fbWidth; + CORE.Window.currentFbo.height = fbHeight; + + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully %s", + FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)? "(HighDPI)" : ""); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + } + else + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); + return -1; + } + + // Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions + if (platform.glContext != 0) rlLoadExtensions(emscripten_webgl_get_proc_address); + //---------------------------------------------------------------------------- + + // Initialize events callbacks + //---------------------------------------------------------------------------- + // Setup window/canvas events callbacks + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenFullscreenChangeCallback); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); + emscripten_set_blur_callback(platform.canvasId, NULL, 1, EmscriptenFocusCallback); + emscripten_set_focus_callback(platform.canvasId, NULL, 1, EmscriptenFocusCallback); + emscripten_set_visibilitychange_callback(NULL, 1, EmscriptenVisibilityChangeCallback); + + // Setup input events + emscripten_set_keypress_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); + emscripten_set_keydown_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); + emscripten_set_keyup_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); + + emscripten_set_click_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); + //emscripten_set_dblclick_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); + emscripten_set_mousedown_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); + emscripten_set_mouseup_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); + emscripten_set_mousemove_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); + emscripten_set_mousemove_callback(platform.canvasId, NULL, 1, EmscriptenMouseMoveCallback); + emscripten_set_wheel_callback(platform.canvasId, NULL, 1, EmscriptenMouseWheelCallback); + emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenPointerlockCallback); + + emscripten_set_touchstart_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchend_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchmove_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchcancel_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + + emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); + emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback); + + // Trigger resize callback to force initial size + EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); + //---------------------------------------------------------------------------- + + // Initialize timing system + //---------------------------------------------------------------------------- + InitTimer(); + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- + + TRACELOG(LOG_INFO, "PLATFORM: WEB: Initialized successfully"); + + return 0; +} + +// Close platform +// NOTE: Platform closing is managed by browser, so, +// this function is actually not required, but still +// implementing some logic behaviour +void ClosePlatform(void) +{ + if (platform.pixels != NULL) RL_FREE(platform.pixels); + if (platform.glContext != 0) emscripten_webgl_destroy_context(platform.glContext); +} + +// Emscripten callback functions, called on specific browser events +//------------------------------------------------------------------------------------------------------- +// Emscripten: Called on resize event +static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData) +{ + // Don't resize non-resizeable windows + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) return 1; +/* + // Set current screen size + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + Vector2 windowScaleDPI = GetWindowScaleDPI(); + + CORE.Window.screen.width = (unsigned int)(width/windowScaleDPI.x); + CORE.Window.screen.height = (unsigned int)(height/windowScaleDPI.y); + } + else + { + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + } +*/ + // This event is called whenever the window changes sizes, + // so the size of the canvas object is explicitly retrieved below + int width = EM_ASM_INT( return window.innerWidth; ); + int height = EM_ASM_INT( return window.innerHeight; ); + + if (width < (int)CORE.Window.screenMin.width) width = CORE.Window.screenMin.width; + else if ((width > (int)CORE.Window.screenMax.width) && (CORE.Window.screenMax.width > 0)) width = CORE.Window.screenMax.width; + + if (height < (int)CORE.Window.screenMin.height) height = CORE.Window.screenMin.height; + else if ((height > (int)CORE.Window.screenMax.height) && (CORE.Window.screenMax.height > 0)) height = CORE.Window.screenMax.height; + + emscripten_set_canvas_element_size(platform.canvasId, width, height); + + SetupViewport(width, height); // Reset viewport and projection matrix for new size + + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + CORE.Window.resizedLastFrame = true; + + if (IsWindowFullscreen()) return 1; + + // Set current screen size + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + + // NOTE: Postprocessing texture is not scaled to new size + + return 0; +} + +// Emscripten: Called on windows focus change events +static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData) +{ + EM_BOOL consumed = 1; + + switch (eventType) + { + case EMSCRIPTEN_EVENT_BLUR: + { + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); + memset(CORE.Input.Keyboard.currentKeyState, 0, sizeof(CORE.Input.Keyboard.currentKeyState)); + } break; + case EMSCRIPTEN_EVENT_FOCUS: FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); break; + default: consumed = 0; break; + } + + return consumed; +} + +// Emscripten: Called on visibility change events +static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData) +{ + if (visibilityChangeEvent->hidden) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was hidden + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was restored + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on fullscreen change events +// TODO: Review fullscreen strategy +static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) +{ + // NOTE: Reset the fullscreen flags if the user left fullscreen manually by pressing the Escape key + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (!wasFullscreen) + { + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + + return 1; // The event was consumed by the callback handler +} + +/* +// GLFW3: Called on file-drop over the window +// TODO: Implement Emscripten (or HTML5/JS) alternative +static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) +{ + if (count > 0) + { + // In case previous dropped filepaths have not been freed, free them + if (CORE.Window.dropFileCount > 0) + { + for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]); + + RL_FREE(CORE.Window.dropFilepaths); + + CORE.Window.dropFileCount = 0; + CORE.Window.dropFilepaths = NULL; + } + + // WARNING: Paths are freed by GLFW when the callback returns, an internal copy should be kept + CORE.Window.dropFileCount = count; + CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *)); + + for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) + { + CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + snprintf(CORE.Window.dropFilepaths[i], MAX_FILEPATH_LENGTH, "%s", paths[i]); + } + } +} +*/ + +// Emscripten keyboard event to raylib key mapping +static KeyboardKey ConvertKeyboardEventToKey(const EmscriptenKeyboardEvent *keyboardEvent) +{ + DOM_PK_CODE_TYPE code = emscripten_compute_dom_pk_code(keyboardEvent->code); + KeyboardKey key = KEY_NULL; + + if ((code > DOM_PK_UNKNOWN) && (code < DOM_PK_CODE_MAPPED_NUM)) key = mapDomCodeToKey[code]; + else if ((code >= DOM_PK_CODE_EXTENDED_OFFSET) && (code < (DOM_PK_CODE_EXTENDED_OFFSET + DOM_PK_CODE_MAPPED_NUM))) + { + key = mapDomCodeToKeyExtended[code - DOM_PK_CODE_EXTENDED_OFFSET]; + } + + return key; +} + +// Emscripten: Called on key events +static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyboardEvent, void *userData) +{ + KeyboardKey key = ConvertKeyboardEventToKey(keyboardEvent); + EM_BOOL preventDefault = 1; + + switch (eventType) + { + case EMSCRIPTEN_EVENT_KEYDOWN: + { + if (key != KEY_NULL) + { + // Check if there is space available in the key queue + if ((!keyboardEvent->repeat) && (CORE.Input.Keyboard.currentKeyState[key] == 0) && + (CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE)) + { + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + + CORE.Input.Keyboard.currentKeyState[key] = 1; + + if (keyboardEvent->repeat) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1; + + // Check the exit key to set close window + if ((key == CORE.Input.Keyboard.exitKey) && !keyboardEvent->repeat) CORE.Window.shouldClose = true; + } + + // Allow printable keydown events so the browser can emit the keypress event used to collect characters + preventDefault = ((key == KEY_BACKSPACE) || (key == KEY_TAB) || + (key == KEY_INSERT) || (key == KEY_DELETE) || + (key == KEY_HOME) || (key == KEY_END) || + (key == KEY_PAGE_UP) || (key == KEY_PAGE_DOWN) || + (key == KEY_LEFT) || (key == KEY_RIGHT) || + (key == KEY_UP) || (key == KEY_DOWN) || + ((key >= KEY_F1) && (key <= KEY_F12)) || keyboardEvent->ctrlKey); + } break; + case EMSCRIPTEN_EVENT_KEYUP: + { + if (key != KEY_NULL) CORE.Input.Keyboard.currentKeyState[key] = 0; + } break; + case EMSCRIPTEN_EVENT_KEYPRESS: + { + // Check if there is space available in the queue for characters to be added + if ((keyboardEvent->charCode > 0) && (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE)) + { + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = keyboardEvent->charCode; + CORE.Input.Keyboard.charPressedQueueCount++; + } + } break; + default: break; + } + + return preventDefault; +} + +// Emscripten: Called on mouse input events +static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) +{ + switch (eventType) + { + case EMSCRIPTEN_EVENT_MOUSEENTER: { + // NOTE: Mouse position updated by EmscriptenMouseMoveCallback(), + // EmscriptenPointerlockCallback(), and EmscriptenTouchCallback() + CORE.Input.Mouse.cursorOnScreen = true; + } break; + case EMSCRIPTEN_EVENT_MOUSELEAVE: + { + CORE.Input.Mouse.cursorOnScreen = false; + CORE.Input.Mouse.currentPosition = (Vector2){ 0 }; + } break; + case EMSCRIPTEN_EVENT_MOUSEDOWN: + { + // NOTE: Emscripten and raylib buttons indices are not aligned + if (mouseEvent->button == 0) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_LEFT] = 1; + else if (mouseEvent->button == 1) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_MIDDLE] = 1; + else if (mouseEvent->button == 2) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_RIGHT] = 1; + + //CORE.Input.Touch.currentTouchState[button] = action; + } break; + case EMSCRIPTEN_EVENT_MOUSEUP: + { + if (mouseEvent->button == 0) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_LEFT] = 0; + else if (mouseEvent->button == 1) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_MIDDLE] = 0; + else if (mouseEvent->button == 2) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_RIGHT] = 0; + } break; + default: break; + } + +#if SUPPORT_GESTURES_SYSTEM && SUPPORT_MOUSE_GESTURES + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + if ((CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_LEFT] == 1) && (CORE.Input.Mouse.previousButtonState[MOUSE_BUTTON_LEFT] == 0)) gestureEvent.touchAction = TOUCH_ACTION_DOWN; + else if ((CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_LEFT] == 0) && (CORE.Input.Mouse.previousButtonState[MOUSE_BUTTON_LEFT] == 1)) gestureEvent.touchAction = TOUCH_ACTION_UP; + + // NOTE: TOUCH_ACTION_MOVE event is registered in MouseMoveCallback() + + // Assign a pointer ID + gestureEvent.pointId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = GetMousePosition(); + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures-system for processing + // Prevent calling ProcessGestureEvent() when Emscripten is present and there's a touch gesture, so EmscriptenTouchCallback() can handle it itself + if (GetMouseX() != 0 || GetMouseY() != 0) ProcessGestureEvent(gestureEvent); +#endif + + // Don't prevent default to allow focusing on canvas + return 0; +} + +// Emscripten: Called on mouse move events +static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) +{ + if (CORE.Input.Mouse.cursorLocked) + { + CORE.Input.Mouse.previousPosition.x = CORE.Input.Mouse.lockedPosition.x - mouseEvent->movementX; + CORE.Input.Mouse.previousPosition.y = CORE.Input.Mouse.lockedPosition.y - mouseEvent->movementY; + } + else + { + // Get mouse position in canvas CSS pixels + float mouseCssX = (float)mouseEvent->targetX; + float mouseCssY = (float)mouseEvent->targetY; + + // Get canvas sizes + double cssWidth = 0.0; + double cssHeight = 0.0; + emscripten_get_element_css_size(platform.canvasId, &cssWidth, &cssHeight); + + float normalizedX = mouseCssX/(float)cssWidth; + float normalizedY = mouseCssY/(float)cssHeight; + CORE.Input.Mouse.currentPosition.x = normalizedX*(float)CORE.Window.screen.width; + CORE.Input.Mouse.currentPosition.y = normalizedY*(float)CORE.Window.screen.height; + + CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + } + +#if SUPPORT_GESTURES_SYSTEM && SUPPORT_MOUSE_GESTURES + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + gestureEvent.touchAction = TOUCH_ACTION_MOVE; + + // Assign a pointer ID + gestureEvent.pointId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = CORE.Input.Touch.position[0]; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures-system for processing + ProcessGestureEvent(gestureEvent); +#endif + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on mouse wheel events +static EM_BOOL EmscriptenMouseWheelCallback(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData) +{ + if (eventType == EMSCRIPTEN_EVENT_WHEEL) + { + CORE.Input.Mouse.currentWheelMove.x = (float)wheelEvent->deltaX; + CORE.Input.Mouse.currentWheelMove.y = -(float)wheelEvent->deltaY; + } + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on pointer lock events +static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPointerlockChangeEvent *pointerlockChangeEvent, void *userData) +{ + CORE.Input.Mouse.cursorLocked = EM_ASM_INT( { if (document.pointerLockElement) return 1; }, 0); + + if (CORE.Input.Mouse.cursorLocked) + { + CORE.Input.Mouse.lockedPosition = CORE.Input.Mouse.currentPosition; + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.lockedPosition; + } + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on connect/disconnect gamepads events +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) +{ + /* + TRACELOG(LOG_DEBUG, "%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", + eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", + gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); + + for (int i = 0; i < gamepadEvent->numAxes; i++) TRACELOG(LOG_DEBUG, "Axis %d: %g", i, gamepadEvent->axis[i]); + for (int i = 0; i < gamepadEvent->numButtons; i++) TRACELOG(LOG_DEBUG, "Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); + */ + + if (gamepadEvent->connected && (gamepadEvent->index < MAX_GAMEPADS)) + { + CORE.Input.Gamepad.ready[gamepadEvent->index] = true; + snprintf(CORE.Input.Gamepad.name[gamepadEvent->index], MAX_GAMEPAD_NAME_LENGTH, "%s", gamepadEvent->id); + } + else CORE.Input.Gamepad.ready[gamepadEvent->index] = false; + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on touch input events +static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) +{ + // Register touch points count + CORE.Input.Touch.pointCount = touchEvent->numTouches; + + double canvasWidth = 0.0; + double canvasHeight = 0.0; + // NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but + // looking for actual CSS size: canvas.style.width and canvas.style.height + // EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); + emscripten_get_element_css_size(platform.canvasId, &canvasWidth, &canvasHeight); + + for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++) + { + // Register touch points id + CORE.Input.Touch.pointId[i] = touchEvent->touches[i].identifier; + + // Register touch points position + CORE.Input.Touch.position[i] = (Vector2){touchEvent->touches[i].targetX, touchEvent->touches[i].targetY}; + + // Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height + CORE.Input.Touch.position[i].x *= ((float)GetScreenWidth()/(float)canvasWidth); + CORE.Input.Touch.position[i].y *= ((float)GetScreenHeight()/(float)canvasHeight); + + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) CORE.Input.Touch.currentTouchState[i] = 1; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0; + } + + // Update mouse position if a single touch is detected + if (CORE.Input.Touch.pointCount == 1) + { + CORE.Input.Mouse.currentPosition.x = CORE.Input.Touch.position[0].x; + CORE.Input.Mouse.currentPosition.y = CORE.Input.Touch.position[0].y; + } + +#if SUPPORT_GESTURES_SYSTEM + GestureEvent gestureEvent = { 0 }; + gestureEvent.pointCount = CORE.Input.Touch.pointCount; + + // Register touch actions + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_ACTION_DOWN; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_ACTION_UP; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; + + for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++) + { + gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i]; + gestureEvent.position[i] = CORE.Input.Touch.position[i]; + + // Normalize gestureEvent.position[i] + gestureEvent.position[i].x /= (float)GetScreenWidth(); + gestureEvent.position[i].y /= (float)GetScreenHeight(); + } + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif + + if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) + { + // Identify the EMSCRIPTEN_EVENT_TOUCHEND and remove it from the list + for (int i = 0; i < CORE.Input.Touch.pointCount; i++) + { + if (touchEvent->touches[i].isChanged) + { + // Move all touch points one position up + for (int j = i; j < CORE.Input.Touch.pointCount - 1; j++) + { + CORE.Input.Touch.pointId[j] = CORE.Input.Touch.pointId[j + 1]; + CORE.Input.Touch.position[j] = CORE.Input.Touch.position[j + 1]; + } + // Decrease touch points count to remove the last one + CORE.Input.Touch.pointCount--; + break; + } + } + // Clamp pointCount to avoid negative values + if (CORE.Input.Touch.pointCount < 0) CORE.Input.Touch.pointCount = 0; + } + + return 1; // The event was consumed by the callback handler +} +//------------------------------------------------------------------------------------------------------- + +// EOF diff --git a/src/libraries/raylib/raylib/src/raudio.c b/src/libraries/raylib/raylib/src/raudio.c new file mode 100644 index 0000000..ec97bde --- /dev/null +++ b/src/libraries/raylib/raylib/src/raudio.c @@ -0,0 +1,2965 @@ +/********************************************************************************************** +* +* raudio v1.1 - A simple and easy-to-use audio library based on miniaudio +* +* FEATURES: +* - Manage audio device (init/close) +* - Manage raw audio context +* - Manage mixing channels +* - Load and unload audio files +* - Format wave data (sample rate, size, channels) +* - Play/Stop/Pause/Resume loaded audio +* +* CONFIGURATION: +* #define SUPPORT_MODULE_RAUDIO 1 +* raudio module is included in the build +* +* #define RAUDIO_STANDALONE +* Define to use the module as standalone library (independently of raylib) +* Required types and functions are defined in the same module +* +* #define SUPPORT_FILEFORMAT_WAV 1 +* #define SUPPORT_FILEFORMAT_OGG 1 +* #define SUPPORT_FILEFORMAT_MP3 1 +* #define SUPPORT_FILEFORMAT_QOA 1 +* #define SUPPORT_FILEFORMAT_FLAC 0 +* #define SUPPORT_FILEFORMAT_XM 1 +* #define SUPPORT_FILEFORMAT_MOD 1 +* Selected desired fileformats to be supported for loading. Some of those formats are +* supported by default, to remove support, #define as 0 in this module or your build system +* +* DEPENDENCIES: +* miniaudio.h - Audio device management lib (https://github.com/mackron/miniaudio) +* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* dr_wav.h - WAV audio files loading (http://github.com/mackron/dr_libs) +* dr_mp3.h - MP3 audio file loading (https://github.com/mackron/dr_libs) +* dr_flac.h - FLAC audio file loading (https://github.com/mackron/dr_libs) +* jar_xm.h - XM module file loading +* jar_mod.h - MOD audio file loading +* +* CONTRIBUTORS: +* David Reid (github: @mackron) (Nov. 2017): +* - Complete port to miniaudio library +* +* Joshua Reisenauer (github: @kd7tck) (2015): +* - XM audio module support (jar_xm) +* - MOD audio module support (jar_mod) +* - Mixing channels support +* - Raw audio context support +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#if defined(RAUDIO_STANDALONE) + #include "raudio.h" +#else + #include "raylib.h" // Declares module functions + + #include "config.h" // Defines module configuration flags +#endif + +#if SUPPORT_MODULE_RAUDIO || defined(RAUDIO_STANDALONE) + +#if defined(_WIN32) +// To avoid conflicting windows.h symbols with raylib, some flags are defined +// WARNING: Those flags avoid inclusion of some Win32 headers that could be required +// by user at some point and won't be included... +//------------------------------------------------------------------------------------- + +// If defined, the following flags inhibit definition of the indicated items +#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ +#define NOVIRTUALKEYCODES // VK_* +#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* +#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* +#define NOSYSMETRICS // SM_* +#define NOMENUS // MF_* +#define NOICONS // IDI_* +#define NOKEYSTATES // MK_* +#define NOSYSCOMMANDS // SC_* +#define NORASTEROPS // Binary and Tertiary raster ops +#define NOSHOWWINDOW // SW_* +#define OEMRESOURCE // OEM Resource values +#define NOATOM // Atom Manager routines +#define NOCLIPBOARD // Clipboard routines +#define NOCOLOR // Screen colors +#define NOCTLMGR // Control and Dialog routines +#define NODRAWTEXT // DrawText() and DT_* +#define NOGDI // All GDI defines and routines +#define NOKERNEL // All KERNEL defines and routines +#define NOUSER // All USER defines and routines +//#define NONLS // All NLS defines and routines +#define NOMB // MB_* and MessageBox() +#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines +#define NOMETAFILE // typedef METAFILEPICT +#define NOMINMAX // Macros min(a,b) and max(a,b) +#define NOMSG // typedef MSG and associated routines +#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* +#define NOSCROLL // SB_* and scrolling routines +#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. +#define NOSOUND // Sound driver routines +#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines +#define NOWH // SetWindowsHook and WH_* +#define NOWINOFFSETS // GWL_*, GCL_*, associated routines +#define NOCOMM // COMM driver routines +#define NOKANJI // Kanji support stuff +#define NOHELP // Help engine interface +#define NOPROFILER // Profiler interface +#define NODEFERWINDOWPOS // DeferWindowPos routines +#define NOMCX // Modem Configuration Extensions + +// Type required before windows.h inclusion +typedef struct tagMSG *LPMSG; + +#include // Windows functionality (miniaudio) + +// Type required by some unused function... +typedef struct tagBITMAPINFOHEADER { + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} BITMAPINFOHEADER, *PBITMAPINFOHEADER; + +#include // Component Object Model (COM) header +#include // Windows Multimedia, defines some WAVE structs +#include // Windows Multimedia, used by Windows GDI, defines DIBINDEX macro + +// Some required types defined for MSVC/TinyC compiler +#if defined(_MSC_VER) || defined(__TINYC__) + #include "propidl.h" +#endif +#endif + +#define MA_MALLOC RL_MALLOC +#define MA_FREE RL_FREE + +#define MA_NO_JACK +#define MA_NO_WAV +#define MA_NO_FLAC +#define MA_NO_MP3 +#define MA_NO_RESOURCE_MANAGER +#define MA_NO_NODE_GRAPH +#define MA_NO_ENGINE +#define MA_NO_GENERATION + +// Threading model: Default: [0] COINIT_MULTITHREADED: COM calls objects on any thread (free threading) +#define MA_COINIT_VALUE 2 // [2] COINIT_APARTMENTTHREADED: Each object has its own thread (apartment model) + +#define MINIAUDIO_IMPLEMENTATION +//#define MA_DEBUG_OUTPUT +#include "external/miniaudio.h" // Audio device initialization and management +#undef PlaySound // Win32 API: windows.h > mmsystem.h defines PlaySound macro + +#include // Required for: malloc(), free() +#include // Required for: FILE, fopen(), fclose(), fread() +#include // Required for: strcmp() [Used in IsFileExtension(), LoadWaveFromMemory(), LoadMusicStreamFromMemory()] + +#if defined(RAUDIO_STANDALONE) + #ifndef TRACELOG + #define TRACELOG(level, ...) printf(__VA_ARGS__) + #endif + + // Allow custom memory allocators + #ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) + #endif + #ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) + #endif + #ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) + #endif + #ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) + #endif +#endif + +#if SUPPORT_FILEFORMAT_WAV + #define DRWAV_MALLOC RL_MALLOC + #define DRWAV_REALLOC RL_REALLOC + #define DRWAV_FREE RL_FREE + + #define DR_WAV_IMPLEMENTATION + #include "external/dr_wav.h" // WAV loading functions +#endif + +#if SUPPORT_FILEFORMAT_OGG + // TODO: Remap stb_vorbis malloc()/free() calls to RL_MALLOC/RL_FREE + #include "external/stb_vorbis.c" // OGG loading functions +#endif + +#if SUPPORT_FILEFORMAT_MP3 + #define DRMP3_MALLOC RL_MALLOC + #define DRMP3_REALLOC RL_REALLOC + #define DRMP3_FREE RL_FREE + + #define DR_MP3_IMPLEMENTATION + #include "external/dr_mp3.h" // MP3 loading functions +#endif + +#if SUPPORT_FILEFORMAT_QOA + #define QOA_MALLOC RL_MALLOC + #define QOA_FREE RL_FREE + + #if defined(_MSC_VER) // Disable some MSVC warning + #pragma warning(push) + #pragma warning(disable : 4018) + #pragma warning(disable : 4267) + #pragma warning(disable : 4244) + #endif + + #define QOA_IMPLEMENTATION + #include "external/qoa.h" // QOA loading and saving functions + #include "external/qoaplay.c" // QOA stream playing helper functions + + #if defined(_MSC_VER) + #pragma warning(pop) // Disable MSVC warning suppression + #endif +#endif + +#if SUPPORT_FILEFORMAT_FLAC + #define DRFLAC_MALLOC RL_MALLOC + #define DRFLAC_REALLOC RL_REALLOC + #define DRFLAC_FREE RL_FREE + + #define DR_FLAC_IMPLEMENTATION + #define DR_FLAC_NO_WIN32_IO + #include "external/dr_flac.h" // FLAC loading functions +#endif + +#if SUPPORT_FILEFORMAT_XM + #define JARXM_MALLOC RL_MALLOC + #define JARXM_FREE RL_FREE + + #if defined(_MSC_VER) // Disable some MSVC warning + #pragma warning(push) + #pragma warning(disable : 4244) + #endif + + #define JAR_XM_IMPLEMENTATION + #include "external/jar_xm.h" // XM loading functions + + #if defined(_MSC_VER) + #pragma warning(pop) // Disable MSVC warning suppression + #endif +#endif + +#if SUPPORT_FILEFORMAT_MOD + #define JARMOD_MALLOC RL_MALLOC + #define JARMOD_FREE RL_FREE + + #define JAR_MOD_IMPLEMENTATION + #include "external/jar_mod.h" // MOD loading functions +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef AUDIO_DEVICE_FORMAT + #define AUDIO_DEVICE_FORMAT ma_format_f32 // Device output format (float-32bit) +#endif +#ifndef AUDIO_DEVICE_CHANNELS + #define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo +#endif +#ifndef AUDIO_DEVICE_SAMPLE_RATE + #define AUDIO_DEVICE_SAMPLE_RATE 0 // Device output sample rate +#endif +#ifndef AUDIO_DEVICE_PERIOD_SIZE_IN_FRAMES + #define AUDIO_DEVICE_PERIOD_SIZE_IN_FRAMES 0 // Device latency. 0 defaults to 10ms +#endif + +#ifndef MAX_AUDIO_BUFFER_POOL_CHANNELS + #define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Audio pool channels +#endif + +#ifndef AUDIO_BUFFER_RESIDUAL_CAPACITY + #define AUDIO_BUFFER_RESIDUAL_CAPACITY 8 // In PCM frames, for resampling and pitch shifting +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if defined(RAUDIO_STANDALONE) +// Trace log level +// NOTE: Organized by priority level +typedef enum { + LOG_ALL = 0, // Display all logs + LOG_TRACE, // Trace logging, intended for internal use only + LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + LOG_INFO, // Info logging, used for program execution info + LOG_WARNING, // Warning logging, used on recoverable failures + LOG_ERROR, // Error logging, used on unrecoverable failures + LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + LOG_NONE // Disable logging +} TraceLogLevel; +#endif + +// Music context type +// NOTE: Depends on data structure provided by the library +// in charge of reading the different file types +typedef enum { + MUSIC_AUDIO_NONE = 0, // No audio context loaded + MUSIC_AUDIO_WAV, // WAV audio context + MUSIC_AUDIO_OGG, // OGG audio context + MUSIC_AUDIO_FLAC, // FLAC audio context + MUSIC_AUDIO_MP3, // MP3 audio context + MUSIC_AUDIO_QOA, // QOA audio context + MUSIC_MODULE_XM, // XM module audio context + MUSIC_MODULE_MOD // MOD module audio context +} MusicContextType; + +// NOTE: Different logic is used when feeding data to the playback device +// depending on whether data is streamed (Music vs Sound) +typedef enum { + AUDIO_BUFFER_USAGE_STATIC = 0, + AUDIO_BUFFER_USAGE_STREAM +} AudioBufferUsage; + +// Audio buffer struct +struct rAudioBuffer { + ma_data_converter converter; // Audio data converter + unsigned char *converterResidual; // Cached residual input frames for use by the converter + unsigned int converterResidualCount; // The number of valid frames sitting in converterResidual + + AudioCallback callback; // Audio buffer callback for buffer filling on audio threads + rAudioProcessor *processor; // Audio processor + + float volume; // Audio buffer volume + float pitch; // Audio buffer pitch + float pan; // Audio buffer pan (-1.0f to 1.0f) + + bool playing; // Audio buffer state: AUDIO_PLAYING + bool paused; // Audio buffer state: AUDIO_PAUSED + bool looping; // Audio buffer looping, default to true for AudioStreams + int usage; // Audio buffer usage mode: STATIC or STREAM + + bool isSubBufferProcessed[2]; // SubBuffer processed (virtual double buffer) + unsigned int sizeInFrames; // Total buffer size in frames + unsigned int frameCursorPos; // Frame cursor position + unsigned int framesProcessed; // Total frames processed in this buffer (required for play timing) + + unsigned char *data; // Data buffer, on music stream keeps filling + + rAudioBuffer *next; // Next audio buffer on the list + rAudioBuffer *prev; // Previous audio buffer on the list +}; + +// Audio processor struct +// NOTE: Useful to apply effects to an AudioBuffer +struct rAudioProcessor { + AudioCallback process; // Processor callback function + rAudioProcessor *next; // Next audio processor on the list + rAudioProcessor *prev; // Previous audio processor on the list +}; + +#define AudioBuffer rAudioBuffer // WARNING: Renamed to avoid symbol collision with CoreAudio (macOS) AudioBuffer type + +// Audio data context +typedef struct AudioData { + struct { + ma_context context; // miniaudio context data + ma_device device; // miniaudio device + ma_mutex lock; // miniaudio mutex lock + bool isReady; // Check if audio device is ready + size_t pcmBufferSize; // Pre-allocated buffer size + void *pcmBuffer; // Pre-allocated buffer to read audio data from file/memory + } System; + struct { + AudioBuffer *first; // Pointer to first AudioBuffer in the list + AudioBuffer *last; // Pointer to last AudioBuffer in the list + int defaultSize; // Default audio buffer size for audio streams + } Buffer; + rAudioProcessor *mixedProcessor; +} AudioData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static AudioData AUDIO = { // Global AUDIO context + + // NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number + // After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds and a + // standard double-buffering system, a 4096 samples buffer has been chosen, it should be enough + // In case of music-stalls, increase this number + .Buffer.defaultSize = 0, + .mixedProcessor = NULL +}; + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage); + +// Reads audio data from an AudioBuffer object in internal/device formats +static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount); +static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount); + +static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount); +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer); + +static bool IsAudioBufferPlayingInLockedState(AudioBuffer *buffer); +static void StopAudioBufferInLockedState(AudioBuffer *buffer); +static void UpdateAudioStreamInLockedState(AudioStream stream, const void *data, int frameCount); + +#if defined(RAUDIO_STANDALONE) +static bool IsFileExtension(const char *fileName, const char *ext); // Check file extension +static const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes the dot: .png) +static const char *GetFileName(const char *filePath); // Get pointer to filename for a path string +static const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) + +static unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) +static void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() +static bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write) +static bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated +#endif + +//---------------------------------------------------------------------------------- +// AudioBuffer management functions declaration +// NOTE: Those functions are not exposed by raylib... for the moment +//---------------------------------------------------------------------------------- +AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage); +void UnloadAudioBuffer(AudioBuffer *buffer); + +bool IsAudioBufferPlaying(AudioBuffer *buffer); +void PlayAudioBuffer(AudioBuffer *buffer); +void StopAudioBuffer(AudioBuffer *buffer); +void PauseAudioBuffer(AudioBuffer *buffer); +void ResumeAudioBuffer(AudioBuffer *buffer); +void SetAudioBufferVolume(AudioBuffer *buffer, float volume); +void SetAudioBufferPitch(AudioBuffer *buffer, float pitch); +void SetAudioBufferPan(AudioBuffer *buffer, float pan); +void TrackAudioBuffer(AudioBuffer *buffer); +void UntrackAudioBuffer(AudioBuffer *buffer); + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Audio Device initialization and Closing +//---------------------------------------------------------------------------------- + +// Initialize audio device +void InitAudioDevice(void) +{ + // Init audio context + ma_context_config ctxConfig = ma_context_config_init(); + ma_log_callback_init(OnLog, NULL); + + ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.System.context); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize context"); + return; + } + + // Init audio device + // NOTE: Using the default device. Format is floating point because it simplifies mixing + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.pDeviceID = NULL; // NULL for the default playback AUDIO.System.device + config.playback.format = AUDIO_DEVICE_FORMAT; + config.playback.channels = AUDIO_DEVICE_CHANNELS; + config.sampleRate = AUDIO_DEVICE_SAMPLE_RATE; + config.periodSizeInFrames = AUDIO_DEVICE_PERIOD_SIZE_IN_FRAMES; + config.dataCallback = OnSendAudioDataToDevice; + config.pUserData = NULL; + config.noPreSilencedOutputBuffer = true; // raylib pre-silences the output buffer manually + config.noFixedSizedCallback = true; // raylib does not require fixed sized callback guarantees. This bypasses an internal intermediary buffer + + result = ma_device_init(&AUDIO.System.context, &config, &AUDIO.System.device); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize playback device"); + ma_context_uninit(&AUDIO.System.context); + return; + } + + // Mixing happens on a separate thread which means synchronization is needed + // A mutex is used here to make things simple, but may want to look at something + // a bit smarter later on to keep everything real-time, if that's necessary + if (ma_mutex_init(&AUDIO.System.lock) != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing"); + ma_device_uninit(&AUDIO.System.device); + ma_context_uninit(&AUDIO.System.context); + return; + } + + // Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running + // while there's at least one sound being played + result = ma_device_start(&AUDIO.System.device); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to start playback device"); + ma_device_uninit(&AUDIO.System.device); + ma_context_uninit(&AUDIO.System.context); + return; + } + + TRACELOG(LOG_INFO, "AUDIO: Device initialized successfully"); + TRACELOG(LOG_INFO, " > Backend: miniaudio | %s", ma_get_backend_name(AUDIO.System.context.backend)); + TRACELOG(LOG_INFO, " > Format: %s -> %s", ma_get_format_name(AUDIO.System.device.playback.format), ma_get_format_name(AUDIO.System.device.playback.internalFormat)); + TRACELOG(LOG_INFO, " > Channels: %d -> %d", AUDIO.System.device.playback.channels, AUDIO.System.device.playback.internalChannels); + TRACELOG(LOG_INFO, " > Sample rate: %d -> %d", AUDIO.System.device.sampleRate, AUDIO.System.device.playback.internalSampleRate); + TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.System.device.playback.internalPeriodSizeInFrames*AUDIO.System.device.playback.internalPeriods); + + AUDIO.System.isReady = true; +} + +// Close the audio device for all contexts +void CloseAudioDevice(void) +{ + if (AUDIO.System.isReady) + { + // Stop the device first (joins the callback thread) before destroying the mutex it locks + ma_device_uninit(&AUDIO.System.device); + ma_mutex_uninit(&AUDIO.System.lock); + ma_context_uninit(&AUDIO.System.context); + + AUDIO.System.isReady = false; + RL_FREE(AUDIO.System.pcmBuffer); + AUDIO.System.pcmBuffer = NULL; + AUDIO.System.pcmBufferSize = 0; + + TRACELOG(LOG_INFO, "AUDIO: Device closed successfully"); + } + else TRACELOG(LOG_WARNING, "AUDIO: Device could not be closed, not currently initialized"); +} + +// Check if device has been initialized successfully +bool IsAudioDeviceReady(void) +{ + return AUDIO.System.isReady; +} + +// Set master volume (listener) +void SetMasterVolume(float volume) +{ + ma_device_set_master_volume(&AUDIO.System.device, volume); +} + +// Get master volume (listener) +float GetMasterVolume(void) +{ + float volume = 0.0f; + ma_device_get_master_volume(&AUDIO.System.device, &volume); + return volume; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Audio Buffer management +//---------------------------------------------------------------------------------- + +// Initialize a new audio buffer (filled with silence) +AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage) +{ + AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(1, sizeof(AudioBuffer)); + + if (audioBuffer == NULL) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for buffer"); + return NULL; + } + + if (sizeInFrames > 0) audioBuffer->data = (unsigned char *)RL_CALLOC(sizeInFrames*channels*ma_get_bytes_per_sample(format), 1); + + // Audio data runs through a format converter + ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO.System.device.sampleRate); + converterConfig.allowDynamicSampleRate = true; + + ma_result result = ma_data_converter_init(&converterConfig, NULL, &audioBuffer->converter); + + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to create data conversion pipeline"); + RL_FREE(audioBuffer); + return NULL; + } + + // A cache for use by the converter is necessary when resampling because + // when generating output frames a different number of input frames will + // be consumed. Any residual input frames need to be kept track of to + // ensure there are no discontinuities. Since raylib supports pitch + // shifting, which is done through resampling, a cache will always be + // required. This will be kept relatively small to avoid too much wastage + audioBuffer->converterResidualCount = 0; + audioBuffer->converterResidual = (unsigned char*)RL_CALLOC(AUDIO_BUFFER_RESIDUAL_CAPACITY*ma_get_bytes_per_frame(format, channels), 1); + + // Init audio buffer values + audioBuffer->volume = 1.0f; + audioBuffer->pitch = 1.0f; + audioBuffer->pan = 0.0f; // Center + + audioBuffer->callback = NULL; + audioBuffer->processor = NULL; + + audioBuffer->playing = false; + audioBuffer->paused = false; + audioBuffer->looping = false; + + audioBuffer->usage = usage; + audioBuffer->frameCursorPos = 0; + audioBuffer->framesProcessed = 0; + audioBuffer->sizeInFrames = sizeInFrames; + + // Buffers should be marked as processed by default so that a call to + // UpdateAudioStream() immediately after initialization works correctly + audioBuffer->isSubBufferProcessed[0] = true; + audioBuffer->isSubBufferProcessed[1] = true; + + // Track audio buffer to linked list next position + TrackAudioBuffer(audioBuffer); + + return audioBuffer; +} + +// Delete an audio buffer +void UnloadAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + UntrackAudioBuffer(buffer); + ma_data_converter_uninit(&buffer->converter, NULL); + RL_FREE(buffer->converterResidual); + RL_FREE(buffer->data); + RL_FREE(buffer); + } +} + +// Check if an audio buffer is playing from a program state without lock +bool IsAudioBufferPlaying(AudioBuffer *buffer) +{ + bool result = false; + ma_mutex_lock(&AUDIO.System.lock); + result = IsAudioBufferPlayingInLockedState(buffer); + ma_mutex_unlock(&AUDIO.System.lock); + return result; +} + +// Play an audio buffer +// NOTE: Buffer is restarted to the start +// Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position should be maintained +void PlayAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + ma_mutex_lock(&AUDIO.System.lock); + buffer->playing = true; + buffer->paused = false; + buffer->frameCursorPos = 0; + buffer->framesProcessed = 0; + buffer->isSubBufferProcessed[0] = true; + buffer->isSubBufferProcessed[1] = true; + ma_mutex_unlock(&AUDIO.System.lock); + } +} + +// Stop an audio buffer from a program state without lock +void StopAudioBuffer(AudioBuffer *buffer) +{ + ma_mutex_lock(&AUDIO.System.lock); + StopAudioBufferInLockedState(buffer); + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Pause an audio buffer +void PauseAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + ma_mutex_lock(&AUDIO.System.lock); + buffer->paused = true; + ma_mutex_unlock(&AUDIO.System.lock); + } +} + +// Resume an audio buffer +void ResumeAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + ma_mutex_lock(&AUDIO.System.lock); + buffer->paused = false; + ma_mutex_unlock(&AUDIO.System.lock); + } +} + +// Set volume for an audio buffer +void SetAudioBufferVolume(AudioBuffer *buffer, float volume) +{ + if (buffer != NULL) + { + ma_mutex_lock(&AUDIO.System.lock); + buffer->volume = volume; + ma_mutex_unlock(&AUDIO.System.lock); + } +} + +// Set pitch for an audio buffer +void SetAudioBufferPitch(AudioBuffer *buffer, float pitch) +{ + if ((buffer != NULL) && (pitch > 0.0f)) + { + ma_mutex_lock(&AUDIO.System.lock); + // Pitching is an adjustment of the sample rate + // Note that this changes the duration of the sound: + // - higher pitches will make the sound faster + // - lower pitches make it slower + ma_uint32 outputSampleRate = (ma_uint32)((float)AUDIO.System.device.sampleRate/pitch); + ma_data_converter_set_rate(&buffer->converter, buffer->converter.sampleRateIn, outputSampleRate); + + buffer->pitch = pitch; + ma_mutex_unlock(&AUDIO.System.lock); + } +} + +// Set pan for an audio buffer +void SetAudioBufferPan(AudioBuffer *buffer, float pan) +{ + if (pan < -1.0f) pan = -1.0f; + else if (pan > 1.0f) pan = 1.0f; + + if (buffer != NULL) + { + ma_mutex_lock(&AUDIO.System.lock); + buffer->pan = pan; + ma_mutex_unlock(&AUDIO.System.lock); + } +} + +// Track audio buffer to linked list next position +void TrackAudioBuffer(AudioBuffer *buffer) +{ + ma_mutex_lock(&AUDIO.System.lock); + { + if (AUDIO.Buffer.first == NULL) AUDIO.Buffer.first = buffer; + else + { + AUDIO.Buffer.last->next = buffer; + buffer->prev = AUDIO.Buffer.last; + } + + AUDIO.Buffer.last = buffer; + } + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Untrack audio buffer from linked list +void UntrackAudioBuffer(AudioBuffer *buffer) +{ + ma_mutex_lock(&AUDIO.System.lock); + { + if (buffer->prev == NULL) AUDIO.Buffer.first = buffer->next; + else buffer->prev->next = buffer->next; + + if (buffer->next == NULL) AUDIO.Buffer.last = buffer->prev; + else buffer->next->prev = buffer->prev; + + buffer->prev = NULL; + buffer->next = NULL; + } + ma_mutex_unlock(&AUDIO.System.lock); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Sounds loading and playing (.WAV) +//---------------------------------------------------------------------------------- + +// Load wave data from file +Wave LoadWave(const char *fileName) +{ + Wave wave = { 0 }; + + // Loading file to memory + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + // Loading wave from memory data + if (fileData != NULL) wave = LoadWaveFromMemory(GetFileExtension(fileName), fileData, dataSize); + + UnloadFileData(fileData); + + return wave; +} + +// Load wave from memory buffer, fileType refers to extension: i.e. ".wav" +// WARNING: File extension must be provided in lower-case +Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) +{ + Wave wave = { 0 }; + + if (false) { } +#if SUPPORT_FILEFORMAT_WAV + else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0)) + { + drwav wav = { 0 }; + bool success = drwav_init_memory(&wav, fileData, dataSize, NULL); + + if (success) + { + wave.frameCount = (unsigned int)wav.totalPCMFrameCount; + wave.sampleRate = wav.sampleRate; + wave.sampleSize = 16; + wave.channels = wav.channels; + wave.data = (short *)RL_CALLOC((size_t)wave.frameCount*wave.channels, sizeof(short)); + + // NOTE: Forcing conversion to 16bit sample size on reading + drwav_read_pcm_frames_s16(&wav, wave.frameCount, (drwav_int16 *)wave.data); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load WAV data"); + + drwav_uninit(&wav); + } +#endif +#if SUPPORT_FILEFORMAT_OGG + else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0)) + { + stb_vorbis *oggData = stb_vorbis_open_memory((unsigned char *)fileData, dataSize, NULL, NULL); + + if (oggData != NULL) + { + stb_vorbis_info info = stb_vorbis_get_info(oggData); + + wave.sampleRate = info.sample_rate; + wave.sampleSize = 16; // By default, ogg data is 16 bit per sample (short) + wave.channels = info.channels; + wave.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData); // NOTE: It returns frames! + wave.data = (short *)RL_CALLOC(wave.frameCount*wave.channels, sizeof(short)); + + // NOTE: Get the number of samples to process (be careful! asking for number of shorts, not bytes!) + stb_vorbis_get_samples_short_interleaved(oggData, info.channels, (short *)wave.data, wave.frameCount*wave.channels); + stb_vorbis_close(oggData); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data"); + } +#endif +#if SUPPORT_FILEFORMAT_MP3 + else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0)) + { + drmp3_config config = { 0 }; + unsigned long long totalFrameCount = 0; + + // NOTE: Forcing conversion to 32bit float sample size on reading + wave.data = drmp3_open_memory_and_read_pcm_frames_f32(fileData, dataSize, &config, &totalFrameCount, NULL); + wave.sampleSize = 32; + + if (wave.data != NULL) + { + wave.channels = config.channels; + wave.sampleRate = config.sampleRate; + wave.frameCount = (unsigned int)totalFrameCount; // WARNING: Potential loss of data + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data"); + + } +#endif +#if SUPPORT_FILEFORMAT_QOA + else if ((strcmp(fileType, ".qoa") == 0) || (strcmp(fileType, ".QOA") == 0)) + { + qoa_desc qoa = { 0 }; + + // NOTE: Returned sample data is always 16 bit? + wave.data = qoa_decode(fileData, dataSize, &qoa); + wave.sampleSize = 16; + + if (wave.data != NULL) + { + wave.channels = qoa.channels; + wave.sampleRate = qoa.samplerate; + wave.frameCount = qoa.samples; + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load QOA data"); + + } +#endif +#if SUPPORT_FILEFORMAT_FLAC + else if ((strcmp(fileType, ".flac") == 0) || (strcmp(fileType, ".FLAC") == 0)) + { + unsigned long long totalFrameCount = 0; + + // NOTE: Forcing conversion to 16bit sample size on reading + wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, dataSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL); + wave.sampleSize = 16; + + if (wave.data != NULL) wave.frameCount = (unsigned int)totalFrameCount; // WARNING: Potential loss of data + else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data"); + } +#endif + else TRACELOG(LOG_WARNING, "WAVE: Data format not supported"); + + TRACELOG(LOG_INFO, "WAVE: Data loaded successfully (%i Hz, %i bit, %i channels)", wave.sampleRate, wave.sampleSize, wave.channels); + + return wave; +} + +// Check if wave data is valid (data loaded and parameters) +bool IsWaveValid(Wave wave) +{ + bool result = false; + + if ((wave.data != NULL) && // Validate wave data available + (wave.frameCount > 0) && // Validate frame count + (wave.sampleRate > 0) && // Validate sample rate is supported + (wave.sampleSize > 0) && // Validate sample size is supported + (wave.channels > 0)) result = true; // Validate number of channels supported + + return result; +} + +// Load sound from file +// NOTE: The entire file is loaded to memory to be played (no-streaming) +Sound LoadSound(const char *fileName) +{ + Wave wave = LoadWave(fileName); + + Sound sound = LoadSoundFromWave(wave); + + UnloadWave(wave); // Sound is loaded, wave can be unloaded + + return sound; +} + +// Load sound from wave data +// NOTE: Wave data must be unallocated manually +Sound LoadSoundFromWave(Wave wave) +{ + Sound sound = { 0 }; + + if (wave.data != NULL) + { + // When using miniaudio mixing needs to b done manually + // To simplify this, the format of each sound needs to be converted to be consistent with + // the format used to open the playback AUDIO.System.device. It can be done in two ways: + // + // 1) Convert the whole sound in one go at load time (here) + // 2) Convert the audio data in chunks at mixing time + // + // First option has been selected, format conversion is done on the loading stage + // The downside is that it uses more memory if the original sound is u8 or s16 + ma_format formatIn = ((wave.sampleSize == 8)? ma_format_u8 : ((wave.sampleSize == 16)? ma_format_s16 : ma_format_f32)); + ma_uint32 frameCountIn = wave.frameCount; + + ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, NULL, frameCountIn, formatIn, wave.channels, wave.sampleRate); + if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed to get frame count for format conversion"); + + AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, frameCount, AUDIO_BUFFER_USAGE_STATIC); + if (audioBuffer != NULL) + { + frameCount = (ma_uint32)ma_convert_frames(audioBuffer->data, frameCount, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, wave.data, frameCountIn, formatIn, wave.channels, wave.sampleRate); + if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed format conversion"); + + sound.frameCount = frameCount; + sound.stream.sampleRate = AUDIO.System.device.sampleRate; + sound.stream.sampleSize = 32; + sound.stream.channels = AUDIO_DEVICE_CHANNELS; + sound.stream.buffer = audioBuffer; + } + else TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer"); + } + + return sound; +} + +// Load sound alias, clone sound from existing sound data, clone does not own wave data +// NOTE: Wave data must be unallocated manually and will be shared across all clones +Sound LoadSoundAlias(Sound source) +{ + Sound sound = { 0 }; + + if (source.stream.buffer->data != NULL) + { + AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, 0, AUDIO_BUFFER_USAGE_STATIC); + + if (audioBuffer != NULL) + { + audioBuffer->sizeInFrames = source.stream.buffer->sizeInFrames; + audioBuffer->data = source.stream.buffer->data; + + // Initalize the buffer as if it was new + audioBuffer->volume = 1.0f; + audioBuffer->pitch = 1.0f; + audioBuffer->pan = 0.0f; // Center + + sound.frameCount = source.frameCount; + sound.stream.sampleRate = AUDIO.System.device.sampleRate; + sound.stream.sampleSize = 32; + sound.stream.channels = AUDIO_DEVICE_CHANNELS; + sound.stream.buffer = audioBuffer; + } + else TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer"); + } + + return sound; +} + +// Check if sound is valid (data loaded and buffers initialized) +bool IsSoundValid(Sound sound) +{ + bool result = false; + + if ((sound.frameCount > 0) && // Validate frame count + (sound.stream.buffer != NULL) && // Validate stream buffer + (sound.stream.sampleRate > 0) && // Validate sample rate is supported + (sound.stream.sampleSize > 0) && // Validate sample size is supported + (sound.stream.channels > 0)) result = true; // Validate number of channels supported + + return result; +} + +// Unload wave data +void UnloadWave(Wave wave) +{ + RL_FREE(wave.data); + //TRACELOG(LOG_INFO, "WAVE: Unloaded wave data from RAM"); +} + +// Unload sound +void UnloadSound(Sound sound) +{ + UnloadAudioBuffer(sound.stream.buffer); + //TRACELOG(LOG_INFO, "SOUND: Unloaded sound data from RAM"); +} + +void UnloadSoundAlias(Sound alias) +{ + // Untrack and unload the sound buffer, not the sample data, it is shared with the source for the alias + if (alias.stream.buffer != NULL) + { + UntrackAudioBuffer(alias.stream.buffer); + ma_data_converter_uninit(&alias.stream.buffer->converter, NULL); + RL_FREE(alias.stream.buffer->converterResidual); + RL_FREE(alias.stream.buffer); + } +} + +// Update sound buffer with new data +// PARAMS: [data], format must match sound.stream.sampleSize, default 32 bit float - stereo +// PARAMS: [frameCount] must not exceed sound.frameCount +void UpdateSound(Sound sound, const void *data, int frameCount) +{ + if (sound.stream.buffer != NULL) + { + StopAudioBuffer(sound.stream.buffer); + + memcpy(sound.stream.buffer->data, data, frameCount*ma_get_bytes_per_frame(sound.stream.buffer->converter.formatIn, sound.stream.buffer->converter.channelsIn)); + } +} + +// Export wave data to file +bool ExportWave(Wave wave, const char *fileName) +{ + bool result = false; + + if (false) { } +#if SUPPORT_FILEFORMAT_WAV + else if (IsFileExtension(fileName, ".wav")) + { + drwav wav = { 0 }; + drwav_data_format format = { 0 }; + format.container = drwav_container_riff; + if (wave.sampleSize == 32) format.format = DR_WAVE_FORMAT_IEEE_FLOAT; + else format.format = DR_WAVE_FORMAT_PCM; + format.channels = wave.channels; + format.sampleRate = wave.sampleRate; + format.bitsPerSample = wave.sampleSize; + + void *fileData = NULL; + size_t fileDataSize = 0; + result = drwav_init_memory_write(&wav, &fileData, &fileDataSize, &format, NULL); + if (result) result = (int)drwav_write_pcm_frames(&wav, wave.frameCount, wave.data); + drwav_result result = drwav_uninit(&wav); + + if (result == DRWAV_SUCCESS) result = SaveFileData(fileName, (unsigned char *)fileData, (unsigned int)fileDataSize); + + drwav_free(fileData, NULL); + } +#endif +#if SUPPORT_FILEFORMAT_QOA + else if (IsFileExtension(fileName, ".qoa")) + { + if (wave.sampleSize == 16) + { + qoa_desc qoa = { 0 }; + qoa.channels = wave.channels; + qoa.samplerate = wave.sampleRate; + qoa.samples = wave.frameCount; + + int bytesWritten = qoa_write(fileName, (const short *)wave.data, &qoa); + if (bytesWritten > 0) result = true; + } + else TRACELOG(LOG_WARNING, "AUDIO: Wave data must be 16 bit per sample for QOA format export"); + } +#endif + else if (IsFileExtension(fileName, ".raw")) + { + // Export raw sample data (without header) + // NOTE: It's up to the user to track wave parameters + result = SaveFileData(fileName, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8); + } + + if (result) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave data exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave data", fileName); + + return result; +} + +// Export wave sample data to code (.h) +bool ExportWaveAsCode(Wave wave, const char *fileName) +{ + bool result = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + int waveDataSize = wave.frameCount*wave.channels*wave.sampleSize/8; + + // NOTE: Text data buffer size is estimated considering wave data size in bytes + // and requiring 12 char bytes for every byte; the actual size varies, but + // the longest possible char being appended is "%.4ff,\n ", which is 12 bytes + char *txtData = (char *)RL_CALLOC(waveDataSize*12 + 2000, sizeof(char)); + + int byteCount = 0; + byteCount += sprintf(txtData + byteCount, "\n//////////////////////////////////////////////////////////////////////////////////\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// WaveAsCode exporter v1.1 - Wave data exported as an array of bytes //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2026 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "//////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path and convert variable name to uppercase + char varFileName[256] = { 0 }; + snprintf(varFileName, 256, "%s", GetFileNameWithoutExt(fileName)); // NOTE: Using function provided by [rcore] module + for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } + + // Add wave information + byteCount += sprintf(txtData + byteCount, "// Wave data information\n"); + byteCount += sprintf(txtData + byteCount, "#define %s_FRAME_COUNT %u\n", varFileName, wave.frameCount); + byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_RATE %u\n", varFileName, wave.sampleRate); + byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_SIZE %u\n", varFileName, wave.sampleSize); + byteCount += sprintf(txtData + byteCount, "#define %s_CHANNELS %u\n\n", varFileName, wave.channels); + + // Write wave data as an array of values + // Wave data is exported as byte array for 8/16bit and float array for 32bit float data + // NOTE: Frame data exported is channel-interlaced: frame01[sampleChannel1, sampleChannel2, ...], frame02[], frame03[] + if (wave.sampleSize == 32) + { + byteCount += sprintf(txtData + byteCount, "static float %s_DATA[%i] = {\n", varFileName, waveDataSize/4); + for (int i = 1; i < waveDataSize/4; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.4ff,\n " : "%.4ff, "), ((float *)wave.data)[i - 1]); + byteCount += sprintf(txtData + byteCount, "%.4ff };\n", ((float *)wave.data)[waveDataSize/4 - 1]); + } + else + { + byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%i] = { ", varFileName, waveDataSize); + for (int i = 1; i < waveDataSize; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n " : "0x%x, "), ((unsigned char *)wave.data)[i - 1]); + byteCount += sprintf(txtData + byteCount, "0x%x };\n", ((unsigned char *)wave.data)[waveDataSize - 1]); + } + + // NOTE: Text data length exported is determined by '\0' (NULL) character + result = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + if (result != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave as code exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave as code", fileName); + + return result; +} + +// Play a sound +void PlaySound(Sound sound) +{ + PlayAudioBuffer(sound.stream.buffer); +} + +// Pause a sound +void PauseSound(Sound sound) +{ + PauseAudioBuffer(sound.stream.buffer); +} + +// Resume a paused sound +void ResumeSound(Sound sound) +{ + ResumeAudioBuffer(sound.stream.buffer); +} + +// Stop reproducing a sound +void StopSound(Sound sound) +{ + StopAudioBuffer(sound.stream.buffer); +} + +// Check if sound is playing +bool IsSoundPlaying(Sound sound) +{ + bool result = false; + + if (IsAudioBufferPlaying(sound.stream.buffer)) result = true; + + return result; +} + +// Set volume for a sound +void SetSoundVolume(Sound sound, float volume) +{ + SetAudioBufferVolume(sound.stream.buffer, volume); +} + +// Set pitch for a sound +void SetSoundPitch(Sound sound, float pitch) +{ + SetAudioBufferPitch(sound.stream.buffer, pitch); +} + +// Set pan for a sound +void SetSoundPan(Sound sound, float pan) +{ + SetAudioBufferPan(sound.stream.buffer, pan); +} + +// Convert wave data to desired format +void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) +{ + ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32)); + ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32)); + + ma_uint32 frameCountIn = wave->frameCount; + ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate); + + if (frameCount == 0) + { + TRACELOG(LOG_WARNING, "WAVE: Failed to get frame count for format conversion"); + return; + } + + void *data = RL_CALLOC(frameCount*channels*(sampleSize/8), 1); + + frameCount = (ma_uint32)ma_convert_frames(data, frameCount, formatOut, channels, sampleRate, wave->data, frameCountIn, formatIn, wave->channels, wave->sampleRate); + if (frameCount == 0) + { + RL_FREE(data); + TRACELOG(LOG_WARNING, "WAVE: Failed format conversion"); + return; + } + + wave->frameCount = frameCount; + wave->sampleSize = sampleSize; + wave->sampleRate = sampleRate; + wave->channels = channels; + + RL_FREE(wave->data); + wave->data = data; +} + +// Copy a wave to a new wave +Wave WaveCopy(Wave wave) +{ + Wave newWave = { 0 }; + + newWave.data = RL_MALLOC(wave.frameCount*wave.channels*wave.sampleSize/8); + + if (newWave.data != NULL) + { + // NOTE: Size must be provided in bytes + memcpy(newWave.data, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8); + + newWave.frameCount = wave.frameCount; + newWave.sampleRate = wave.sampleRate; + newWave.sampleSize = wave.sampleSize; + newWave.channels = wave.channels; + } + + return newWave; +} + +// Crop a wave to defined frames range +// NOTE: Security check in case of out-of-range +void WaveCrop(Wave *wave, int initFrame, int finalFrame) +{ + if ((initFrame >= 0) && (initFrame < finalFrame) && ((unsigned int)finalFrame <= wave->frameCount)) + { + int frameCount = finalFrame - initFrame; + + void *data = RL_MALLOC(frameCount*wave->channels*wave->sampleSize/8); + + memcpy(data, (unsigned char *)wave->data + (initFrame*wave->channels*wave->sampleSize/8), frameCount*wave->channels*wave->sampleSize/8); + + RL_FREE(wave->data); + wave->data = data; + wave->frameCount = (unsigned int)frameCount; + } + else TRACELOG(LOG_WARNING, "WAVE: Crop range out of bounds"); +} + +// Load samples data from wave as a floats array +// NOTE 1: Returned sample values are normalized to range [-1..1] +// NOTE 2: Sample data allocated should be freed with UnloadWaveSamples() +float *LoadWaveSamples(Wave wave) +{ + float *samples = (float *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(float)); + + // NOTE: sampleCount is the total number of interlaced samples (including channels) + + for (unsigned int i = 0; i < wave.frameCount*wave.channels; i++) + { + if (wave.sampleSize == 8) samples[i] = (float)(((unsigned char *)wave.data)[i] - 128)/128.0f; + else if (wave.sampleSize == 16) samples[i] = (float)(((short *)wave.data)[i])/32768.0f; + else if (wave.sampleSize == 32) samples[i] = ((float *)wave.data)[i]; + } + + return samples; +} + +// Unload samples data loaded with LoadWaveSamples() +void UnloadWaveSamples(float *samples) +{ + RL_FREE(samples); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Music loading and stream playing +//---------------------------------------------------------------------------------- + +// Load music stream from file +Music LoadMusicStream(const char *fileName) +{ + Music music = { 0 }; + bool musicLoaded = false; + + if (false) { } +#if SUPPORT_FILEFORMAT_WAV + else if (IsFileExtension(fileName, ".wav")) + { + drwav *ctxWav = (drwav *)RL_CALLOC(1, sizeof(drwav)); + bool success = drwav_init_file(ctxWav, fileName, NULL); + + if (success) + { + music.ctxType = MUSIC_AUDIO_WAV; + music.ctxData = ctxWav; + int sampleSize = ctxWav->bitsPerSample; + if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + + music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); + music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else + { + RL_FREE(ctxWav); + } + } +#endif +#if SUPPORT_FILEFORMAT_OGG + else if (IsFileExtension(fileName, ".ogg")) + { + // Open ogg audio stream + stb_vorbis *ctxOgg = stb_vorbis_open_filename(fileName, NULL, NULL); + + if (ctxOgg != NULL) + { + music.ctxType = MUSIC_AUDIO_OGG; + music.ctxData = ctxOgg; + stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info + + // OGG bit rate defaults to 16 bit, it's enough for compressed format + music.stream = LoadAudioStream(info.sample_rate, 16, info.channels); + + // WARNING: It seems this function returns length in frames, not samples, so multiply by channels + music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData); + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else + { + stb_vorbis_close(ctxOgg); + } + } +#endif +#if SUPPORT_FILEFORMAT_MP3 + else if (IsFileExtension(fileName, ".mp3")) + { + drmp3 *ctxMp3 = (drmp3 *)RL_CALLOC(1, sizeof(drmp3)); + int result = drmp3_init_file(ctxMp3, fileName, NULL); + + if (result > 0) + { + music.ctxType = MUSIC_AUDIO_MP3; + music.ctxData = ctxMp3; + music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); + music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3); + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else + { + RL_FREE(ctxMp3); + } + } +#endif +#if SUPPORT_FILEFORMAT_QOA + else if (IsFileExtension(fileName, ".qoa")) + { + qoaplay_desc *ctxQoa = qoaplay_open(fileName); + + if (ctxQoa != NULL) + { + music.ctxType = MUSIC_AUDIO_QOA; + music.ctxData = ctxQoa; + // NOTE: Loading samples as 32bit float normalized data, so, + // configure the output audio stream to also use float 32bit + music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels); + music.frameCount = ctxQoa->info.samples; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else{} //No uninit required + } +#endif +#if SUPPORT_FILEFORMAT_FLAC + else if (IsFileExtension(fileName, ".flac")) + { + drflac *ctxFlac = drflac_open_file(fileName, NULL); + + if (ctxFlac != NULL) + { + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = ctxFlac; + int sampleSize = ctxFlac->bitsPerSample; + if (ctxFlac->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + music.stream = LoadAudioStream(ctxFlac->sampleRate, sampleSize, ctxFlac->channels); + music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else + { + drflac_free(ctxFlac, NULL); + } + } +#endif +#if SUPPORT_FILEFORMAT_XM + else if (IsFileExtension(fileName, ".xm")) + { + jar_xm_context_t *ctxXm = NULL; + int result = jar_xm_create_context_from_file(&ctxXm, AUDIO.System.device.sampleRate, fileName); + + if (result == 0) // XM AUDIO.System.context created successfully + { + music.ctxType = MUSIC_MODULE_XM; + music.ctxData = ctxXm; + jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops + + unsigned int bits = 32; + if (AUDIO_DEVICE_FORMAT == ma_format_s16) bits = 16; + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) bits = 8; + + // NOTE: Only stereo is supported for XM + music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, AUDIO_DEVICE_CHANNELS); + music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); // NOTE: Always 2 channels (stereo) + music.looping = true; // Looping enabled by default + jar_xm_reset(ctxXm); // Make sure to start at the beginning of the song + musicLoaded = true; + } + else + { + jar_xm_free_context(ctxXm); + } + } +#endif +#if SUPPORT_FILEFORMAT_MOD + else if (IsFileExtension(fileName, ".mod")) + { + jar_mod_context_t *ctxMod = (jar_mod_context_t *)RL_CALLOC(1, sizeof(jar_mod_context_t)); + jar_mod_init(ctxMod); + int result = jar_mod_load_file(ctxMod, fileName); + + if (result > 0) + { + music.ctxType = MUSIC_MODULE_MOD; + music.ctxData = ctxMod; + // NOTE: Only stereo is supported for MOD + music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, AUDIO_DEVICE_CHANNELS); + music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); // NOTE: Always 2 channels (stereo) + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else + { + jar_mod_unload(ctxMod); + RL_FREE(ctxMod); + } + } +#endif + else TRACELOG(LOG_WARNING, "STREAM: [%s] File format not supported", fileName); + + if (!musicLoaded) + { + TRACELOG(LOG_WARNING, "FILEIO: [%s] Music file could not be opened", fileName); + } + else + { + // Show some music stream info + TRACELOG(LOG_INFO, "FILEIO: [%s] Music file loaded successfully", fileName); + TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); + TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); + TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); + TRACELOG(LOG_INFO, " > Total frames: %i", music.frameCount); + } + + return music; +} + +// Load music stream from memory buffer, fileType refers to extension: i.e. ".wav" +// WARNING: File extension must be provided in lower-case +Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize) +{ + Music music = { 0 }; + bool musicLoaded = false; + + if (false) { } +#if SUPPORT_FILEFORMAT_WAV + else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0)) + { + drwav *ctxWav = (drwav *)RL_CALLOC(1, sizeof(drwav)); + + bool success = drwav_init_memory(ctxWav, (const void *)data, dataSize, NULL); + + if (success) + { + music.ctxType = MUSIC_AUDIO_WAV; + music.ctxData = ctxWav; + int sampleSize = ctxWav->bitsPerSample; + if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + + music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); + music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else + { + drwav_uninit(ctxWav); + RL_FREE(ctxWav); + } + } +#endif +#if SUPPORT_FILEFORMAT_OGG + else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0)) + { + // Open ogg audio stream + stb_vorbis *ctxOgg = stb_vorbis_open_memory((const unsigned char *)data, dataSize, NULL, NULL); + + if (ctxOgg != NULL) + { + music.ctxType = MUSIC_AUDIO_OGG; + music.ctxData = ctxOgg; + stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info + + // OGG bit rate defaults to 16 bit, it's enough for compressed format + music.stream = LoadAudioStream(info.sample_rate, 16, info.channels); + + // WARNING: It seems this function returns length in frames, not samples, so multiply by channels + music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData); + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else + { + stb_vorbis_close(ctxOgg); + } + } +#endif +#if SUPPORT_FILEFORMAT_MP3 + else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0)) + { + drmp3 *ctxMp3 = (drmp3 *)RL_CALLOC(1, sizeof(drmp3)); + int success = drmp3_init_memory(ctxMp3, (const void *)data, dataSize, NULL); + + if (success) + { + music.ctxType = MUSIC_AUDIO_MP3; + music.ctxData = ctxMp3; + music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); + music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3); + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else + { + drmp3_uninit(ctxMp3); + RL_FREE(ctxMp3); + } + } +#endif +#if SUPPORT_FILEFORMAT_QOA + else if ((strcmp(fileType, ".qoa") == 0) || (strcmp(fileType, ".QOA") == 0)) + { + qoaplay_desc *ctxQoa = NULL; + if ((data != NULL) && (dataSize > 0)) + { + ctxQoa = qoaplay_open_memory(data, dataSize); + } + + if (ctxQoa != NULL) + { + music.ctxType = MUSIC_AUDIO_QOA; + music.ctxData = ctxQoa; + + // NOTE: Loading samples are 32bit float normalized data, so, + // configure the output audio stream to also use float 32bit + music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels); + music.frameCount = ctxQoa->info.samples; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else{} //No uninit required + } +#endif +#if SUPPORT_FILEFORMAT_FLAC + else if ((strcmp(fileType, ".flac") == 0) || (strcmp(fileType, ".FLAC") == 0)) + { + drflac *ctxFlac = drflac_open_memory((const void *)data, dataSize, NULL); + + if (ctxFlac != NULL) + { + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = ctxFlac; + int sampleSize = ctxFlac->bitsPerSample; + if (ctxFlac->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + music.stream = LoadAudioStream(ctxFlac->sampleRate, sampleSize, ctxFlac->channels); + music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else + { + drflac_free(ctxFlac, NULL); + } + } +#endif +#if SUPPORT_FILEFORMAT_XM + else if ((strcmp(fileType, ".xm") == 0) || (strcmp(fileType, ".XM") == 0)) + { + jar_xm_context_t *ctxXm = NULL; + int result = jar_xm_create_context_safe(&ctxXm, (const char *)data, dataSize, AUDIO.System.device.sampleRate); + if (result == 0) // XM AUDIO.System.context created successfully + { + music.ctxType = MUSIC_MODULE_XM; + music.ctxData = ctxXm; + jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops + + unsigned int bits = 32; + if (AUDIO_DEVICE_FORMAT == ma_format_s16) bits = 16; + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) bits = 8; + + // NOTE: Only stereo is supported for XM + music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, 2); + music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); // NOTE: Always 2 channels (stereo) + music.looping = true; // Looping enabled by default + jar_xm_reset(ctxXm); // Make sure to start at the beginning of the song + + musicLoaded = true; + } + else + { + jar_xm_free_context(ctxXm); + } + } +#endif +#if SUPPORT_FILEFORMAT_MOD + else if ((strcmp(fileType, ".mod") == 0) || (strcmp(fileType, ".MOD") == 0)) + { + jar_mod_context_t *ctxMod = (jar_mod_context_t *)RL_MALLOC(sizeof(jar_mod_context_t)); + int result = 0; + + jar_mod_init(ctxMod); + + // Copy data to allocated memory for default UnloadMusicStream + unsigned char *newData = (unsigned char *)RL_MALLOC(dataSize); + for (int i = 0; i < dataSize; i++) newData[i] = data[i]; + + // Memory loaded version for jar_mod_load_file() + if (dataSize && (dataSize < 32*1024*1024)) + { + ctxMod->modfilesize = dataSize; + ctxMod->modfile = newData; + if (jar_mod_load(ctxMod, (void *)ctxMod->modfile, dataSize)) result = dataSize; + } + + if (result > 0) + { + music.ctxType = MUSIC_MODULE_MOD; + music.ctxData = ctxMod; + + // NOTE: Only stereo is supported for MOD + music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, 2); + music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); // NOTE: Always 2 channels (stereo) + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + else + { + jar_mod_unload(ctxMod); + RL_FREE(ctxMod); + } + } +#endif + else TRACELOG(LOG_WARNING, "STREAM: Data format not supported"); + + if (!musicLoaded) + { + TRACELOG(LOG_WARNING, "FILEIO: Music data could not be loaded"); + } + else + { + // Show some music stream info + TRACELOG(LOG_INFO, "FILEIO: Music data loaded successfully"); + TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); + TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); + TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); + TRACELOG(LOG_INFO, " > Total frames: %i", music.frameCount); + } + + return music; +} + +// Check if music stream is valid (context and buffers initialized) +bool IsMusicValid(Music music) +{ + return ((music.ctxData != NULL) && // Validate context loaded + (music.frameCount > 0) && // Validate audio frame count + (music.stream.sampleRate > 0) && // Validate sample rate is supported + (music.stream.sampleSize > 0) && // Validate sample size is supported + (music.stream.channels > 0)); // Validate number of channels supported +} + +// Unload music stream +void UnloadMusicStream(Music music) +{ + if (IsMusicStreamPlaying(music)) StopMusicStream(music); + + UnloadAudioStream(music.stream); + + if (music.ctxData != NULL) + { + if (false) { } +#if SUPPORT_FILEFORMAT_WAV + else if (music.ctxType == MUSIC_AUDIO_WAV) { drwav_uninit((drwav *)music.ctxData); RL_FREE(music.ctxData); } +#endif +#if SUPPORT_FILEFORMAT_OGG + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); +#endif +#if SUPPORT_FILEFORMAT_MP3 + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } +#endif +#if SUPPORT_FILEFORMAT_QOA + else if (music.ctxType == MUSIC_AUDIO_QOA) qoaplay_close((qoaplay_desc *)music.ctxData); +#endif +#if SUPPORT_FILEFORMAT_FLAC + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_close((drflac *)music.ctxData); +#endif +#if SUPPORT_FILEFORMAT_XM + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); +#endif +#if SUPPORT_FILEFORMAT_MOD + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } +#endif + } +} + +// Start music playing (open stream) from beginning +void PlayMusicStream(Music music) +{ + PlayAudioStream(music.stream); +} + +// Pause music playing +void PauseMusicStream(Music music) +{ + PauseAudioStream(music.stream); +} + +// Resume music playing +void ResumeMusicStream(Music music) +{ + ResumeAudioStream(music.stream); +} + +// Stop music playing (close stream) +void StopMusicStream(Music music) +{ + StopAudioStream(music.stream); + + switch (music.ctxType) + { +#if SUPPORT_FILEFORMAT_WAV + case MUSIC_AUDIO_WAV: drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); break; +#endif +#if SUPPORT_FILEFORMAT_OGG + case MUSIC_AUDIO_OGG: stb_vorbis_seek_start((stb_vorbis *)music.ctxData); break; +#endif +#if SUPPORT_FILEFORMAT_MP3 + case MUSIC_AUDIO_MP3: drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); break; +#endif +#if SUPPORT_FILEFORMAT_QOA + case MUSIC_AUDIO_QOA: qoaplay_rewind((qoaplay_desc *)music.ctxData); break; +#endif +#if SUPPORT_FILEFORMAT_FLAC + case MUSIC_AUDIO_FLAC: drflac__seek_to_first_frame((drflac *)music.ctxData); break; +#endif +#if SUPPORT_FILEFORMAT_XM + case MUSIC_MODULE_XM: jar_xm_reset((jar_xm_context_t *)music.ctxData); break; +#endif +#if SUPPORT_FILEFORMAT_MOD + case MUSIC_MODULE_MOD: jar_mod_seek_start((jar_mod_context_t *)music.ctxData); break; +#endif + default: break; + } +} + +// Seek music to a certain position (in seconds) +void SeekMusicStream(Music music, float position) +{ + // Seeking is not supported in module formats + if ((music.ctxType == MUSIC_MODULE_XM) || (music.ctxType == MUSIC_MODULE_MOD)) return; + + unsigned int positionInFrames = (unsigned int)(position*music.stream.sampleRate); + + switch (music.ctxType) + { +#if SUPPORT_FILEFORMAT_WAV + case MUSIC_AUDIO_WAV: drwav_seek_to_pcm_frame((drwav *)music.ctxData, positionInFrames); break; +#endif +#if SUPPORT_FILEFORMAT_OGG + case MUSIC_AUDIO_OGG: stb_vorbis_seek_frame((stb_vorbis *)music.ctxData, positionInFrames); break; +#endif +#if SUPPORT_FILEFORMAT_MP3 + case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, positionInFrames); break; +#endif +#if SUPPORT_FILEFORMAT_QOA + case MUSIC_AUDIO_QOA: + { + int qoaFrame = positionInFrames/QOA_FRAME_LEN; + qoaplay_seek_frame((qoaplay_desc *)music.ctxData, qoaFrame); // Seeks to QOA frame, not PCM frame + + // Compute QOA frame number and update positionInFrames + positionInFrames = ((qoaplay_desc *)music.ctxData)->sample_position; + } break; +#endif +#if SUPPORT_FILEFORMAT_FLAC + case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, positionInFrames); break; +#endif + default: break; + } + + ma_mutex_lock(&AUDIO.System.lock); + music.stream.buffer->framesProcessed = positionInFrames; + music.stream.buffer->isSubBufferProcessed[0] = true; + music.stream.buffer->isSubBufferProcessed[1] = true; + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Update (re-fill) music buffers if data already processed +void UpdateMusicStream(Music music) +{ + if (music.stream.buffer == NULL) return; + if (!music.stream.buffer->playing) return; + + ma_mutex_lock(&AUDIO.System.lock); + + unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2; + + // On first call of this function, lazily pre-allocated a temp buffer to read audio files/memory data in + int frameSize = music.stream.channels*music.stream.sampleSize/8; + unsigned int pcmSize = subBufferSizeInFrames*frameSize; + + if (AUDIO.System.pcmBufferSize < pcmSize) + { + RL_FREE(AUDIO.System.pcmBuffer); + AUDIO.System.pcmBuffer = RL_CALLOC(1, pcmSize); + AUDIO.System.pcmBufferSize = pcmSize; + } + + // Check both sub-buffers to check if they require refilling + for (int i = 0; i < 2; i++) + { + unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; // Frames left to be processed + unsigned int framesToStream = 0; // Total frames to be streamed + + if ((framesLeft >= subBufferSizeInFrames) || music.looping) framesToStream = subBufferSizeInFrames; + else framesToStream = framesLeft; + + if (framesToStream == 0) + { + // Check if both buffers have been processed + if (music.stream.buffer->isSubBufferProcessed[0] && music.stream.buffer->isSubBufferProcessed[1]) + { + ma_mutex_unlock(&AUDIO.System.lock); + StopMusicStream(music); + return; + } + + ma_mutex_unlock(&AUDIO.System.lock); + return; + } + + if (!music.stream.buffer->isSubBufferProcessed[i]) continue; // No refilling required, move to next sub-buffer + + int frameCountStillNeeded = framesToStream; + int frameCountReadTotal = 0; + + switch (music.ctxType) + { + #if SUPPORT_FILEFORMAT_WAV + case MUSIC_AUDIO_WAV: + { + if (music.stream.sampleSize == 16) + { + while (true) + { + int frameCountRead = (int)drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); + } + } + else if (music.stream.sampleSize == 32) + { + while (true) + { + int frameCountRead = (int)drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); + } + } + } break; + #endif + #if SUPPORT_FILEFORMAT_OGG + case MUSIC_AUDIO_OGG: + { + while (true) + { + int frameCountRead = stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded*music.stream.channels); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else stb_vorbis_seek_start((stb_vorbis *)music.ctxData); + } + } break; + #endif + #if SUPPORT_FILEFORMAT_MP3 + case MUSIC_AUDIO_MP3: + { + while (true) + { + int frameCountRead = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); + } + } break; + #endif + #if SUPPORT_FILEFORMAT_QOA + case MUSIC_AUDIO_QOA: + { + unsigned int frameCountRead = qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream); + frameCountReadTotal += frameCountRead; + /* + while (true) + { + int frameCountRead = (int)qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else qoaplay_rewind((qoaplay_desc *)music.ctxData); + } + */ + } break; + #endif + #if SUPPORT_FILEFORMAT_FLAC + case MUSIC_AUDIO_FLAC: + { + while (true) + { + int frameCountRead = (int)drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else drflac__seek_to_first_frame((drflac *)music.ctxData); + } + } break; + #endif + #if SUPPORT_FILEFORMAT_XM + case MUSIC_MODULE_XM: + { + // NOTE: Internally considering 2 channels generation, so sampleCount/2 + if (AUDIO_DEVICE_FORMAT == ma_format_f32) jar_xm_generate_samples((jar_xm_context_t *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream); + else if (AUDIO_DEVICE_FORMAT == ma_format_s16) jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream); + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) jar_xm_generate_samples_8bit((jar_xm_context_t *)music.ctxData, (char *)AUDIO.System.pcmBuffer, framesToStream); + //jar_xm_reset((jar_xm_context_t *)music.ctxData); + + } break; + #endif + #if SUPPORT_FILEFORMAT_MOD + case MUSIC_MODULE_MOD: + { + // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples desired, so sampleCount/2 + jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream, 0); + //jar_mod_seek_start((jar_mod_context_t *)music.ctxData); + + } break; + #endif + default: break; + } + + UpdateAudioStreamInLockedState(music.stream, AUDIO.System.pcmBuffer, framesToStream); + } + + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Check if any music is playing +bool IsMusicStreamPlaying(Music music) +{ + return IsAudioStreamPlaying(music.stream); +} + +// Set volume for music +void SetMusicVolume(Music music, float volume) +{ + SetAudioStreamVolume(music.stream, volume); +} + +// Set pitch for music +void SetMusicPitch(Music music, float pitch) +{ + SetAudioBufferPitch(music.stream.buffer, pitch); +} + +// Set pan for music +void SetMusicPan(Music music, float pan) +{ + SetAudioBufferPan(music.stream.buffer, pan); +} + +// Get music time length (in seconds) +float GetMusicTimeLength(Music music) +{ + float totalSeconds = 0.0f; + + totalSeconds = (float)music.frameCount/music.stream.sampleRate; + + return totalSeconds; +} + +// Get current music time played (in seconds) +float GetMusicTimePlayed(Music music) +{ + float secondsPlayed = 0.0f; + if (music.stream.buffer != NULL) + { +#if SUPPORT_FILEFORMAT_XM + if (music.ctxType == MUSIC_MODULE_XM) + { + uint64_t framesPlayed = 0; + + jar_xm_get_position((jar_xm_context_t *)music.ctxData, NULL, NULL, NULL, &framesPlayed); + secondsPlayed = (float)framesPlayed/music.stream.sampleRate; + } + else +#endif + { + ma_mutex_lock(&AUDIO.System.lock); + //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; + int framesProcessed = (int)music.stream.buffer->framesProcessed; + int subBufferSize = (int)music.stream.buffer->sizeInFrames/2; + int framesInFirstBuffer = music.stream.buffer->isSubBufferProcessed[0]? 0 : subBufferSize; + int framesInSecondBuffer = music.stream.buffer->isSubBufferProcessed[1]? 0 : subBufferSize; + int framesInBuffers = framesInFirstBuffer + framesInSecondBuffer; + if (((unsigned int)framesInBuffers > music.frameCount) && !music.looping) framesInBuffers = music.frameCount; + int framesSentToMix = music.stream.buffer->frameCursorPos%subBufferSize; + int framesPlayed = (framesProcessed - framesInBuffers + framesSentToMix)%(int)music.frameCount; + if (framesPlayed < 0) framesPlayed += music.frameCount; + secondsPlayed = (float)framesPlayed/music.stream.sampleRate; + ma_mutex_unlock(&AUDIO.System.lock); + } + } + + return secondsPlayed; +} + +// Load audio stream (to stream audio pcm data) +AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) +{ + AudioStream stream = { 0 }; + + stream.sampleRate = sampleRate; + stream.sampleSize = sampleSize; + stream.channels = channels; + + ma_format formatIn = ((stream.sampleSize == 8)? ma_format_u8 : ((stream.sampleSize == 16)? ma_format_s16 : ma_format_f32)); + + // The size of a streaming buffer must be at least double the size of a period + unsigned int periodSize = AUDIO.System.device.playback.internalPeriodSizeInFrames; + + // If the buffer is not set, compute one that would give us a buffer good enough for a decent frame rate at the device bit size/rate + int deviceBitsPerSample = AUDIO.System.device.playback.format; + if (deviceBitsPerSample > 4) deviceBitsPerSample = 4; + deviceBitsPerSample *= AUDIO.System.device.playback.channels; + + unsigned int subBufferSize = (AUDIO.Buffer.defaultSize == 0)? (AUDIO.System.device.sampleRate/30*deviceBitsPerSample) : AUDIO.Buffer.defaultSize; + + if (subBufferSize < periodSize) subBufferSize = periodSize; + + // Create a double audio buffer of defined size + stream.buffer = LoadAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM); + + if (stream.buffer != NULL) + { + stream.buffer->looping = true; // Always loop for streaming buffers + TRACELOG(LOG_INFO, "STREAM: Initialized successfully (%i Hz, %i bit, %s)", stream.sampleRate, stream.sampleSize, (stream.channels == 1)? "Mono" : "Stereo"); + } + else TRACELOG(LOG_WARNING, "STREAM: Failed to load audio buffer, stream could not be created"); + + return stream; +} + +// Check if an audio stream is valid (buffers initialized) +bool IsAudioStreamValid(AudioStream stream) +{ + return ((stream.buffer != NULL) && // Validate stream buffer + (stream.sampleRate > 0) && // Validate sample rate is supported + (stream.sampleSize > 0) && // Validate sample size is supported + (stream.channels > 0)); // Validate number of channels supported +} + +// Unload audio stream and free memory +void UnloadAudioStream(AudioStream stream) +{ + UnloadAudioBuffer(stream.buffer); + + TRACELOG(LOG_INFO, "STREAM: Unloaded audio stream data from RAM"); +} + +// Update audio stream buffers with data +// NOTE 1: Only updates one buffer of the stream source: dequeue -> update -> queue +// NOTE 2: To dequeue a buffer it needs to be processed: IsAudioStreamProcessed() +void UpdateAudioStream(AudioStream stream, const void *data, int frameCount) +{ + ma_mutex_lock(&AUDIO.System.lock); + UpdateAudioStreamInLockedState(stream, data, frameCount); + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Check if any audio stream buffers requires refill +bool IsAudioStreamProcessed(AudioStream stream) +{ + bool result = false; + + if (stream.buffer != NULL) + { + ma_mutex_lock(&AUDIO.System.lock); + result = stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1]; + ma_mutex_unlock(&AUDIO.System.lock); + } + + return result; +} + +// Play audio stream +void PlayAudioStream(AudioStream stream) +{ + PlayAudioBuffer(stream.buffer); +} + +// Play audio stream +void PauseAudioStream(AudioStream stream) +{ + PauseAudioBuffer(stream.buffer); +} + +// Resume audio stream playing +void ResumeAudioStream(AudioStream stream) +{ + ResumeAudioBuffer(stream.buffer); +} + +// Check if audio stream is playing +bool IsAudioStreamPlaying(AudioStream stream) +{ + return IsAudioBufferPlaying(stream.buffer); +} + +// Stop audio stream +void StopAudioStream(AudioStream stream) +{ + StopAudioBuffer(stream.buffer); +} + +// Set volume for audio stream (1.0 is max level) +void SetAudioStreamVolume(AudioStream stream, float volume) +{ + SetAudioBufferVolume(stream.buffer, volume); +} + +// Set pitch for audio stream (1.0 is base level) +void SetAudioStreamPitch(AudioStream stream, float pitch) +{ + SetAudioBufferPitch(stream.buffer, pitch); +} + +// Set pan for audio stream +void SetAudioStreamPan(AudioStream stream, float pan) +{ + SetAudioBufferPan(stream.buffer, pan); +} + +// Default size for new audio streams +void SetAudioStreamBufferSizeDefault(int size) +{ + AUDIO.Buffer.defaultSize = size; +} + +// Audio thread callback to request new data +void SetAudioStreamCallback(AudioStream stream, AudioCallback callback) +{ + if (stream.buffer != NULL) + { + ma_mutex_lock(&AUDIO.System.lock); + stream.buffer->callback = callback; + ma_mutex_unlock(&AUDIO.System.lock); + } +} + +// Add processor to audio stream. Contrary to buffers, the order of processors is important +// The new processor must be added at the end. As there aren't supposed to be a lot of processors attached to +// a given stream, iterate through the list to find the end. That way there is no need to keep a pointer to the last element +void AttachAudioStreamProcessor(AudioStream stream, AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor)); + processor->process = process; + + rAudioProcessor *last = stream.buffer->processor; + + while (last && last->next) + { + last = last->next; + } + if (last) + { + processor->prev = last; + last->next = processor; + } + else stream.buffer->processor = processor; + + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Remove processor from audio stream +void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = stream.buffer->processor; + + while (processor) + { + rAudioProcessor *next = processor->next; + rAudioProcessor *prev = processor->prev; + + if (processor->process == process) + { + if (stream.buffer->processor == processor) stream.buffer->processor = next; + if (prev) prev->next = next; + if (next) next->prev = prev; + + RL_FREE(processor); + } + + processor = next; + } + + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Add processor to audio pipeline. Order of processors is important +// Works the same way as {Attach,Detach}AudioStreamProcessor() functions, except +// these two work on the already mixed output before sending it to the sound hardware +void AttachAudioMixedProcessor(AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor)); + processor->process = process; + + rAudioProcessor *last = AUDIO.mixedProcessor; + + while (last && last->next) + { + last = last->next; + } + if (last) + { + processor->prev = last; + last->next = processor; + } + else AUDIO.mixedProcessor = processor; + + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Remove processor from audio pipeline +void DetachAudioMixedProcessor(AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = AUDIO.mixedProcessor; + + while (processor) + { + rAudioProcessor *next = processor->next; + rAudioProcessor *prev = processor->prev; + + if (processor->process == process) + { + if (AUDIO.mixedProcessor == processor) AUDIO.mixedProcessor = next; + if (prev) prev->next = next; + if (next) next->prev = prev; + + RL_FREE(processor); + } + + processor = next; + } + + ma_mutex_unlock(&AUDIO.System.lock); +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +// Log callback function +static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage) +{ + TRACELOG(LOG_WARNING, "miniaudio: %s", pMessage); // All log messages from miniaudio are errors +} + +// Reads audio data from an AudioBuffer object in internal format +static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount) +{ + // Don't read anything if the sound is not playing + if (!audioBuffer->playing) return 0; + + // Using audio buffer callback + if (audioBuffer->callback) + { + audioBuffer->callback(framesOut, frameCount); + audioBuffer->framesProcessed += frameCount; + + return frameCount; + } + + ma_uint32 subBufferSizeInFrames = (audioBuffer->sizeInFrames > 1)? audioBuffer->sizeInFrames/2 : audioBuffer->sizeInFrames; + ma_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames; + + if (currentSubBufferIndex > 1) return 0; + + // Another thread can update the processed state of buffers, so + // take a copy here to try and avoid potential synchronization problems + bool isSubBufferProcessed[2] = { 0 }; + isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0]; + isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1]; + + ma_uint32 frameSizeInBytes = ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn); + + // Fill out every frame until a buffer that's marked as processed is found, then fill the remainder with 0 + ma_uint32 framesRead = 0; + while (1) + { + // Break from this loop differently depending on the buffer's usage + // - For static buffers, simply fill as much data as possible + // - For streaming buffers, only fill half of the buffer that are processed + // Unprocessed halves must keep their audio data in-tact + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) + { + if (framesRead >= frameCount) break; + } + else + { + if (isSubBufferProcessed[currentSubBufferIndex]) break; + } + + ma_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining == 0) break; + + ma_uint32 framesRemainingInOutputBuffer; + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) + { + framesRemainingInOutputBuffer = audioBuffer->sizeInFrames - audioBuffer->frameCursorPos; + } + else + { + ma_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames*currentSubBufferIndex; + framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer); + } + + ma_uint32 framesToRead = totalFramesRemaining; + if (framesToRead > framesRemainingInOutputBuffer) framesToRead = framesRemainingInOutputBuffer; + + memcpy((unsigned char *)framesOut + (framesRead*frameSizeInBytes), audioBuffer->data + (audioBuffer->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); + audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead)%audioBuffer->sizeInFrames; + framesRead += framesToRead; + + // If the end of the buffer is read, mark it as processed + if (framesToRead == framesRemainingInOutputBuffer) + { + audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true; + isSubBufferProcessed[currentSubBufferIndex] = true; + + currentSubBufferIndex = (currentSubBufferIndex + 1)%2; + + // Break from this loop if looping not enabled + if (!audioBuffer->looping) + { + StopAudioBufferInLockedState(audioBuffer); + break; + } + } + } + + // Zero-fill excess + ma_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining > 0) + { + memset((unsigned char *)framesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); + + // For static buffers, fill the remaining frames with silence for safety, but don't report those + // frames as "read"; The reason for this is that the caller uses the return value + // to know whether a non-looping sound has finished playback + if (audioBuffer->usage != AUDIO_BUFFER_USAGE_STATIC) framesRead += totalFramesRemaining; + } + + return framesRead; +} + +// Reads audio data from an AudioBuffer object in device format, returned data will be in a format appropriate for mixing +static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount) +{ + // NOTE: Continuously converting data from the AudioBuffer's internal format to the mixing format, + // which should be defined by the output format of the data converter + // This is done until frameCount frames have been output + ma_uint32 bpf = ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn); + ma_uint8 inputBuffer[4096] = { 0 }; + ma_uint32 inputBufferFrameCap = sizeof(inputBuffer)/bpf; + + ma_uint32 totalOutputFramesProcessed = 0; + while (totalOutputFramesProcessed < frameCount) + { + float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.channelsOut); + ma_uint64 outputFramesToProcessThisIteration = frameCount - totalOutputFramesProcessed; + //ma_uint64 inputFramesToProcessThisIteration = 0; + + // Process any residual input frames from the previous read first + if (audioBuffer->converterResidualCount > 0) + { + ma_uint64 inputFramesProcessedThisIteration = audioBuffer->converterResidualCount; + ma_uint64 outputFramesProcessedThisIteration = outputFramesToProcessThisIteration; + ma_data_converter_process_pcm_frames(&audioBuffer->converter, audioBuffer->converterResidual, &inputFramesProcessedThisIteration, runningFramesOut, &outputFramesProcessedThisIteration); + + // Make sure the data in the cache is consumed, this can be optimized to use a cursor instead of a memmove() + memmove(audioBuffer->converterResidual, audioBuffer->converterResidual + inputFramesProcessedThisIteration*bpf, (size_t)(AUDIO_BUFFER_RESIDUAL_CAPACITY - inputFramesProcessedThisIteration)*bpf); + audioBuffer->converterResidualCount -= (ma_uint32)inputFramesProcessedThisIteration; // Safe cast + + totalOutputFramesProcessed += (ma_uint32)outputFramesProcessedThisIteration; // Safe cast + } + else + { + // Getting here means there are no residual frames from the previous read + // Fresh data can now be pulled from the AudioBuffer and processed + // + // A best guess needs to be used made to determine how many input frames to pull from the buffer + // There are three possible outcomes: 1) exact; 2) underestimated; 3) overestimated + // + // When the guess is exactly correct or underestimated there is nothing special to handle, + // it'll be handled naturally by the loop + // + // When the guess is overestimated, that's when it gets more complicated + // In this case, any overflow needs to be stored in a buffer for later processing by the next read + ma_uint32 estimatedInputFrameCount = (ma_uint32)(((float)audioBuffer->converter.resampler.sampleRateIn / audioBuffer->converter.resampler.sampleRateOut)*outputFramesToProcessThisIteration); + if (estimatedInputFrameCount == 0) estimatedInputFrameCount = 1; // Make sure at least one input frame is read + if (estimatedInputFrameCount > inputBufferFrameCap) estimatedInputFrameCount = inputBufferFrameCap; + + ma_uint32 inputFramesInInternalFormatCount = ReadAudioBufferFramesInInternalFormat(audioBuffer, inputBuffer, estimatedInputFrameCount); + + ma_uint64 inputFramesProcessedThisIteration = inputFramesInInternalFormatCount; + ma_uint64 outputFramesProcessedThisIteration = outputFramesToProcessThisIteration; + ma_data_converter_process_pcm_frames(&audioBuffer->converter, inputBuffer, &inputFramesProcessedThisIteration, runningFramesOut, &outputFramesProcessedThisIteration); + + totalOutputFramesProcessed += (ma_uint32)outputFramesProcessedThisIteration; + + if (inputFramesInInternalFormatCount > inputFramesProcessedThisIteration) + { + // Getting here means the estimated input frame count was overestimated + // The residual needs be stored for later use + ma_uint64 residualFrameCount = inputFramesInInternalFormatCount - inputFramesProcessedThisIteration; + + // A safety check to make sure the capacity of the residual cache is not exceeded + if (residualFrameCount > AUDIO_BUFFER_RESIDUAL_CAPACITY) + { + residualFrameCount = AUDIO_BUFFER_RESIDUAL_CAPACITY; + } + + memcpy(audioBuffer->converterResidual, inputBuffer + inputFramesProcessedThisIteration*bpf, (size_t)(residualFrameCount*bpf)); + audioBuffer->converterResidualCount = (unsigned int)residualFrameCount; + } + + if (inputFramesInInternalFormatCount < estimatedInputFrameCount) break; // Reached the end of the sound + } + } + + return totalOutputFramesProcessed; +} + +// Sending audio data to device callback function +// This function will be called when miniaudio needs more data +// NOTE: All the mixing takes place here +static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount) +{ + (void)pDevice; + + // Mixing is basically an accumulation, need to initialize the output buffer to 0 + memset(pFramesOut, 0, frameCount*pDevice->playback.channels*ma_get_bytes_per_sample(pDevice->playback.format)); + + // Using a mutex here for thread-safety which makes things not real-time + // This is unlikely to be necessary for this project, but it can be reconsidered + ma_mutex_lock(&AUDIO.System.lock); + { + for (AudioBuffer *audioBuffer = AUDIO.Buffer.first; audioBuffer != NULL; audioBuffer = audioBuffer->next) + { + // Ignore stopped or paused sounds + if (!audioBuffer->playing || audioBuffer->paused) continue; + + ma_uint32 framesRead = 0; + + while (1) + { + if (framesRead >= frameCount) break; + + // Read as much data as possible from the stream + ma_uint32 framesToRead = (frameCount - framesRead); + + while (framesToRead > 0) + { + float tempBuffer[1024] = { 0 }; // Frames for stereo + + ma_uint32 framesToReadRightNow = framesToRead; + if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS) + { + framesToReadRightNow = sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS; + } + + ma_uint32 framesJustRead = ReadAudioBufferFramesInMixingFormat(audioBuffer, tempBuffer, framesToReadRightNow); + if (framesJustRead > 0) + { + float *framesOut = (float *)pFramesOut + (framesRead*AUDIO.System.device.playback.channels); + float *framesIn = tempBuffer; + + // Apply processors chain if defined + rAudioProcessor *processor = audioBuffer->processor; + while (processor) + { + processor->process(framesIn, framesJustRead); + processor = processor->next; + } + + MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer); + + framesToRead -= framesJustRead; + framesRead += framesJustRead; + } + + if (!audioBuffer->playing) + { + framesRead = frameCount; + break; + } + + // If all the frames requested can't be read, break + if (framesJustRead < framesToReadRightNow) + { + if (!audioBuffer->looping) + { + StopAudioBufferInLockedState(audioBuffer); + break; + } + else + { + // Should never get here, but for safety, + // move the cursor position back to the start and continue the loop + audioBuffer->frameCursorPos = 0; + continue; + } + } + } + + // If for some reason is not possible to read every frame, the loop needs to be broken + // Not doing this could theoretically eend up into an infinite loop + if (framesToRead > 0) break; + } + } + } + + rAudioProcessor *processor = AUDIO.mixedProcessor; + while (processor) + { + processor->process(pFramesOut, frameCount); + processor = processor->next; + } + + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Main mixing function, pretty simple in this project, only an accumulation +// NOTE: framesOut is both an input and an output, it is initially filled with zeros outside of this function +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer) +{ + const float localVolume = buffer->volume; + const ma_uint32 channels = AUDIO.System.device.playback.channels; + + if (channels == 2) // Consider panning + { + const float right = (buffer->pan + 1.0f)/2.0f; // Normalize: [-1..1] -> [0..1] + const float left = 1.0f - right; + + // Fast sine approximation in [0..1] for pan law: y = 0.5f*x*(3 - x*x); + const float levels[2] = { localVolume*0.5f*left*(3.0f - left*left), localVolume*0.5f*right*(3.0f - right*right) }; + + float *frameOut = framesOut; + const float *frameIn = framesIn; + + for (ma_uint32 frame = 0; frame < frameCount; frame++) + { + frameOut[0] += (frameIn[0]*levels[0]); + frameOut[1] += (frameIn[1]*levels[1]); + + frameOut += 2; + frameIn += 2; + } + } + else // Do not consider panning + { + for (ma_uint32 frame = 0; frame < frameCount; frame++) + { + for (ma_uint32 c = 0; c < channels; c++) + { + float *frameOut = framesOut + (frame*channels); + const float *frameIn = framesIn + (frame*channels); + + // Output accumulates input multiplied by volume to provided output (usually 0) + frameOut[c] += (frameIn[c]*localVolume); + } + } + } +} + +// Check if an audio buffer is playing, assuming the audio system mutex has been locked +static bool IsAudioBufferPlayingInLockedState(AudioBuffer *buffer) +{ + bool result = false; + + if (buffer != NULL) result = (buffer->playing && !buffer->paused); + + return result; +} + +// Stop an audio buffer, assuming the audio system mutex has been locked +static void StopAudioBufferInLockedState(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + if (IsAudioBufferPlayingInLockedState(buffer)) + { + buffer->playing = false; + buffer->paused = false; + buffer->frameCursorPos = 0; + buffer->framesProcessed = 0; + buffer->isSubBufferProcessed[0] = true; + buffer->isSubBufferProcessed[1] = true; + } + } +} + +// Update audio stream, assuming the audio system mutex has been locked +static void UpdateAudioStreamInLockedState(AudioStream stream, const void *data, int frameCount) +{ + if (stream.buffer != NULL) + { + if (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1]) + { + ma_uint32 subBufferToUpdate = 0; + + if (stream.buffer->isSubBufferProcessed[0] && stream.buffer->isSubBufferProcessed[1]) + { + // Both buffers are available for updating + // Update the first one and make sure the cursor is moved back to the front + subBufferToUpdate = 0; + stream.buffer->frameCursorPos = 0; + } + else + { + // Update whichever sub-buffer is processed + subBufferToUpdate = (stream.buffer->isSubBufferProcessed[0])? 0 : 1; + } + + ma_uint32 subBufferSizeInFrames = stream.buffer->sizeInFrames/2; + unsigned char *subBuffer = stream.buffer->data + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); + + stream.buffer->framesProcessed += frameCount; + + // Does this API expect a whole buffer to be updated in one go? + // Assuming so, but if not will need to change this logic + if (subBufferSizeInFrames >= (ma_uint32)frameCount) + { + ma_uint32 framesToWrite = (ma_uint32)frameCount; + + ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); + memcpy(subBuffer, data, bytesToWrite); + + // Any leftover frames should be filled with zeros + ma_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; + + if (leftoverFrameCount > 0) memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8)); + + stream.buffer->isSubBufferProcessed[subBufferToUpdate] = false; + } + else TRACELOG(LOG_WARNING, "STREAM: Attempting to write too many frames to buffer"); + } + else TRACELOG(LOG_WARNING, "STREAM: Buffer not available for updating"); + } +} + +// Some required functions for audio standalone module version +#if defined(RAUDIO_STANDALONE) +// Check file extension +static bool IsFileExtension(const char *fileName, const char *ext) +{ + bool result = false; + const char *fileExt; + + if ((fileExt = strrchr(fileName, '.')) != NULL) + { + if (strcmp(fileExt, ext) == 0) result = true; + } + + return result; +} + +// Get pointer to extension for a filename string (includes the dot: .png) +static const char *GetFileExtension(const char *fileName) +{ + const char *dot = strrchr(fileName, '.'); + + if (!dot || dot == fileName) return NULL; + + return dot; +} + +// String pointer reverse break: returns right-most occurrence of charset in text +static const char *strprbrk(const char *text, const char *charset) +{ + const char *latestMatch = NULL; + + for (; (text != NULL) && (text = strpbrk(text, charset)); latestMatch = text++) { } + + return latestMatch; +} + +// Get pointer to filename for a path string +static const char *GetFileName(const char *filePath) +{ + const char *fileName = NULL; + if (filePath != NULL) fileName = strprbrk(filePath, "\\/"); + + if (!fileName) return filePath; + + return fileName + 1; +} + +// Get filename string without extension (uses static string) +static const char *GetFileNameWithoutExt(const char *filePath) +{ + #define MAX_FILENAMEWITHOUTEXT_LENGTH 256 + + static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH] = { 0 }; + memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH); + + if (filePath != NULL) snprintf(fileName, MAX_FILENAMEWITHOUTEXT_LENGTH, "%s", GetFileName(filePath)); + + int fileNameLength = (int)strlen(fileName); // Get size in bytes + + for (int i = 0; (i < fileNameLength) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++) + { + if (fileName[i] == '.') + { + // NOTE: Break on first '.' found + fileName[i] = '\0'; + break; + } + } + + return fileName; +} + +// Load data from file into a buffer +static unsigned char *LoadFileData(const char *fileName, int *dataSize) +{ + unsigned char *data = NULL; + *dataSize = 0; + + if (fileName != NULL) + { + FILE *file = fopen(fileName, "rb"); + + if (file != NULL) + { + // WARNING: On binary streams SEEK_END could not be found, + // using fseek() and ftell() could not work in some (rare) cases + fseek(file, 0, SEEK_END); + int size = ftell(file); + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char)); + + // NOTE: fread() returns number of read elements instead of bytes, so reading [1 byte, size elements] + unsigned int count = (unsigned int)fread(data, sizeof(unsigned char), size, file); + *dataSize = count; + + if (count != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return data; +} + +// Unload file data allocated by LoadFileData() +static void UnloadFileData(unsigned char *data) +{ + RL_FREE(data); +} + +// Save data to file from buffer +static bool SaveFileData(const char *fileName, void *data, int dataSize) +{ + bool result = true; + + if (fileName != NULL) + { + FILE *file = fopen(fileName, "wb"); + + if (file != NULL) + { + unsigned int count = (unsigned int)fwrite(data, sizeof(unsigned char), dataSize, file); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName); + else if (count != dataSize) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName); + + fclose(file); + } + else + { + TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); + result = false; + } + } + else + { + TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + result = false; + } + + return result; +} + +// Save text data to file (write), string must be '\0' terminated +static bool SaveFileText(const char *fileName, char *text) +{ + bool result = true; + + if (fileName != NULL) + { + FILE *file = fopen(fileName, "wt"); + + if (file != NULL) + { + int count = 0; + if (text != NULL) count = fprintf(file, "%s", text); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); + + fclose(file); + } + else + { + TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); + result = false; + } + } + else + { + TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + result = false; + } + + return result; +} +#endif + +#undef AudioBuffer + +#endif // SUPPORT_MODULE_RAUDIO diff --git a/src/libraries/raylib/raylib/src/raylib.dll.rc b/src/libraries/raylib/raylib/src/raylib.dll.rc new file mode 100644 index 0000000..e8d24a0 --- /dev/null +++ b/src/libraries/raylib/raylib/src/raylib.dll.rc @@ -0,0 +1,27 @@ +GLFW_ICON ICON "raylib.ico" + +1 VERSIONINFO +FILEVERSION 6,0,0,0 +PRODUCTVERSION 6,0,0,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + //BLOCK "080904E4" // English UK + BLOCK "040904E4" // English US + BEGIN + VALUE "CompanyName", "raylib technologies" + VALUE "FileDescription", "raylib dynamic library (www.raylib.com)" + VALUE "FileVersion", "6.0.0" + VALUE "InternalName", "raylib.dll" + VALUE "LegalCopyright", "(c) 2026 Ramon Santamaria (@raysan5)" + VALUE "OriginalFilename", "raylib.dll" + VALUE "ProductName", "raylib" + VALUE "ProductVersion", "6.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + //VALUE "Translation", 0x809, 1252 // English UK + VALUE "Translation", 0x409, 1252 // English US + END +END diff --git a/src/libraries/raylib/raylib/src/raylib.dll.rc.data b/src/libraries/raylib/raylib/src/raylib.dll.rc.data new file mode 100644 index 0000000..4a8a6a1 Binary files /dev/null and b/src/libraries/raylib/raylib/src/raylib.dll.rc.data differ diff --git a/src/libraries/raylib/raylib/src/raylib.h b/src/libraries/raylib/raylib/src/raylib.h new file mode 100644 index 0000000..8f88e3d --- /dev/null +++ b/src/libraries/raylib/raylib/src/raylib.h @@ -0,0 +1,1764 @@ +/********************************************************************************************** +* +* raylib v6.1-dev - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) +* +* FEATURES: +* - NO external dependencies, all required libraries included with raylib +* - Multiplatform: Windows, Linux, macOS, FreeBSD, Web, Android, Raspberry Pi, DRM native... +* - Written in plain C code (C99) in PascalCase/camelCase notation +* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile) +* - Software renderer optional, for systems with no GPU: [rlsw] +* - Custom OpenGL abstraction layer (usable as standalone module): [rlgl] +* - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts) +* - Many texture formats supported, including compressed formats (DXT, ETC, ASTC) +* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! +* - Flexible Materials system, supporting classic maps and PBR maps +* - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF) +* - Shaders support, including Model shaders and Postprocessing shaders +* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] +* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, QOA, XM, MOD) +* - VR stereo rendering with configurable HMD device parameters +* - Bindings to multiple programming languages available! +* +* NOTES: +* - One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] +* - One default Texture2D is loaded on rlglInit(), 1x1 white pixel R8G8B8A8 [rlgl] (OpenGL 3.3 or ES2) +* - One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) +* - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) +* +* DEPENDENCIES: +* [rcore] Depends on the selected platform backend, check rcore.c header for details +* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL extensions loading +* [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management +* +* OPTIONAL DEPENDENCIES (included): +* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm +* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm +* [rcore] rprand (Ramon Santamaria) for pseudo-random numbers generation +* [rtextures] qoi (Dominic Szablewski - https://phoboslab.org) for QOI image management +* [rtextures] stb_image (Sean Barrett) for images loading (BMP, TGA, PNG, JPEG, HDR...) +* [rtextures] stb_image_write (Sean Barrett) for image writing (BMP, TGA, PNG, JPG) +* [rtextures] stb_image_resize2 (Sean Barrett) for image resizing algorithms +* [rtextures] stb_perlin (Sean Barrett) for Perlin Noise image generation +* [rtextures] rltexgpu (Ramon Santamaria) for GPU-compressed texture formats +* [rtext] stb_truetype (Sean Barrett) for ttf fonts loading +* [rtext] stb_rect_pack (Sean Barrett) for rectangles packing +* [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation +* [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) +* [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF) +* [rmodels] m3d (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) +* [rmodels] vox_loader (Johann Nadalutti) for models loading (VOX) +* [raudio] dr_wav (David Reid) for WAV audio file loading +* [raudio] dr_flac (David Reid) for FLAC audio file loading +* [raudio] dr_mp3 (David Reid) for MP3 audio file loading +* [raudio] stb_vorbis (Sean Barrett) for OGG audio loading +* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading +* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading +* [raudio] qoa (Dominic Szablewski - https://phoboslab.org) for QOA audio management +* +* +* LICENSE: zlib/libpng +* +* raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software: +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYLIB_H +#define RAYLIB_H + +#include // Required for: va_list - Only used by TraceLogCallback + +#define RAYLIB_VERSION_MAJOR 6 +#define RAYLIB_VERSION_MINOR 1 +#define RAYLIB_VERSION_PATCH 0 +#define RAYLIB_VERSION "6.1-dev" + +// Function specifiers in case library is build/used as a shared library +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +// NOTE: visibility("default") attribute makes symbols "visible" when compiled with -fvisibility=hidden +#if defined(_WIN32) + #if defined(__TINYC__) + #define __declspec(x) __attribute__((x)) + #endif + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // Building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // Using the library as a Win32 shared library (.dll) + #endif +#else + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __attribute__((visibility("default"))) // Building as a Unix shared library (.so/.dylib) + #endif +#endif + +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Allow custom memory allocators +// NOTE: Require recompiling raylib sources +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) +#endif + +// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) +// Plain structures in C++ (without constructors) can be initialized with { } +// This is called aggregate initialization (C++11 feature) +#if defined(__cplusplus) + #define CLITERAL(type) type +#else + #define CLITERAL(type) (type) +#endif + +// Some compilers (mostly macos clang) default to C++98, +// where aggregate initialization can't be used +// So, give a more clear error stating how to fix this +#if !defined(_MSC_VER) && (defined(__cplusplus) && __cplusplus < 201103L) + #error "C++11 or later is required. Add -std=c++11" +#endif + +// NOTE: Set some defines with some data types declared by raylib +// Other modules (raymath, rlgl) also require some of those types, so, +// to be able to use those other modules as standalone (not depending on raylib) +// this defines are useful for internal check and avoid type (re)definitions +#define RL_COLOR_TYPE +#define RL_RECTANGLE_TYPE +#define RL_VECTOR2_TYPE +#define RL_VECTOR3_TYPE +#define RL_VECTOR4_TYPE +#define RL_QUATERNION_TYPE +#define RL_MATRIX_TYPE + +// Some Basic Colors +// NOTE: Custom raylib color palette for amazing visuals on WHITE background +#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray +#define GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray +#define DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray +#define YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow +#define GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold +#define ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange +#define PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink +#define RED CLITERAL(Color){ 230, 41, 55, 255 } // Red +#define MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon +#define GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green +#define LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime +#define DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green +#define SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue +#define BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue +#define DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue +#define PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple +#define VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet +#define DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple +#define BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige +#define BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown +#define DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown + +#define WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White +#define BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black +#define BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) +#define MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta +#define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Boolean type +#if !defined(__cplusplus) +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) + #include +#elif !defined(bool) + typedef enum bool { false = 0, true = !false } bool; + #define RL_BOOL_TYPE +#endif +#endif + +// Vector2, 2 components +typedef struct Vector2 { + float x; // Vector x component + float y; // Vector y component +} Vector2; + +// Vector3, 3 components +typedef struct Vector3 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component +} Vector3; + +// Vector4, 4 components +typedef struct Vector4 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component + float w; // Vector w component +} Vector4; + +// Quaternion, 4 components (Vector4 alias) +typedef Vector4 Quaternion; + +// Matrix, 4x4 components, column major, OpenGL style, right-handed +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; + +// Color, 4 components, R8G8B8A8 (32bit) +typedef struct Color { + unsigned char r; // Color red value + unsigned char g; // Color green value + unsigned char b; // Color blue value + unsigned char a; // Color alpha value +} Color; + +// Rectangle, 4 components +typedef struct Rectangle { + float x; // Rectangle top-left corner position x + float y; // Rectangle top-left corner position y + float width; // Rectangle width + float height; // Rectangle height +} Rectangle; + +// Image, pixel data stored in CPU memory (RAM) +typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Image; + +// Texture, tex data stored in GPU memory (VRAM) +typedef struct Texture { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Texture; + +// Texture2D, same as Texture +typedef Texture Texture2D; + +// TextureCubemap, same as Texture +typedef Texture TextureCubemap; + +// RenderTexture, fbo for texture rendering +typedef struct RenderTexture { + unsigned int id; // OpenGL framebuffer object id + Texture texture; // Color buffer attachment texture + Texture depth; // Depth buffer attachment texture +} RenderTexture; + +// RenderTexture2D, same as RenderTexture +typedef RenderTexture RenderTexture2D; + +// NPatchInfo, n-patch layout info +typedef struct NPatchInfo { + Rectangle source; // Texture source rectangle + int left; // Left border offset + int top; // Top border offset + int right; // Right border offset + int bottom; // Bottom border offset + int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 +} NPatchInfo; + +// GlyphInfo, font characters glyphs info +typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data +} GlyphInfo; + +// Font, font texture and GlyphInfo array data +typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of glyph characters + int glyphPadding; // Padding around the glyph characters + Texture2D texture; // Texture atlas containing the glyphs + Rectangle *recs; // Rectangles in texture for the glyphs + GlyphInfo *glyphs; // Glyphs info data +} Font; + +// Camera, defines position/orientation in 3d space +typedef struct Camera3D { + Vector3 position; // Camera position + Vector3 target; // Camera target it looks-at + Vector3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view aperture in Y (degrees) in perspective, used as near plane height in world units in orthographic + int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC +} Camera3D; + +typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + +// Camera2D, defines position/orientation in 2d space +typedef struct Camera2D { + Vector2 offset; // Camera offset (screen space offset from window origin) + Vector2 target; // Camera target (world space target point that is mapped to screen space offset) + float rotation; // Camera rotation in degrees (pivots around target) + float zoom; // Camera zoom (scaling around target), must not be set to 0, set to 1.0f for no scale +} Camera2D; + +// Mesh, vertex data and vao/vbo +typedef struct Mesh { + int vertexCount; // Number of vertices stored in arrays + int triangleCount; // Number of triangles stored (indexed or not) + + // Vertex attributes data + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *texcoords2; // Vertex texture second coordinates (UV - 2 components per vertex) (shader-location = 5) + float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) + float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) + + // Skin data for animation + int boneCount; // Number of bones (MAX: 256 bones) + unsigned char *boneIndices; // Vertex bone indices, up to 4 bones influence by vertex (skinning) (shader-location = 6) + float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7) + + // Runtime animation vertex data (CPU skinning) + // NOTE: In case of GPU skinning, not used, pointers are NULL + float *animVertices; // Animated vertex positions (after bones transformations) + float *animNormals; // Animated normals (after bones transformations) + + // OpenGL identifiers + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) +} Mesh; + +// Shader +typedef struct Shader { + unsigned int id; // Shader program id + int *locs; // Shader locations array (RL_MAX_SHADER_LOCATIONS) +} Shader; + +// MaterialMap +typedef struct MaterialMap { + Texture2D texture; // Material map texture + Color color; // Material map color + float value; // Material map value +} MaterialMap; + +// Material, includes shader and maps +typedef struct Material { + Shader shader; // Material shader + MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) + float params[4]; // Material generic parameters (if required) +} Material; + +// Transform, vertex transformation data +typedef struct Transform { + Vector3 translation; // Translation + Quaternion rotation; // Rotation + Vector3 scale; // Scale +} Transform; + +// Anim pose, an array of Transform[] +typedef Transform *ModelAnimPose; + +// Bone, skeletal animation bone +typedef struct BoneInfo { + char name[32]; // Bone name + int parent; // Bone parent +} BoneInfo; + +// Skeleton, animation bones hierarchy +typedef struct ModelSkeleton { + unsigned int boneCount; // Number of bones + BoneInfo *bones; // Bones information (skeleton) + ModelAnimPose bindPose; // Bones base transformation (Transform[]) +} ModelSkeleton; + +// Model, meshes, materials and animation data +typedef struct Model { + Matrix transform; // Local transform matrix + + int meshCount; // Number of meshes + int materialCount; // Number of materials + Mesh *meshes; // Meshes array + Material *materials; // Materials array + int *meshMaterial; // Mesh material number + + // Animation data + ModelSkeleton skeleton; // Skeleton for animation + + // Runtime animation data (CPU/GPU skinning) + ModelAnimPose currentPose; // Current animation pose (Transform[]) + Matrix *boneMatrices; // Bones animated transformation matrices +} Model; + +// ModelAnimation, contains a full animation sequence +typedef struct ModelAnimation { + char name[32]; // Animation name + + unsigned int boneCount; // Number of bones (per pose) + int keyframeCount; // Number of animation key frames + ModelAnimPose *keyframePoses; // Animation sequence keyframe poses [keyframe][pose] +} ModelAnimation; + +// Ray, ray for raycasting +typedef struct Ray { + Vector3 position; // Ray position (origin) + Vector3 direction; // Ray direction (normalized) +} Ray; + +// RayCollision, ray hit information +typedef struct RayCollision { + bool hit; // Did the ray hit something? + float distance; // Distance to the nearest hit + Vector3 point; // Point of the nearest hit + Vector3 normal; // Surface normal of hit +} RayCollision; + +// BoundingBox +typedef struct BoundingBox { + Vector3 min; // Minimum vertex box-corner + Vector3 max; // Maximum vertex box-corner +} BoundingBox; + +// Wave, audio wave data +typedef struct Wave { + unsigned int frameCount; // Total number of frames (considering channels) + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) + void *data; // Buffer data pointer +} Wave; + +// Opaque structs declaration +// NOTE: Actual structs are defined internally in raudio module +typedef struct rAudioBuffer rAudioBuffer; +typedef struct rAudioProcessor rAudioProcessor; + +// AudioStream, custom audio stream +typedef struct AudioStream { + rAudioBuffer *buffer; // Pointer to internal data used by the audio system + rAudioProcessor *processor; // Pointer to internal data processor, useful for audio effects + + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) +} AudioStream; + +// Sound +typedef struct Sound { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) +} Sound; + +// Music, audio stream, anything longer than ~10 seconds should be streamed +typedef struct Music { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) + bool looping; // Music looping enable + + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type +} Music; + +// VrDeviceInfo, Head-Mounted-Display device parameters +typedef struct VrDeviceInfo { + int hResolution; // Horizontal resolution in pixels + int vResolution; // Vertical resolution in pixels + float hScreenSize; // Horizontal size in meters + float vScreenSize; // Vertical size in meters + float eyeToScreenDistance; // Distance between eye and display in meters + float lensSeparationDistance; // Lens separation distance in meters + float interpupillaryDistance; // IPD (distance between pupils) in meters + float lensDistortionValues[4]; // Lens distortion constant parameters + float chromaAbCorrection[4]; // Chromatic aberration correction parameters +} VrDeviceInfo; + +// VrStereoConfig, VR stereo rendering configuration for simulator +typedef struct VrStereoConfig { + Matrix projection[2]; // VR projection matrices (per eye) + Matrix viewOffset[2]; // VR view offset matrices (per eye) + float leftLensCenter[2]; // VR left lens center + float rightLensCenter[2]; // VR right lens center + float leftScreenCenter[2]; // VR left screen center + float rightScreenCenter[2]; // VR right screen center + float scale[2]; // VR distortion scale + float scaleIn[2]; // VR distortion scale in +} VrStereoConfig; + +// File path list +typedef struct FilePathList { + unsigned int count; // Filepaths entries count + char **paths; // Filepaths entries +} FilePathList; + +// Automation event +typedef struct AutomationEvent { + unsigned int frame; // Event frame + unsigned int type; // Event type (AutomationEventType) + int params[4]; // Event parameters (if required) +} AutomationEvent; + +// Automation event list +typedef struct AutomationEventList { + unsigned int capacity; // Events max entries (MAX_AUTOMATION_EVENTS) + unsigned int count; // Events entries count + AutomationEvent *events; // Events entries +} AutomationEventList; + +//---------------------------------------------------------------------------------- +// Enumerators Definition +//---------------------------------------------------------------------------------- +// System/Window config flags +// NOTE: Every bit registers one state (use it with bit masks) +// By default all flags are set to 0 +typedef enum { + FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU + FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen + FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window + FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) + FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window + FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) + FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) + FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused + FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top + FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized + FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer + FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI + FLAG_WINDOW_MOUSE_PASSTHROUGH = 0x00004000, // Set to support mouse passthrough, only supported when FLAG_WINDOW_UNDECORATED + FLAG_BORDERLESS_WINDOWED_MODE = 0x00008000, // Set to run program in borderless windowed mode + FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X + FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) +} ConfigFlags; + +// Trace log level +// NOTE: Organized by priority level +typedef enum { + LOG_ALL = 0, // Display all logs + LOG_TRACE, // Trace logging, intended for internal use only + LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + LOG_INFO, // Info logging, used for program execution info + LOG_WARNING, // Warning logging, used on recoverable failures + LOG_ERROR, // Error logging, used on unrecoverable failures + LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + LOG_NONE // Disable logging +} TraceLogLevel; + +// Keyboard keys (US keyboard layout) +// NOTE: Use GetKeyPressed() to allow redefining required keys for alternative layouts +typedef enum { + KEY_NULL = 0, // Key: NULL, used for no key pressed + // Alphanumeric keys + KEY_APOSTROPHE = 39, // Key: ' + KEY_COMMA = 44, // Key: , + KEY_MINUS = 45, // Key: - + KEY_PERIOD = 46, // Key: . + KEY_SLASH = 47, // Key: / + KEY_ZERO = 48, // Key: 0 + KEY_ONE = 49, // Key: 1 + KEY_TWO = 50, // Key: 2 + KEY_THREE = 51, // Key: 3 + KEY_FOUR = 52, // Key: 4 + KEY_FIVE = 53, // Key: 5 + KEY_SIX = 54, // Key: 6 + KEY_SEVEN = 55, // Key: 7 + KEY_EIGHT = 56, // Key: 8 + KEY_NINE = 57, // Key: 9 + KEY_SEMICOLON = 59, // Key: ; + KEY_EQUAL = 61, // Key: = + KEY_A = 65, // Key: A | a + KEY_B = 66, // Key: B | b + KEY_C = 67, // Key: C | c + KEY_D = 68, // Key: D | d + KEY_E = 69, // Key: E | e + KEY_F = 70, // Key: F | f + KEY_G = 71, // Key: G | g + KEY_H = 72, // Key: H | h + KEY_I = 73, // Key: I | i + KEY_J = 74, // Key: J | j + KEY_K = 75, // Key: K | k + KEY_L = 76, // Key: L | l + KEY_M = 77, // Key: M | m + KEY_N = 78, // Key: N | n + KEY_O = 79, // Key: O | o + KEY_P = 80, // Key: P | p + KEY_Q = 81, // Key: Q | q + KEY_R = 82, // Key: R | r + KEY_S = 83, // Key: S | s + KEY_T = 84, // Key: T | t + KEY_U = 85, // Key: U | u + KEY_V = 86, // Key: V | v + KEY_W = 87, // Key: W | w + KEY_X = 88, // Key: X | x + KEY_Y = 89, // Key: Y | y + KEY_Z = 90, // Key: Z | z + KEY_LEFT_BRACKET = 91, // Key: [ + KEY_BACKSLASH = 92, // Key: '\' + KEY_RIGHT_BRACKET = 93, // Key: ] + KEY_GRAVE = 96, // Key: ` + // Function keys + KEY_SPACE = 32, // Key: Space + KEY_ESCAPE = 256, // Key: Esc + KEY_ENTER = 257, // Key: Enter + KEY_TAB = 258, // Key: Tab + KEY_BACKSPACE = 259, // Key: Backspace + KEY_INSERT = 260, // Key: Ins + KEY_DELETE = 261, // Key: Del + KEY_RIGHT = 262, // Key: Cursor right + KEY_LEFT = 263, // Key: Cursor left + KEY_DOWN = 264, // Key: Cursor down + KEY_UP = 265, // Key: Cursor up + KEY_PAGE_UP = 266, // Key: Page up + KEY_PAGE_DOWN = 267, // Key: Page down + KEY_HOME = 268, // Key: Home + KEY_END = 269, // Key: End + KEY_CAPS_LOCK = 280, // Key: Caps lock + KEY_SCROLL_LOCK = 281, // Key: Scroll down + KEY_NUM_LOCK = 282, // Key: Num lock + KEY_PRINT_SCREEN = 283, // Key: Print screen + KEY_PAUSE = 284, // Key: Pause + KEY_F1 = 290, // Key: F1 + KEY_F2 = 291, // Key: F2 + KEY_F3 = 292, // Key: F3 + KEY_F4 = 293, // Key: F4 + KEY_F5 = 294, // Key: F5 + KEY_F6 = 295, // Key: F6 + KEY_F7 = 296, // Key: F7 + KEY_F8 = 297, // Key: F8 + KEY_F9 = 298, // Key: F9 + KEY_F10 = 299, // Key: F10 + KEY_F11 = 300, // Key: F11 + KEY_F12 = 301, // Key: F12 + KEY_LEFT_SHIFT = 340, // Key: Shift left + KEY_LEFT_CONTROL = 341, // Key: Control left + KEY_LEFT_ALT = 342, // Key: Alt left + KEY_LEFT_SUPER = 343, // Key: Super left + KEY_RIGHT_SHIFT = 344, // Key: Shift right + KEY_RIGHT_CONTROL = 345, // Key: Control right + KEY_RIGHT_ALT = 346, // Key: Alt right + KEY_RIGHT_SUPER = 347, // Key: Super right + KEY_KB_MENU = 348, // Key: KB menu + // Keypad keys + KEY_KP_0 = 320, // Key: Keypad 0 + KEY_KP_1 = 321, // Key: Keypad 1 + KEY_KP_2 = 322, // Key: Keypad 2 + KEY_KP_3 = 323, // Key: Keypad 3 + KEY_KP_4 = 324, // Key: Keypad 4 + KEY_KP_5 = 325, // Key: Keypad 5 + KEY_KP_6 = 326, // Key: Keypad 6 + KEY_KP_7 = 327, // Key: Keypad 7 + KEY_KP_8 = 328, // Key: Keypad 8 + KEY_KP_9 = 329, // Key: Keypad 9 + KEY_KP_DECIMAL = 330, // Key: Keypad . + KEY_KP_DIVIDE = 331, // Key: Keypad / + KEY_KP_MULTIPLY = 332, // Key: Keypad * + KEY_KP_SUBTRACT = 333, // Key: Keypad - + KEY_KP_ADD = 334, // Key: Keypad + + KEY_KP_ENTER = 335, // Key: Keypad Enter + KEY_KP_EQUAL = 336, // Key: Keypad = + // Android key buttons + KEY_BACK = 4, // Key: Android back button + KEY_MENU = 5, // Key: Android menu button + KEY_VOLUME_UP = 24, // Key: Android volume up button + KEY_VOLUME_DOWN = 25 // Key: Android volume down button +} KeyboardKey; + +// Add backwards compatibility support for deprecated names +#define MOUSE_LEFT_BUTTON MOUSE_BUTTON_LEFT +#define MOUSE_RIGHT_BUTTON MOUSE_BUTTON_RIGHT +#define MOUSE_MIDDLE_BUTTON MOUSE_BUTTON_MIDDLE + +// Mouse buttons +typedef enum { + MOUSE_BUTTON_LEFT = 0, // Mouse button left + MOUSE_BUTTON_RIGHT = 1, // Mouse button right + MOUSE_BUTTON_MIDDLE = 2, // Mouse button middle (pressed wheel) + MOUSE_BUTTON_SIDE = 3, // Mouse button side (advanced mouse device) + MOUSE_BUTTON_EXTRA = 4, // Mouse button extra (advanced mouse device) + MOUSE_BUTTON_FORWARD = 5, // Mouse button forward (advanced mouse device) + MOUSE_BUTTON_BACK = 6, // Mouse button back (advanced mouse device) +} MouseButton; + +// Mouse cursor +typedef enum { + MOUSE_CURSOR_DEFAULT = 0, // Default pointer shape + MOUSE_CURSOR_ARROW = 1, // Arrow shape + MOUSE_CURSOR_IBEAM = 2, // Text writing cursor shape + MOUSE_CURSOR_CROSSHAIR = 3, // Cross shape + MOUSE_CURSOR_POINTING_HAND = 4, // Pointing hand cursor + MOUSE_CURSOR_RESIZE_EW = 5, // Horizontal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NS = 6, // Vertical resize/move arrow shape + MOUSE_CURSOR_RESIZE_NWSE = 7, // Top-left to bottom-right diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_ALL = 9, // The omnidirectional resize/move cursor shape + MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape +} MouseCursor; + +// Gamepad buttons +typedef enum { + GAMEPAD_BUTTON_UNKNOWN = 0, // Unknown button, for error checking + GAMEPAD_BUTTON_LEFT_FACE_UP, // Gamepad left DPAD up button + GAMEPAD_BUTTON_LEFT_FACE_RIGHT, // Gamepad left DPAD right button + GAMEPAD_BUTTON_LEFT_FACE_DOWN, // Gamepad left DPAD down button + GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Gamepad left DPAD left button + GAMEPAD_BUTTON_RIGHT_FACE_UP, // Gamepad right button up (i.e. PS3: Triangle, Xbox: Y) + GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, // Gamepad right button right (i.e. PS3: Circle, Xbox: B) + GAMEPAD_BUTTON_RIGHT_FACE_DOWN, // Gamepad right button down (i.e. PS3: Cross, Xbox: A) + GAMEPAD_BUTTON_RIGHT_FACE_LEFT, // Gamepad right button left (i.e. PS3: Square, Xbox: X) + GAMEPAD_BUTTON_LEFT_TRIGGER_1, // Gamepad top/back trigger left (first), it could be a trailing button + GAMEPAD_BUTTON_LEFT_TRIGGER_2, // Gamepad top/back trigger left (second), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_1, // Gamepad top/back trigger right (first), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_2, // Gamepad top/back trigger right (second), it could be a trailing button + GAMEPAD_BUTTON_MIDDLE_LEFT, // Gamepad center buttons, left one (i.e. PS3: Select) + GAMEPAD_BUTTON_MIDDLE, // Gamepad center buttons, middle one (i.e. PS3: PS, Xbox: XBOX) + GAMEPAD_BUTTON_MIDDLE_RIGHT, // Gamepad center buttons, right one (i.e. PS3: Start) + GAMEPAD_BUTTON_LEFT_THUMB, // Gamepad joystick pressed button left + GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right +} GamepadButton; + +// Gamepad axes +typedef enum { + GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis + GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis + GAMEPAD_AXIS_RIGHT_X = 2, // Gamepad right stick X axis + GAMEPAD_AXIS_RIGHT_Y = 3, // Gamepad right stick Y axis + GAMEPAD_AXIS_LEFT_TRIGGER = 4, // Gamepad back trigger left, pressure level: [1..-1] + GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // Gamepad back trigger right, pressure level: [1..-1] +} GamepadAxis; + +// Material map index +typedef enum { + MATERIAL_MAP_ALBEDO = 0, // Albedo material (same as: MATERIAL_MAP_DIFFUSE) + MATERIAL_MAP_METALNESS, // Metalness material (same as: MATERIAL_MAP_SPECULAR) + MATERIAL_MAP_NORMAL, // Normal material + MATERIAL_MAP_ROUGHNESS, // Roughness material + MATERIAL_MAP_OCCLUSION, // Ambient occlusion material + MATERIAL_MAP_EMISSION, // Emission material + MATERIAL_MAP_HEIGHT, // Heightmap material + MATERIAL_MAP_CUBEMAP, // Cubemap material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_IRRADIANCE, // Irradiance material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_PREFILTER, // Prefilter material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_BRDF // Brdf material +} MaterialMapIndex; + +#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO +#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS + +// Shader location index +// NOTE: Some locations are tried to be set automatically on shader loading, +// but only if default attributes/uniforms names are found, check config.h for names +typedef enum { + SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position + SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 + SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 + SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal + SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent + SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color + SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection + SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) + SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection + SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) + SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal + SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view + SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color + SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color + SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color + SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: SHADER_LOC_MAP_DIFFUSE) + SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: SHADER_LOC_MAP_SPECULAR) + SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal + SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness + SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion + SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission + SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: heightmap + SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap + SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance + SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter + SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf + SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: bone indices + SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: bone weights + SHADER_LOC_MATRIX_BONETRANSFORMS, // Shader location: matrix attribute: bone transforms (animation) + SHADER_LOC_VERTEX_INSTANCETRANSFORM // Shader location: vertex attribute: instance transforms +} ShaderLocationIndex; + +#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO +#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float + SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) + SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) + SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) + SHADER_UNIFORM_INT, // Shader uniform type: int + SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) + SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) + SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int + SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) + SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) + SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) + SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d +} ShaderUniformDataType; + +// Shader attribute data types +typedef enum { + SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float + SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) + SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) + SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) +} ShaderAttributeDataType; + +// Pixel formats +// NOTE: Support depends on OpenGL version and platform +typedef enum { + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) + PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) + PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} PixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + TEXTURE_FILTER_POINT = 0, // No filter, pixel approximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} TextureFilter; + +// Texture parameters: wrap mode +typedef enum { + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode +} TextureWrap; + +// Cubemap layouts +typedef enum { + CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type + CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces + CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by a horizontal line with faces + CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces + CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE // Layout is defined by a 4x3 cross with cubemap faces +} CubemapLayout; + +// Font type, defines generation method +typedef enum { + FONT_DEFAULT = 0, // Default font generation, anti-aliased + FONT_BITMAP, // Bitmap font generation, no anti-aliasing + FONT_SDF // SDF font generation, requires external shader +} FontType; + +// Color blending modes (pre-defined) +typedef enum { + BLEND_ALPHA = 0, // Blend textures considering alpha (default) + BLEND_ADDITIVE, // Blend textures adding colors + BLEND_MULTIPLIED, // Blend textures multiplying colors + BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha + BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) + BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) +} BlendMode; + +// Gesture +// NOTE: Provided as bit-wise flags to enable only desired gestures +typedef enum { + GESTURE_NONE = 0, // No gesture + GESTURE_TAP = 1, // Tap gesture + GESTURE_DOUBLETAP = 2, // Double tap gesture + GESTURE_HOLD = 4, // Hold gesture + GESTURE_DRAG = 8, // Drag gesture + GESTURE_SWIPE_RIGHT = 16, // Swipe right gesture + GESTURE_SWIPE_LEFT = 32, // Swipe left gesture + GESTURE_SWIPE_UP = 64, // Swipe up gesture + GESTURE_SWIPE_DOWN = 128, // Swipe down gesture + GESTURE_PINCH_IN = 256, // Pinch in gesture + GESTURE_PINCH_OUT = 512 // Pinch out gesture +} Gesture; + +// Camera system modes +typedef enum { + CAMERA_CUSTOM = 0, // Camera custom, controlled by user (UpdateCamera() does nothing) + CAMERA_FREE, // Camera free mode + CAMERA_ORBITAL, // Camera orbital, around target, zoom supported + CAMERA_FIRST_PERSON, // Camera first person + CAMERA_THIRD_PERSON // Camera third person +} CameraMode; + +// Camera projection +typedef enum { + CAMERA_PERSPECTIVE = 0, // Perspective projection + CAMERA_ORTHOGRAPHIC // Orthographic projection +} CameraProjection; + +// N-patch layout +typedef enum { + NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles + NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles + NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles +} NPatchLayout; + +// Callbacks to hook some internal functions +// WARNING: These callbacks are intended for advanced users +typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages +typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, int *dataSize); // FileIO: Load binary data +typedef bool (*SaveFileDataCallback)(const char *fileName, const void *data, int dataSize); // FileIO: Save binary data +typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data +typedef bool (*SaveFileTextCallback)(const char *fileName, const char *text); // FileIO: Save text data + +//------------------------------------------------------------------------------------ +// Global Variables Definition +//------------------------------------------------------------------------------------ +// It's lonely here... + +//------------------------------------------------------------------------------------ +// Window and Graphics Device Functions (Module: core) +//------------------------------------------------------------------------------------ + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Window-related functions +RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context +RLAPI void CloseWindow(void); // Close window and unload OpenGL context +RLAPI bool WindowShouldClose(void); // Check if application should close (KEY_ESCAPE pressed or windows close icon clicked) +RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully +RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen +RLAPI bool IsWindowHidden(void); // Check if window is currently hidden +RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized +RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized +RLAPI bool IsWindowFocused(void); // Check if window is currently focused +RLAPI bool IsWindowResized(void); // Check if window has been resized last frame +RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled +RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags +RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution +RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed, resizes window to match monitor resolution +RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable +RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable +RLAPI void RestoreWindow(void); // Restore window from being minimized/maximized +RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit) +RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit) +RLAPI void SetWindowTitle(const char *title); // Set title for window +RLAPI void SetWindowPosition(int x, int y); // Set window position on screen +RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window +RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowMaxSize(int width, int height); // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowSize(int width, int height); // Set window dimensions +RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] +RLAPI void SetWindowFocused(void); // Set window focused +RLAPI void *GetWindowHandle(void); // Get native window handle +RLAPI int GetScreenWidth(void); // Get current screen width +RLAPI int GetScreenHeight(void); // Get current screen height +RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI) +RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI) +RLAPI int GetMonitorCount(void); // Get number of connected monitors +RLAPI int GetCurrentMonitor(void); // Get current monitor where window is placed +RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position +RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (current video mode used by monitor) +RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (current video mode used by monitor) +RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres +RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres +RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate +RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor +RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor +RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor +RLAPI void SetClipboardText(const char *text); // Set clipboard text content +RLAPI const char *GetClipboardText(void); // Get clipboard text content +RLAPI Image GetClipboardImage(void); // Get clipboard image content +RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling +RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling + +// Cursor-related functions +RLAPI void ShowCursor(void); // Show cursor +RLAPI void HideCursor(void); // Hide cursor +RLAPI bool IsCursorHidden(void); // Check if cursor is not visible +RLAPI void EnableCursor(void); // Enable cursor (unlock cursor) +RLAPI void DisableCursor(void); // Disable cursor (lock cursor) +RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the screen + +// Drawing-related functions +RLAPI void ClearBackground(Color color); // Clear background (framebuffer) to color +RLAPI void BeginDrawing(void); // Begin canvas (framebuffer) drawing +RLAPI void EndDrawing(void); // End canvas (framebuffer) drawing and swap buffers (double buffering) +RLAPI void BeginMode2D(Camera2D camera); // Begin 2D mode with custom camera (2D) +RLAPI void EndMode2D(void); // End 2D mode with custom camera +RLAPI void BeginMode3D(Camera3D camera); // Begin 3D mode with custom camera (3D) +RLAPI void EndMode3D(void); // End 3D mode and returns to default 2D orthographic mode +RLAPI void BeginTextureMode(RenderTexture2D target); // Begin drawing to render texture +RLAPI void EndTextureMode(void); // End drawing to render texture +RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing +RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) +RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied, subtract, custom) +RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) +RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) +RLAPI void EndScissorMode(void); // End scissor mode +RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) +RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) + +// VR stereo config functions for VR simulator +RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters +RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config + +// Shader management functions +// NOTE: Shader functionality is not available on OpenGL 1.1 +RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations +RLAPI bool IsShaderValid(Shader shader); // Check if shader is valid (loaded on GPU) +RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location +RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location +RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value +RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector +RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) +RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value and bind the texture (sampler2d) +RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) + +// Screen-space-related functions +RLAPI Ray GetScreenToWorldRay(Vector2 position, Camera camera); // Get a ray trace from screen position (i.e mouse) +RLAPI Ray GetScreenToWorldRayEx(Vector2 position, Camera camera, int width, int height); // Get a ray trace from screen position (i.e mouse) in a viewport +RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Get screen space position for a 3d world space position +RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Get sized screen space position for a 3d world space position +RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Get screen space position for a 2d camera world space position +RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Get world space position for a 2d camera screen space position +RLAPI Matrix GetCameraMatrix(Camera camera); // Get camera transform matrix (view matrix) +RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix + +// Timing-related functions +RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) +RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) +RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() +RLAPI int GetFPS(void); // Get current FPS + +// Custom frame control functions +// NOTE: Those functions are intended for advanced users that want full control over the frame processing +// By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() +// To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL +RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) +RLAPI void PollInputEvents(void); // Register all input events +RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) + +// Random values generation functions +RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator +RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) +RLAPI int *LoadRandomSequence(unsigned int count, int min, int max); // Load random values sequence, no values repeated +RLAPI void UnloadRandomSequence(int *sequence); // Unload random values sequence + +// Misc. functions +RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) +RLAPI void SetConfigFlags(unsigned int flags); // Set up init configuration flags (view FLAGS) +RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) + +// Logging system +RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level +RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) +RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log + +// Memory management, using internal allocators +RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator +RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator +RLAPI void MemFree(void *ptr); // Internal memory free + +// File system management functions +RLAPI unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) +RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() +RLAPI bool SaveFileData(const char *fileName, const void *data, int dataSize); // Save data to file from byte array (write), returns true on success +RLAPI bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName); // Export data to code (.h), returns true on success +RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string +RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() +RLAPI bool SaveFileText(const char *fileName, const char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success + +// File access custom callbacks +// WARNING: Callbacks setup is intended for advanced users +RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader +RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver +RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader +RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver + +RLAPI int FileRename(const char *fileName, const char *fileRename); // Rename file (if exists), returns 0 on success +RLAPI int FileRemove(const char *fileName); // Remove file (if exists), returns 0 on success +RLAPI int FileCopy(const char *srcPath, const char *dstPath); // Copy file from one path to another, dstPath created if it doesn't exist, returns 0 on success +RLAPI int FileMove(const char *srcPath, const char *dstPath); // Move file from one directory to another, dstPath created if it doesn't exist, returns 0 on success +RLAPI int FileTextReplace(const char *fileName, const char *search, const char *replacement); // Replace text in an existing file, returns 0 on success +RLAPI int FileTextFindIndex(const char *fileName, const char *search); // Find text in existing file, returns -1 if index not found or index otherwise +RLAPI bool FileExists(const char *fileName); // Check if file exists +RLAPI bool DirectoryExists(const char *dirPath); // Check if directory path exists +RLAPI bool IsFileExtension(const char *fileName, const char *ext); // Check file extension (recommended include point: .png, .wav) +RLAPI bool IsFileHidden(const char *filePath); // Check if file path (file or directory) is hidden by OS +RLAPI int GetFileLength(const char *fileName); // Get file length in bytes (NOTE: GetFileSize() conflicts with windows.h) +RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) +RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: '.png') +RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string +RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) +RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a provided fileName with path (uses static string) +RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a provided path (uses static string) +RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) +RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string) +RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success +RLAPI int ChangeDirectory(const char *dirPath); // Change working directory, returns 0 on success +RLAPI bool IsPathFile(const char *path); // Check if provided path points to a file +RLAPI bool IsPathDirectory(const char *path); // Check if provided path points to a directory +RLAPI bool IsPathAbsolute(const char *path); // Check if provided path is an absolute path +RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS +RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths, files and directories, no subdirs scan +RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and subdir scan; some filters available: '*.*','FILES*','DIRS*' +RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths +RLAPI bool IsFileDropped(void); // Check if file has been dropped into window +RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths +RLAPI void UnloadDroppedFiles(FilePathList files); // Unload dropped filepaths +RLAPI unsigned int GetDirectoryFileCount(const char *dirPath); // Get the file count in a directory +RLAPI unsigned int GetDirectoryFileCountEx(const char *basePath, const char *filter, bool scanSubdirs); // Get the file count in a directory with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result + +// Compression/Encoding functionality +RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() +RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() +RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string (includes NULL terminator), memory must be MemFree() +RLAPI unsigned char *DecodeDataBase64(const char *text, int *outputSize); // Decode Base64 string (expected NULL terminated), memory must be MemFree() +RLAPI unsigned int ComputeCRC32(const unsigned char *data, int dataSize); // Compute CRC32 hash code +RLAPI unsigned int *ComputeMD5(const unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) +RLAPI unsigned int *ComputeSHA1(const unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) +RLAPI unsigned int *ComputeSHA256(const unsigned char *data, int dataSize); // Compute SHA256 hash code, returns static int[8] (32 bytes) + +// Automation events functionality +RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS +RLAPI void UnloadAutomationEventList(AutomationEventList list); // Unload automation events list from file +RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName); // Export automation events list as text file +RLAPI void SetAutomationEventList(AutomationEventList *list); // Set automation event list to record to +RLAPI void SetAutomationEventBaseFrame(int frame); // Set automation event internal base frame to start recording +RLAPI void StartAutomationEventRecording(void); // Start recording automation events (AutomationEventList must be set) +RLAPI void StopAutomationEventRecording(void); // Stop recording automation events +RLAPI void PlayAutomationEvent(AutomationEvent event); // Play a recorded automation event + +//------------------------------------------------------------------------------------ +// Input Handling Functions (Module: core) +//------------------------------------------------------------------------------------ + +// Input-related functions: keyboard +RLAPI bool IsKeyPressed(int key); // Check if key has been pressed once +RLAPI bool IsKeyPressedRepeat(int key); // Check if key has been pressed again +RLAPI bool IsKeyDown(int key); // Check if key is being pressed +RLAPI bool IsKeyReleased(int key); // Check if key has been released once +RLAPI bool IsKeyUp(int key); // Check if key is NOT being pressed +RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty +RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty +RLAPI const char *GetKeyName(int key); // Get name of a QWERTY key on the current keyboard layout (eg returns string 'q' for KEY_A on an AZERTY keyboard) +RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) + +// Input-related functions: gamepads +RLAPI bool IsGamepadAvailable(int gamepad); // Check if gamepad is available +RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id +RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if gamepad button has been pressed once +RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if gamepad button is being pressed +RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if gamepad button has been released once +RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if gamepad button is NOT being pressed +RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed +RLAPI int GetGamepadAxisCount(int gamepad); // Get axis count for a gamepad +RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get movement value for a gamepad axis +RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) +RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds) + +// Input-related functions: mouse +RLAPI bool IsMouseButtonPressed(int button); // Check if mouse button has been pressed once +RLAPI bool IsMouseButtonDown(int button); // Check if mouse button is being pressed +RLAPI bool IsMouseButtonReleased(int button); // Check if mouse button has been released once +RLAPI bool IsMouseButtonUp(int button); // Check if mouse button is NOT being pressed +RLAPI int GetMouseX(void); // Get mouse position X +RLAPI int GetMouseY(void); // Get mouse position Y +RLAPI Vector2 GetMousePosition(void); // Get mouse position XY +RLAPI Vector2 GetMouseDelta(void); // Get mouse delta between frames +RLAPI void SetMousePosition(int x, int y); // Set mouse position XY +RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset +RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling +RLAPI float GetMouseWheelMove(void); // Get mouse wheel movement for X or Y, whichever is larger +RLAPI Vector2 GetMouseWheelMoveV(void); // Get mouse wheel movement for both X and Y +RLAPI void SetMouseCursor(int cursor); // Set mouse cursor + +// Input-related functions: touch +RLAPI int GetTouchX(void); // Get touch position X for touch point 0 (relative to screen size) +RLAPI int GetTouchY(void); // Get touch position Y for touch point 0 (relative to screen size) +RLAPI Vector2 GetTouchPosition(int index); // Get touch position XY for a touch point index (relative to screen size) +RLAPI int GetTouchPointId(int index); // Get touch point identifier for provided index +RLAPI int GetTouchPointCount(void); // Get number of touch points + +//------------------------------------------------------------------------------------ +// Gestures and Touch Handling Functions (Module: rgestures) +//------------------------------------------------------------------------------------ +RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +RLAPI bool IsGestureDetected(unsigned int gesture); // Check if gesture has been detected +RLAPI int GetGestureDetected(void); // Get latest detected gesture +RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in seconds +RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector +RLAPI float GetGestureDragAngle(void); // Get gesture drag angle +RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle + +//------------------------------------------------------------------------------------ +// Camera System Functions (Module: rcamera) +//------------------------------------------------------------------------------------ +RLAPI void UpdateCamera(Camera *camera, int mode); // Update camera position for selected mode +RLAPI void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, float zoom); // Update camera movement/rotation + +//------------------------------------------------------------------------------------ +// Basic Shapes Drawing Functions (Module: shapes) +//------------------------------------------------------------------------------------ +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +RLAPI void SetShapesTexture(Texture2D texture, Rectangle rec); // Set texture and rectangle to be used on shapes drawing +RLAPI Texture2D GetShapesTexture(void); // Get texture that is used for shapes drawing +RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing + +// Basic shapes drawing functions +RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care] +RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel using geometry (Vector version) [Can be slow, use with care] +RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line +RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines) +RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads) +RLAPI void DrawLineStrip(const Vector2 *points, int pointCount, Color color); // Draw lines sequence (using gl lines) +RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw line segment cubic-bezier in-out interpolation +RLAPI void DrawLineDashed(Vector2 startPos, Vector2 endPos, int dashSize, int spaceSize, Color color); // Draw a dashed line +RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle, counter-clockwise vertex order +RLAPI void DrawTriangleGradient(Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors, counter-clockwise vertex/color order +RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline, counter-clockwise vertex order +RLAPI void DrawTriangleLinesEx(Vector2 v1, Vector2 v2, Vector2 v3, float thick, Color color); // Draw triangle outline with line thickness, counter-clockwise vertex order +RLAPI void DrawTriangleFan(const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) +RLAPI void DrawTriangleStrip(const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) +RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters +RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors, counter-clockwise color order +RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline +RLAPI void DrawRectangleLinesEx(Rectangle rec, float thick, Color color); // Draw rectangle outline with line thickness +RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges +RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle lines with rounded edges +RLAPI void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float thick, Color color); // Draw rectangle lines with rounded edges outline and line thickness +RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon of n sides +RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides +RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float thick, Color color); // Draw a polygon outline of n sides with line thickness +RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle +RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) +RLAPI void DrawCircleGradient(Vector2 center, float radius, Color inner, Color outer); // Draw a gradient-filled circle +RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle +RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline +RLAPI void DrawCircleSectorLinesEx(Vector2 center, float radius, float startAngle, float endAngle, int segments, float thick, Color color); // Draw circle sector outline with thickness +RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline +RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) +RLAPI void DrawCircleLinesEx(Vector2 center, float radius, float thick, Color color); // Draw circle outline with line thickness +RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse +RLAPI void DrawEllipseV(Vector2 center, float radiusH, float radiusV, Color color); // Draw ellipse (Vector version) +RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline +RLAPI void DrawEllipseLinesV(Vector2 center, float radiusH, float radiusV, Color color); // Draw ellipse outline (Vector version) +RLAPI void DrawEllipseLinesEx(Vector2 center, float radiusH, float radiusV, float thick, Color color); // Draw ellipse outline with line thickness +RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring +RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline +RLAPI void DrawRingLinesEx(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, float thick, Color color); // Draw ring outline with line thickness + +// Splines drawing functions +RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points +RLAPI void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points +RLAPI void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull-Rom, minimum 4 points +RLAPI void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] +RLAPI void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] +RLAPI void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color); // Draw spline segment: Linear, 2 points +RLAPI void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: B-Spline, 4 points +RLAPI void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: Catmull-Rom, 4 points +RLAPI void DrawSplineSegmentBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float thick, Color color); // Draw spline segment: Quadratic Bezier, 2 points, 1 control point +RLAPI void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float thick, Color color); // Draw spline segment: Cubic Bezier, 2 points, 2 control points + +// Spline segment point evaluation functions, for a provided t [0.0f .. 1.0f] +RLAPI Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t); // Get (evaluate) spline point: Linear +RLAPI Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: B-Spline +RLAPI Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: Catmull-Rom +RLAPI Vector2 GetSplinePointBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float t); // Get (evaluate) spline point: Quadratic Bezier +RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float t); // Get (evaluate) spline point: Cubic Bezier + +// Basic shapes collision detection functions +RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles +RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles +RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle +RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created between two points [p1] and [p2] +RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle +RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle +RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] +RLAPI bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount); // Check if point is within a polygon described by array of vertices +RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference +RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision + +//------------------------------------------------------------------------------------ +// Texture Loading and Drawing Functions (Module: textures) +//------------------------------------------------------------------------------------ + +// Image loading functions +// NOTE: These functions do not require GPU access +RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) +RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data +RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) +RLAPI Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames); // Load image sequence from memory buffer +RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png' +RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data +RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer (screenshot) +RLAPI bool IsImageValid(Image image); // Check if an image is valid (data and parameters) +RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) +RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success +RLAPI unsigned char *ExportImageToMemory(Image image, const char *fileType, int *fileSize); // Export image to memory buffer, memory must be MemFree() +RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success + +// Image generation functions +RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color +RLAPI Image GenImageGradientLinear(int width, int height, int direction, Color start, Color end); // Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient +RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient +RLAPI Image GenImageGradientSquare(int width, int height, float density, Color inner, Color outer); // Generate image: square gradient +RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked +RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise +RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise +RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm, bigger tileSize means bigger cells +RLAPI Image GenImageText(int width, int height, const char *text); // Generate image: grayscale image from text data + +// Image manipulation functions +RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) +RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece +RLAPI Image ImageFromChannel(Image image, int selectedChannel); // Create an image from a selected channel of another image (GRAYSCALE) +RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) +RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) +RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format +RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) +RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle +RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value +RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image +RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel +RLAPI void ImageBlurGaussian(Image *image, int blurSize); // Apply Gaussian blur using a box blur approximation +RLAPI void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize); // Apply custom square convolution kernel to image +RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) +RLAPI void ImageResizeNN(Image *image, int newWidth, int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) +RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color +RLAPI void ImageMipmaps(Image *image); // Compute all mipmap levels for a provided image +RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +RLAPI void ImageFlipVertical(Image *image); // Flip image vertically +RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally +RLAPI void ImageRotate(Image *image, int degrees); // Rotate image by input angle in degrees (-359 to 359) +RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg +RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg +RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint +RLAPI void ImageColorInvert(Image *image); // Modify image color: invert +RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale +RLAPI void ImageColorContrast(Image *image, int contrast); // Modify image color: contrast (-100 to 100) +RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) +RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color +RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) +RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount); // Load colors palette from image as a Color array (RGBA - 32bit) +RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() +RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() +RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle +RLAPI Color GetImageColor(Image image, int x, int y); // Get image pixel color at (x, y) position + +// Image drawing functions +// NOTE: Image software-rendering functions (CPU) +RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with provided color +RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image +RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) +RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image +RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) +RLAPI void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color color); // Draw a line defining thickness within an image +RLAPI void ImageDrawLineStrip(Image *dst, const Vector2 *points, int pointCount, Color color); // Draw a lines sequence within an image +RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image +RLAPI void ImageDrawTriangleGradient(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image +RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image +RLAPI void ImageDrawTriangleFan(Image *dst, const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) +RLAPI void ImageDrawTriangleStrip(Image *dst, const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image +RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) +RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectanglePro(Image *dst, Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters within and image +RLAPI void ImageDrawRectangleLines(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle lines within an image +RLAPI void ImageDrawRectangleLinesEx(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image with line thickness +RLAPI void ImageDrawRectangleGradientEx(Image *dst, Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw rectangle with gradient colors within an image, counter-clockwise color order +RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw a filled circle within an image +RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw a filled circle within an image (Vector version) +RLAPI void ImageDrawCircleLines(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle outline within an image +RLAPI void ImageDrawCircleLinesV(Image *dst, Vector2 center, int radius, Color color); // Draw circle outline within an image (Vector version) +RLAPI void ImageDrawCircleGradient(Image *dst, Vector2 center, float radius, Color inner, Color outer); // Draw a gradient-filled circle within an image + +RLAPI void ImageDrawImage(Image *dst, Image src, int posX, int posY, Color tint); // Draw an image within an image +RLAPI void ImageDrawImageEx(Image *dst, Image src, Vector2 position, float rotation, float scale, Color tint); // Draw an image with scaling and rotation within an image +RLAPI void ImageDrawImageRec(Image *dst, Image src, Rectangle srcRec, Vector2 position, Color tint); // Draw a part of an image defined by a rectangle within an image +RLAPI void ImageDrawImagePro(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Vector2 origin, float rotation, Color tint); // Draw a part of an image defined by a rectangle into destination rectangle, with scaling and rotation, within an image +RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) +RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) +RLAPI void ImageDrawTextPro(Image *dst, Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation) + +// Texture loading functions +// NOTE: These functions require GPU access +RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) +RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data +RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported +RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) +RLAPI RenderTexture2D LoadRenderTextureEx(int width, int height, int format); // Load texture for rendering (framebuffer), with specific format +RLAPI bool IsTextureValid(Texture2D texture); // Check if texture is valid (loaded in GPU) +RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) +RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if render texture is valid (loaded in GPU) +RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) +RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data (pixels should be able to fill texture) +RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data (pixels and rec should fit in texture) + +// Texture configuration functions +RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture +RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode +RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode + +// Texture drawing functions +RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D +RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 +RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with rotation and scale +RLAPI void DrawTextureRec(Texture2D texture, Rectangle rec, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle +RLAPI void DrawTexturePro(Texture2D texture, Rectangle srcrec, Rectangle dstrec, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a source rectangle to destination rectangle, with scaling and rotation +RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dstrec, Vector2 origin, float rotation, Color tint); // Draw a texture (or part of it) that stretches or shrinks nicely + +// Color/pixel related functions +RLAPI bool ColorIsEqual(Color col1, Color col2); // Check if two colors are equal +RLAPI Color Fade(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI int ColorToInt(Color color); // Get hexadecimal value for a Color (0xRRGGBBAA) +RLAPI Vector4 ColorNormalize(Color color); // Get Color normalized as float [0..1] +RLAPI Color ColorFromNormalized(Vector4 normalized); // Get Color from normalized values [0..1] +RLAPI Vector3 ColorToHSV(Color color); // Get HSV values for a Color, hue [0..360], saturation/value [0..1] +RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Get a Color from HSV values, hue [0..360], saturation/value [0..1] +RLAPI Color ColorTint(Color color, Color tint); // Get color multiplied with another color +RLAPI Color ColorBrightness(Color color, float factor); // Get color with brightness correction, brightness factor goes from -1.0f to 1.0f +RLAPI Color ColorContrast(Color color, float contrast); // Get color with contrast correction, contrast values between -1.0f and 1.0f +RLAPI Color ColorAlpha(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Get src alpha-blended into dst color with tint +RLAPI Color ColorLerp(Color color1, Color color2, float factor); // Get color lerp interpolation between two colors, factor [0.0f..1.0f] +RLAPI Color GetColor(unsigned int hexValue); // Get Color structure from hexadecimal value +RLAPI Color GetPixelColor(const void *srcPtr, int format); // Get Color from a source pixel pointer of certain format +RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer +RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format + +//------------------------------------------------------------------------------------ +// Font Loading and Text Drawing Functions (Module: text) +//------------------------------------------------------------------------------------ + +// Font loading/unloading functions +RLAPI Font GetFontDefault(void); // Get the default Font +RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) +RLAPI Font LoadFontEx(const char *fileName, int fontSize, const int *codepoints, int codepointCount); // Load font from file with defined codepoints and generation size, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height +RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) +RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, const int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' +RLAPI bool IsFontValid(Font font); // Check if font is valid (font data loaded, WARNING: GPU texture not checked) +RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, const int *codepoints, int codepointCount, int type, int *glyphCount); // Load font data for further use +RLAPI Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI void UnloadFontData(GlyphInfo *glyphs, int glyphCount); // Unload font chars info data (RAM) +RLAPI void UnloadFont(Font font); // Unload font from GPU memory (VRAM) +RLAPI bool ExportFontAsCode(Font font, const char *fileName); // Export font as code file, returns true on success + +// Text drawing functions +RLAPI void DrawFPS(int posX, int posY); // Draw current FPS +RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) +RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation) +RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) +RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple characters (codepoint) + +// Text font info functions +RLAPI void SetTextLineSpacing(int spacing); // Set vertical line spacing when drawing with line-breaks +RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font +RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font +RLAPI Vector2 MeasureTextCodepoints(Font font, const int *codepoints, int length, float fontSize, float spacing); // Measure string size for an existing array of codepoints for Font +RLAPI int GetGlyphIndex(Font font, int codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found +RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found +RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found + +// Text codepoints management functions (unicode characters) +RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array +RLAPI void UnloadUTF8(char *text); // Unload UTF-8 text encoded from codepoints array +RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter +RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory +RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string +RLAPI int GetCodepoint(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointPrevious(const char *text, int *codepointSize); // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI const char *CodepointToUTF8(int codepoint, int *utf8Size); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) + +// Text strings management functions (no UTF-8 strings, only byte chars) +// WARNING 1: Most of these functions use internal static buffers[], it's recommended to store returned data on user-side for re-use +// WARNING 2: Some functions allocate memory internally for the returned strings, those strings must be freed by user using MemFree() +RLAPI char **LoadTextLines(const char *text, int *count); // Load text as separate lines ('\n') +RLAPI void UnloadTextLines(char **text, int lineCount); // Unload text lines +RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied +RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text strings are equal +RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending +RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf() style) +RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string +RLAPI const char *TextRemoveSpaces(const char *text); // Remove text spaces, concat words +RLAPI char *GetTextBetween(const char *text, const char *begin, const char *end); // Get text between two strings +RLAPI char *TextReplace(const char *text, const char *search, const char *replacement); // Replace text string with new string +RLAPI char *TextReplaceAlloc(const char *text, const char *search, const char *replacement); // Replace text string with new string, memory must be MemFree() +RLAPI char *TextReplaceBetween(const char *text, const char *begin, const char *end, const char *replacement); // Replace text between two specific strings +RLAPI char *TextReplaceBetweenAlloc(const char *text, const char *begin, const char *end, const char *replacement); // Replace text between two specific strings, memory must be MemFree() +RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a defined byte position +RLAPI char *TextInsertAlloc(const char *text, const char *insert, int position); // Insert text in a defined byte position, memory must be MemFree() +RLAPI char *TextJoin(char **textList, int count, const char *delimiter); // Join text strings with delimiter +RLAPI char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings, using MAX_TEXTSPLIT_COUNT static strings +RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor +RLAPI int TextFindIndex(const char *text, const char *search); // Find first text occurrence within a string, -1 if not found +RLAPI char *TextToUpper(const char *text); // Get upper case version of provided string +RLAPI char *TextToLower(const char *text); // Get lower case version of provided string +RLAPI char *TextToPascal(const char *text); // Get Pascal case notation version of provided string +RLAPI char *TextToSnake(const char *text); // Get Snake case notation version of provided string +RLAPI char *TextToCamel(const char *text); // Get Camel case notation version of provided string +RLAPI int TextToInteger(const char *text); // Get integer value from text +RLAPI float TextToFloat(const char *text); // Get float value from text + +//------------------------------------------------------------------------------------ +// Basic 3d Shapes Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Basic geometric 3D shapes drawing functions +RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space +RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line +RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space +RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle, counter-clockwise vertex order +RLAPI void DrawTriangleStrip3D(const Vector3 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube +RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) +RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires +RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) +RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere +RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with defined rings and slices +RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires +RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color); // Draw a cylinder/cone +RLAPI void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder with base at startPos and top at endPos +RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color); // Draw a cylinder/cone wires +RLAPI void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder wires with base at startPos and top at endPos +RLAPI void DrawCapsule(Vector3 startPos, Vector3 endPos, float radius, int rings, int slices, Color color); // Draw a capsule with the center of its sphere caps at startPos and endPos +RLAPI void DrawCapsuleWires(Vector3 startPos, Vector3 endPos, float radius, int rings, int slices, Color color); // Draw capsule wireframe with the center of its sphere caps at startPos and endPos +RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ +RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line +RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) + +//------------------------------------------------------------------------------------ +// Model 3d Loading and Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Model management functions +RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) +RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) +RLAPI bool IsModelValid(Model model); // Check if model is valid (loaded in GPU, VAO/VBOs) +RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) +RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes) + +// Model drawing functions +RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) +RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with custom transform +RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) +RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires with custom transform +RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) +RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture +RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle rec, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by rectangle +RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle rec, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source rectangle with scaling and rotation + +// Mesh management functions +RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids +RLAPI void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index +RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU +RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform +RLAPI void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms +RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits +RLAPI void GenMeshTangents(Mesh *mesh); // Compute mesh tangents +RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success +RLAPI bool ExportMeshAsCode(Mesh mesh, const char *fileName); // Export mesh as code file (.h) defining multiple arrays of vertex attributes + +// Mesh generation functions +RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh +RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) +RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh +RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) +RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) +RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh +RLAPI Mesh GenMeshCone(float radius, float height, int slices); // Generate cone/pyramid mesh +RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh +RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh +RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data +RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data + +// Material loading/unloading functions +RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file +RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +RLAPI bool IsMaterialValid(Material material); // Check if material is valid (shader assigned, map textures loaded in GPU) +RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) +RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh + +// Model animations loading/unloading functions +RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file +RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, float frame); // Update model animation pose (vertex buffers and bone matrices) +RLAPI void UpdateModelAnimationEx(Model model, ModelAnimation animA, float frameA, ModelAnimation animB, float frameB, float blend); // Update model animation pose, blending two animations +RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data +RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match + +// Collision detection functions +RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres +RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes +RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere +RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere +RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box +RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh +RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle +RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad + +//------------------------------------------------------------------------------------ +// Audio Loading and Playing Functions (Module: audio) +//------------------------------------------------------------------------------------ +typedef void (*AudioCallback)(void *bufferData, unsigned int frames); + +// Audio device management functions +RLAPI void InitAudioDevice(void); // Initialize audio device and context +RLAPI void CloseAudioDevice(void); // Close the audio device and context +RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +RLAPI void SetMasterVolume(float volume); // Set master volume (listener) +RLAPI float GetMasterVolume(void); // Get master volume (listener) + +// Wave/Sound loading/unloading functions +RLAPI Wave LoadWave(const char *fileName); // Load wave data from file +RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' +RLAPI bool IsWaveValid(Wave wave); // Check if wave data is valid (data loaded and parameters) +RLAPI Sound LoadSound(const char *fileName); // Load sound from file +RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +RLAPI Sound LoadSoundAlias(Sound source); // Load sound alias, new sound that shares the same sample data as the source sound, does not own the sound data +RLAPI bool IsSoundValid(Sound sound); // Check if sound is valid (data loaded and buffers initialized) +RLAPI void UpdateSound(Sound sound, const void *data, int frameCount); // Update sound buffer with new data (default data format: 32 bit float, stereo) +RLAPI void UnloadWave(Wave wave); // Unload wave data +RLAPI void UnloadSound(Sound sound); // Unload sound +RLAPI void UnloadSoundAlias(Sound alias); // Unload sound alias (does not deallocate sample data) +RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success +RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success + +// Wave/Sound management functions +RLAPI void PlaySound(Sound sound); // Play a sound +RLAPI void StopSound(Sound sound); // Stop playing a sound +RLAPI void PauseSound(Sound sound); // Pause a sound +RLAPI void ResumeSound(Sound sound); // Resume a paused sound +RLAPI bool IsSoundPlaying(Sound sound); // Check if sound is currently playing +RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +RLAPI void SetSoundPan(Sound sound, float pan); // Set pan for a sound (-1.0 left, 0.0 center, 1.0 right) +RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave +RLAPI void WaveCrop(Wave *wave, int initFrame, int finalFrame); // Crop a wave to defined frames range +RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a 32bit float data array +RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() + +// Music management functions +RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file +RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data +RLAPI bool IsMusicValid(Music music); // Check if music stream is valid (context and buffers initialized) +RLAPI void UnloadMusicStream(Music music); // Unload music stream +RLAPI void PlayMusicStream(Music music); // Start music playing +RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing +RLAPI void UpdateMusicStream(Music music); // Update buffers for music streaming +RLAPI void StopMusicStream(Music music); // Stop music playing +RLAPI void PauseMusicStream(Music music); // Pause music playing +RLAPI void ResumeMusicStream(Music music); // Resume playing paused music +RLAPI void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) +RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for music (1.0 is base level) +RLAPI void SetMusicPan(Music music, float pan); // Set pan for music (-1.0 left, 0.0 center, 1.0 right) +RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) +RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) +RLAPI bool IsAudioStreamValid(AudioStream stream); // Check if an audio stream is valid (buffers initialized) +RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory +RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data +RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream +RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream +RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream +RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream +RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +RLAPI void SetAudioStreamPan(AudioStream stream, float pan); // Set pan for audio stream (-1.0 left, 0.0 center, 1.0 right) +RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams +RLAPI void SetAudioStreamCallback(AudioStream stream, AudioCallback callback); // Audio thread callback to request new data + +RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream, receives frames x 2 samples as 'float' (stereo) +RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream + +RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline, receives frames x 2 samples as 'float' (stereo) +RLAPI void DetachAudioMixedProcessor(AudioCallback processor); // Detach audio stream processor from the entire audio pipeline + +#if defined(__cplusplus) +} +#endif + +#endif // RAYLIB_H diff --git a/src/libraries/raylib/raylib/src/raylib.ico b/src/libraries/raylib/raylib/src/raylib.ico new file mode 100644 index 0000000..8dd7bb7 Binary files /dev/null and b/src/libraries/raylib/raylib/src/raylib.ico differ diff --git a/src/libraries/raylib/raylib/src/raylib.rc b/src/libraries/raylib/raylib/src/raylib.rc new file mode 100644 index 0000000..bd9fcea --- /dev/null +++ b/src/libraries/raylib/raylib/src/raylib.rc @@ -0,0 +1,27 @@ +GLFW_ICON ICON "raylib.ico" + +1 VERSIONINFO +FILEVERSION 6,0,0,0 +PRODUCTVERSION 6,0,0,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + //BLOCK "080904E4" // English UK + BLOCK "040904E4" // English US + BEGIN + VALUE "CompanyName", "raylib technologies" + VALUE "FileDescription", "raylib application (www.raylib.com)" + VALUE "FileVersion", "6.0.0" + VALUE "InternalName", "raylib" + VALUE "LegalCopyright", "(c) 2026 Ramon Santamaria (@raysan5)" + VALUE "OriginalFilename", "raylib" + VALUE "ProductName", "raylib app" + VALUE "ProductVersion", "6.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + //VALUE "Translation", 0x809, 1252 // English UK + VALUE "Translation", 0x409, 1252 // English US + END +END diff --git a/src/libraries/raylib/raylib/src/raylib.rc.data b/src/libraries/raylib/raylib/src/raylib.rc.data new file mode 100644 index 0000000..64ed4e3 Binary files /dev/null and b/src/libraries/raylib/raylib/src/raylib.rc.data differ diff --git a/src/libraries/raylib/raylib/src/raymath.h b/src/libraries/raylib/raylib/src/raymath.h new file mode 100644 index 0000000..66c4026 --- /dev/null +++ b/src/libraries/raylib/raylib/src/raymath.h @@ -0,0 +1,3169 @@ +/********************************************************************************************** +* +* raymath v2.0 - Math functions to work with Vector2, Vector3, Vector4, Matrix and Quaternions +* +* CONVENTIONS: +* - Matrix structure is defined as row-major (memory layout) but parameters naming AND all +* math operations performed by the library consider the structure as it was column-major +* It is like transposed versions of the matrices are used for all the maths +* It benefits some functions making them cache-friendly and also avoids matrix +* transpositions sometimes required by OpenGL +* Example: In memory order, row0 is [m0 m4 m8 m12] but in semantic math row0 is [m0 m1 m2 m3] +* - Functions are always self-contained, no function use another raymath function inside, +* required code is directly re-implemented inside +* - Functions input parameters are always received by value (2 unavoidable exceptions) +* - Functions use always a "result" variable for return (except C++ operators) +* - Functions are always defined inline +* - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) +* - No compound literals used to make sure the library is compatible with C++ +* +* CONFIGURATION: +* #define RAYMATH_IMPLEMENTATION +* Generates the implementation of the library into the included file +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation +* +* #define RAYMATH_STATIC_INLINE +* Define static inline functions code, so #include header suffices for use +* This may use up lots of memory +* +* #define RAYMATH_DISABLE_CPP_OPERATORS +* Disables C++ operator overloads for raymath types. +* +* #define RAYMATH_USE_SIMD_INTRINSICS 1 +* Try to enable SIMD intrinsics for MatrixMultiply() +* Note that users enabling it must be aware of the target platform where application will +* run to support the selected SIMD intrinsic, for now, only SSE is supported +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2015-2026 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYMATH_H +#define RAYMATH_H + +#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_STATIC_INLINE) + #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_STATIC_INLINE is contradictory" +#endif + +// Function specifiers definition +#if defined(RAYMATH_IMPLEMENTATION) + #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RMAPI __declspec(dllexport) extern inline // Building raylib as a Win32 shared library (.dll) + #elif defined(BUILD_LIBTYPE_SHARED) + #define RMAPI __attribute__((visibility("default"))) // Building raylib as a Unix shared library (.so/.dylib) + #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RMAPI __declspec(dllimport) // Using raylib as a Win32 shared library (.dll) + #else + #define RMAPI extern inline // Provide external definition + #endif +#elif defined(RAYMATH_STATIC_INLINE) + #define RMAPI static inline // Functions may be inlined, no external out-of-line definition +#else + #if defined(__TINYC__) + #define RMAPI static inline // plain inline not supported by tinycc (See issue #435) + #else + #define RMAPI inline // Functions may be inlined or external definition used + #endif +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif + +#ifndef EPSILON + #define EPSILON 0.000001f +#endif + +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif + +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Get float vector for Matrix +#ifndef MatrixToFloat + #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) +#endif + +// Get float vector for Vector3 +#ifndef Vector3ToFloat + #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if !defined(RL_VECTOR2_TYPE) +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; +#define RL_VECTOR2_TYPE +#endif + +#if !defined(RL_VECTOR3_TYPE) +// Vector3 type +typedef struct Vector3 { + float x; + float y; + float z; +} Vector3; +#define RL_VECTOR3_TYPE +#endif + +#if !defined(RL_VECTOR4_TYPE) +// Vector4 type +typedef struct Vector4 { + float x; + float y; + float z; + float w; +} Vector4; +#define RL_VECTOR4_TYPE +#endif + +#if !defined(RL_QUATERNION_TYPE) +// Quaternion type +typedef Vector4 Quaternion; +#define RL_QUATERNION_TYPE +#endif + +#if !defined(RL_MATRIX_TYPE) +// Matrix type (OpenGL style 4x4 - right handed, column major) +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; +#define RL_MATRIX_TYPE +#endif + +// NOTE: Helper types to be used instead of array return types for *ToFloat functions +#if !defined(RL_FLOAT3_TYPE) +typedef struct float3 { + float v[3]; +} float3; +#define RL_FLOAT3_TYPE +#endif + +#if !defined(RL_FLOAT16_TYPE) +typedef struct float16 { + float v[16]; +} float16; +#define RL_FLOAT16_TYPE +#endif + +#include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() + +#if RAYMATH_USE_SIMD_INTRINSICS + // SIMD is used on the most costly raymath function MatrixMultiply() + // NOTE: Only SSE intrinsics support implemented + // TODO: Consider support for other SIMD intrinsics: + // - SSEx, AVX, AVX2, FMA, NEON, RVV + /* + #if defined(__SSE4_2__) + #include + #define RAYMATH_SSE42_ENABLED + #elif defined(__SSE4_1__) + #include + #define RAYMATH_SSE41_ENABLED + #elif defined(__SSSE3__) + #include + #define RAYMATH_SSSE3_ENABLED + #elif defined(__SSE3__) + #include + #define RAYMATH_SSE3_ENABLED + #elif defined(__SSE2__) || (defined(_M_AMD64) || defined(_M_X64)) // SSE2 x64 + #include + #define RAYMATH_SSE2_ENABLED + #endif + */ + #if defined(__SSE__) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1)) + #include + #define RAYMATH_SSE_ENABLED + #endif +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Utils math +//---------------------------------------------------------------------------------- + +// Clamp float value +RMAPI float Clamp(float value, float min, float max) +{ + float result = (value < min)? min : value; + + if (result > max) result = max; + + return result; +} + +// Calculate linear interpolation between two floats +RMAPI float Lerp(float start, float end, float amount) +{ + float result = start + amount*(end - start); + + return result; +} + +// Normalize input value within input range +RMAPI float Normalize(float value, float start, float end) +{ + float result = (value - start)/(end - start); + + return result; +} + +// Remap input value within input range to output range +RMAPI float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) +{ + float result = (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; + + return result; +} + +// Wrap input value from min to max +RMAPI float Wrap(float value, float min, float max) +{ + float result = value - (max - min)*floorf((value - min)/(max - min)); + + return result; +} + +// Check whether two given floats are almost equal +RMAPI int FloatEquals(float x, float y) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = (fabsf(x - y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(x), fabsf(y)))); + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector2 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector2 Vector2Zero(void) +{ + Vector2 result = { 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector2 Vector2One(void) +{ + Vector2 result = { 1.0f, 1.0f }; + + return result; +} + +// Add two vectors (v1 + v2) +RMAPI Vector2 Vector2Add(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x + v2.x, v1.y + v2.y }; + + return result; +} + +// Add vector and float value +RMAPI Vector2 Vector2AddValue(Vector2 v, float add) +{ + Vector2 result = { v.x + add, v.y + add }; + + return result; +} + +// Subtract two vectors (v1 - v2) +RMAPI Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x - v2.x, v1.y - v2.y }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector2 Vector2SubtractValue(Vector2 v, float sub) +{ + Vector2 result = { v.x - sub, v.y - sub }; + + return result; +} + +// Calculate vector length +RMAPI float Vector2Length(Vector2 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y)); + + return result; +} + +// Calculate vector square length +RMAPI float Vector2LengthSqr(Vector2 v) +{ + float result = (v.x*v.x) + (v.y*v.y); + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector2DotProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y); + + return result; +} + +// Calculate two vectors cross product +RMAPI float Vector2CrossProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.y - v1.y*v2.x); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) +{ + float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector2DistanceSqr(Vector2 v1, Vector2 v2) +{ + float result = ((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + + return result; +} + +// Calculate the signed angle from v1 to v2, relative to the origin (0, 0) +// NOTE: Coordinate system convention: positive X right, positive Y down +// positive angles appear clockwise, and negative angles appear counterclockwise +RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) +{ + float result = 0.0f; + + float dot = v1.x*v2.x + v1.y*v2.y; + float det = v1.x*v2.y - v1.y*v2.x; + + result = atan2f(det, dot); + + return result; +} + +// Calculate angle defined by a two vectors line +// NOTE: Parameters need to be normalized +// Current implementation should be aligned with glm::angle +RMAPI float Vector2LineAngle(Vector2 start, Vector2 end) +{ + float result = 0.0f; + + // TODO(10/9/2023): Currently angles move clockwise, determine if this is wanted behavior + result = -atan2f(end.y - start.y, end.x - start.x); + + return result; +} + +// Scale vector (multiply by value) +RMAPI Vector2 Vector2Scale(Vector2 v, float scale) +{ + Vector2 result = { v.x*scale, v.y*scale }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x*v2.x, v1.y*v2.y }; + + return result; +} + +// Negate vector +RMAPI Vector2 Vector2Negate(Vector2 v) +{ + Vector2 result = { -v.x, -v.y }; + + return result; +} + +// Divide vector by vector +RMAPI Vector2 Vector2Divide(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x/v2.x, v1.y/v2.y }; + + return result; +} + +// Normalize provided vector +RMAPI Vector2 Vector2Normalize(Vector2 v) +{ + Vector2 result = { 0 }; + float length = sqrtf((v.x*v.x) + (v.y*v.y)); + + if (length > 0) + { + float ilength = 1.0f/length; + result.x = v.x*ilength; + result.y = v.y*ilength; + } + + return result; +} + +// Transforms a Vector2 by a given Matrix +RMAPI Vector2 Vector2Transform(Vector2 v, Matrix mat) +{ + Vector2 result = { 0 }; + + float x = v.x; + float y = v.y; + float z = 0; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) +{ + Vector2 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector2 Vector2Reflect(Vector2 v, Vector2 normal) +{ + Vector2 result = { 0 }; + + float dotProduct = (v.x*normal.x + v.y*normal.y); // Dot product + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + + return result; +} + +// Get min value for each pair of components +RMAPI Vector2 Vector2Min(Vector2 v1, Vector2 v2) +{ + Vector2 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector2 Vector2Max(Vector2 v1, Vector2 v2) +{ + Vector2 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + + return result; +} + +// Rotate vector by angle +RMAPI Vector2 Vector2Rotate(Vector2 v, float angle) +{ + Vector2 result = { 0 }; + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.x = v.x*cosres - v.y*sinres; + result.y = v.x*sinres + v.y*cosres; + + return result; +} + +// Move Vector towards target +RMAPI Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) +{ + Vector2 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float value = (dx*dx) + (dy*dy); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + + return result; +} + +// Invert the given vector +RMAPI Vector2 Vector2Invert(Vector2 v) +{ + Vector2 result = { 1.0f/v.x, 1.0f/v.y }; + + return result; +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +RMAPI Vector2 Vector2Clamp(Vector2 v, Vector2 min, Vector2 max) +{ + Vector2 result = { 0 }; + + result.x = fminf(max.x, fmaxf(min.x, v.x)); + result.y = fminf(max.y, fmaxf(min.y, v.y)); + + return result; +} + +// Clamp the magnitude of the vector between two min and max values +RMAPI Vector2 Vector2ClampValue(Vector2 v, float min, float max) +{ + Vector2 result = v; + + float length = (v.x*v.x) + (v.y*v.y); + if (length > 0.0f) + { + length = sqrtf(length); + + float scale = 1; // By default, 1 as the neutral element + if (length < min) scale = min/length; + else if (length > max) scale = max/length; + + result.x = v.x*scale; + result.y = v.y*scale; + } + + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector2Equals(Vector2 p, Vector2 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))); + + return result; +} + +// Compute the direction of a refracted ray +// v: normalized direction of the incoming ray +// n: normalized normal vector of the interface of two optical media +// r: ratio of the refractive index of the medium from where the ray comes +// to the refractive index of the medium on the other side of the surface +RMAPI Vector2 Vector2Refract(Vector2 v, Vector2 n, float r) +{ + Vector2 result = { 0 }; + + float dot = v.x*n.x + v.y*n.y; + float d = 1.0f - r*r*(1.0f - dot*dot); + + if (d >= 0.0f) + { + d = sqrtf(d); + v.x = r*v.x - (r*dot + d)*n.x; + v.y = r*v.y - (r*dot + d)*n.y; + + result = v; + } + + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector3 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector3 Vector3Zero(void) +{ + Vector3 result = { 0.0f, 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector3 Vector3One(void) +{ + Vector3 result = { 1.0f, 1.0f, 1.0f }; + + return result; +} + +// Add two vectors +RMAPI Vector3 Vector3Add(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + + return result; +} + +// Add vector and float value +RMAPI Vector3 Vector3AddValue(Vector3 v, float add) +{ + Vector3 result = { v.x + add, v.y + add, v.z + add }; + + return result; +} + +// Subtract two vectors +RMAPI Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector3 Vector3SubtractValue(Vector3 v, float sub) +{ + Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; + + return result; +} + +// Multiply vector by scalar +RMAPI Vector3 Vector3Scale(Vector3 v, float scalar) +{ + Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; + + return result; +} + +// Calculate two vectors cross product +RMAPI Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + + return result; +} + +// Calculate one vector perpendicular vector +RMAPI Vector3 Vector3Perpendicular(Vector3 v) +{ + Vector3 result = { 0 }; + + float min = fabsf(v.x); + Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; + + if (fabsf(v.y) < min) + { + min = fabsf(v.y); + Vector3 tmp = {0.0f, 1.0f, 0.0f}; + cardinalAxis = tmp; + } + + if (fabsf(v.z) < min) + { + Vector3 tmp = {0.0f, 0.0f, 1.0f}; + cardinalAxis = tmp; + } + + // Cross product between vectors + result.x = v.y*cardinalAxis.z - v.z*cardinalAxis.y; + result.y = v.z*cardinalAxis.x - v.x*cardinalAxis.z; + result.z = v.x*cardinalAxis.y - v.y*cardinalAxis.x; + + return result; +} + +// Calculate vector length +RMAPI float Vector3Length(const Vector3 v) +{ + float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + + return result; +} + +// Calculate vector square length +RMAPI float Vector3LengthSqr(const Vector3 v) +{ + float result = v.x*v.x + v.y*v.y + v.z*v.z; + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector3DotProduct(Vector3 v1, Vector3 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector3Distance(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + result = sqrtf(dx*dx + dy*dy + dz*dz); + + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector3DistanceSqr(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + result = dx*dx + dy*dy + dz*dz; + + return result; +} + +// Calculate angle between two vectors +RMAPI float Vector3Angle(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + Vector3 cross = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + float len = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z); + float dot = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + result = atan2f(len, dot); + + return result; +} + +// Negate provided vector (invert direction) +RMAPI Vector3 Vector3Negate(Vector3 v) +{ + Vector3 result = { -v.x, -v.y, -v.z }; + + return result; +} + +// Divide vector by vector +RMAPI Vector3 Vector3Divide(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; + + return result; +} + +// Normalize provided vector +RMAPI Vector3 Vector3Normalize(Vector3 v) +{ + Vector3 result = v; + + float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length != 0.0f) + { + float ilength = 1.0f/length; + + result.x *= ilength; + result.y *= ilength; + result.z *= ilength; + } + + return result; +} + +//Calculate the projection of the vector v1 on to v2 +RMAPI Vector3 Vector3Project(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); + + float mag = v1dv2/v2dv2; + + result.x = v2.x*mag; + result.y = v2.y*mag; + result.z = v2.z*mag; + + return result; +} + +//Calculate the rejection of the vector v1 on to v2 +RMAPI Vector3 Vector3Reject(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); + + float mag = v1dv2/v2dv2; + + result.x = v1.x - (v2.x*mag); + result.y = v1.y - (v2.y*mag); + result.z = v1.z - (v2.z*mag); + + return result; +} + +// Orthonormalize provided vectors +// Makes vectors normalized and orthogonal to each other +// Gram-Schmidt function implementation +RMAPI void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) +{ + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Normalize(*v1); + Vector3 v = *v1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + v1->x *= ilength; + v1->y *= ilength; + v1->z *= ilength; + + // Vector3CrossProduct(*v1, *v2) + Vector3 vn1 = { v1->y*v2->z - v1->z*v2->y, v1->z*v2->x - v1->x*v2->z, v1->x*v2->y - v1->y*v2->x }; + + // Vector3Normalize(vn1); + v = vn1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vn1.x *= ilength; + vn1.y *= ilength; + vn1.z *= ilength; + + // Vector3CrossProduct(vn1, *v1) + Vector3 vn2 = { vn1.y*v1->z - vn1.z*v1->y, vn1.z*v1->x - vn1.x*v1->z, vn1.x*v1->y - vn1.y*v1->x }; + + *v2 = vn2; +} + +// Transforms a Vector3 by a given Matrix +RMAPI Vector3 Vector3Transform(Vector3 v, Matrix mat) +{ + Vector3 result = { 0 }; + + float x = v.x; + float y = v.y; + float z = v.z; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; + + return result; +} + +// Transform a vector by quaternion rotation +RMAPI Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) +{ + Vector3 result = { 0 }; + + result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); + result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); + result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); + + return result; +} + +// Rotates a vector around an axis +RMAPI Vector3 Vector3RotateByAxisAngle(Vector3 v, Vector3 axis, float angle) +{ + // Using Euler-Rodrigues Formula + // Ref.: https://en.wikipedia.org/w/index.php?title=Euler%E2%80%93Rodrigues_formula + + Vector3 result = v; + + // Vector3Normalize(axis); + float length = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + angle /= 2.0f; + float a = sinf(angle); + float b = axis.x*a; + float c = axis.y*a; + float d = axis.z*a; + a = cosf(angle); + Vector3 w = { b, c, d }; + + // Vector3CrossProduct(w, v) + Vector3 wv = { w.y*v.z - w.z*v.y, w.z*v.x - w.x*v.z, w.x*v.y - w.y*v.x }; + + // Vector3CrossProduct(w, wv) + Vector3 wwv = { w.y*wv.z - w.z*wv.y, w.z*wv.x - w.x*wv.z, w.x*wv.y - w.y*wv.x }; + + // Vector3Scale(wv, 2*a) + a *= 2; + wv.x *= a; + wv.y *= a; + wv.z *= a; + + // Vector3Scale(wwv, 2) + wwv.x *= 2; + wwv.y *= 2; + wwv.z *= 2; + + result.x += wv.x; + result.y += wv.y; + result.z += wv.z; + + result.x += wwv.x; + result.y += wwv.y; + result.z += wwv.z; + + return result; +} + +// Move Vector towards target +RMAPI Vector3 Vector3MoveTowards(Vector3 v, Vector3 target, float maxDistance) +{ + Vector3 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float dz = target.z - v.z; + float value = (dx*dx) + (dy*dy) + (dz*dz); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + result.z = v.z + dz/dist*maxDistance; + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) +{ + Vector3 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + + return result; +} + +// Calculate cubic hermite interpolation between two vectors and their tangents +// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic +RMAPI Vector3 Vector3CubicHermite(Vector3 v1, Vector3 tangent1, Vector3 v2, Vector3 tangent2, float amount) +{ + Vector3 result = { 0 }; + + float amountPow2 = amount*amount; + float amountPow3 = amount*amount*amount; + + result.x = (2*amountPow3 - 3*amountPow2 + 1)*v1.x + (amountPow3 - 2*amountPow2 + amount)*tangent1.x + (-2*amountPow3 + 3*amountPow2)*v2.x + (amountPow3 - amountPow2)*tangent2.x; + result.y = (2*amountPow3 - 3*amountPow2 + 1)*v1.y + (amountPow3 - 2*amountPow2 + amount)*tangent1.y + (-2*amountPow3 + 3*amountPow2)*v2.y + (amountPow3 - amountPow2)*tangent2.y; + result.z = (2*amountPow3 - 3*amountPow2 + 1)*v1.z + (amountPow3 - 2*amountPow2 + amount)*tangent1.z + (-2*amountPow3 + 3*amountPow2)*v2.z + (amountPow3 - amountPow2)*tangent2.z; + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal) +{ + Vector3 result = { 0 }; + + // I is the original vector + // N is the normal of the incident plane + // R = I - (2*N*(DotProduct[I, N])) + + float dotProduct = (v.x*normal.x + v.y*normal.y + v.z*normal.z); + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + result.z = v.z - (2.0f*normal.z)*dotProduct; + + return result; +} + +// Get min value for each pair of components +RMAPI Vector3 Vector3Min(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector3 Vector3Max(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + + return result; +} + +// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) +// NOTE: Assumes P is on the plane of the triangle +RMAPI Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) +{ + Vector3 result = { 0 }; + + Vector3 v0 = { b.x - a.x, b.y - a.y, b.z - a.z }; // Vector3Subtract(b, a) + Vector3 v1 = { c.x - a.x, c.y - a.y, c.z - a.z }; // Vector3Subtract(c, a) + Vector3 v2 = { p.x - a.x, p.y - a.y, p.z - a.z }; // Vector3Subtract(p, a) + float d00 = (v0.x*v0.x + v0.y*v0.y + v0.z*v0.z); // Vector3DotProduct(v0, v0) + float d01 = (v0.x*v1.x + v0.y*v1.y + v0.z*v1.z); // Vector3DotProduct(v0, v1) + float d11 = (v1.x*v1.x + v1.y*v1.y + v1.z*v1.z); // Vector3DotProduct(v1, v1) + float d20 = (v2.x*v0.x + v2.y*v0.y + v2.z*v0.z); // Vector3DotProduct(v2, v0) + float d21 = (v2.x*v1.x + v2.y*v1.y + v2.z*v1.z); // Vector3DotProduct(v2, v1) + + float denom = d00*d11 - d01*d01; + + result.y = (d11*d20 - d01*d21)/denom; + result.z = (d00*d21 - d01*d20)/denom; + result.x = 1.0f - (result.z + result.y); + + return result; +} + +// Projects a Vector3 from screen space into object space +// NOTE: Self-contained function, no other raymath functions are called +RMAPI Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) +{ + Vector3 result = { 0 }; + + // Calculate unprojected matrix (multiply view matrix by projection matrix) and invert it + Matrix matViewProj = { // MatrixMultiply(view, projection); + view.m0*projection.m0 + view.m1*projection.m4 + view.m2*projection.m8 + view.m3*projection.m12, + view.m0*projection.m1 + view.m1*projection.m5 + view.m2*projection.m9 + view.m3*projection.m13, + view.m0*projection.m2 + view.m1*projection.m6 + view.m2*projection.m10 + view.m3*projection.m14, + view.m0*projection.m3 + view.m1*projection.m7 + view.m2*projection.m11 + view.m3*projection.m15, + view.m4*projection.m0 + view.m5*projection.m4 + view.m6*projection.m8 + view.m7*projection.m12, + view.m4*projection.m1 + view.m5*projection.m5 + view.m6*projection.m9 + view.m7*projection.m13, + view.m4*projection.m2 + view.m5*projection.m6 + view.m6*projection.m10 + view.m7*projection.m14, + view.m4*projection.m3 + view.m5*projection.m7 + view.m6*projection.m11 + view.m7*projection.m15, + view.m8*projection.m0 + view.m9*projection.m4 + view.m10*projection.m8 + view.m11*projection.m12, + view.m8*projection.m1 + view.m9*projection.m5 + view.m10*projection.m9 + view.m11*projection.m13, + view.m8*projection.m2 + view.m9*projection.m6 + view.m10*projection.m10 + view.m11*projection.m14, + view.m8*projection.m3 + view.m9*projection.m7 + view.m10*projection.m11 + view.m11*projection.m15, + view.m12*projection.m0 + view.m13*projection.m4 + view.m14*projection.m8 + view.m15*projection.m12, + view.m12*projection.m1 + view.m13*projection.m5 + view.m14*projection.m9 + view.m15*projection.m13, + view.m12*projection.m2 + view.m13*projection.m6 + view.m14*projection.m10 + view.m15*projection.m14, + view.m12*projection.m3 + view.m13*projection.m7 + view.m14*projection.m11 + view.m15*projection.m15 }; + + // Calculate inverted matrix -> MatrixInvert(matViewProj); + // Cache the matrix values (speed optimization) + float a00 = matViewProj.m0, a01 = matViewProj.m1, a02 = matViewProj.m2, a03 = matViewProj.m3; + float a10 = matViewProj.m4, a11 = matViewProj.m5, a12 = matViewProj.m6, a13 = matViewProj.m7; + float a20 = matViewProj.m8, a21 = matViewProj.m9, a22 = matViewProj.m10, a23 = matViewProj.m11; + float a30 = matViewProj.m12, a31 = matViewProj.m13, a32 = matViewProj.m14, a33 = matViewProj.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + Matrix matViewProjInv = { + (a11*b11 - a12*b10 + a13*b09)*invDet, + (-a01*b11 + a02*b10 - a03*b09)*invDet, + (a31*b05 - a32*b04 + a33*b03)*invDet, + (-a21*b05 + a22*b04 - a23*b03)*invDet, + (-a10*b11 + a12*b08 - a13*b07)*invDet, + (a00*b11 - a02*b08 + a03*b07)*invDet, + (-a30*b05 + a32*b02 - a33*b01)*invDet, + (a20*b05 - a22*b02 + a23*b01)*invDet, + (a10*b10 - a11*b08 + a13*b06)*invDet, + (-a00*b10 + a01*b08 - a03*b06)*invDet, + (a30*b04 - a31*b02 + a33*b00)*invDet, + (-a20*b04 + a21*b02 - a23*b00)*invDet, + (-a10*b09 + a11*b07 - a12*b06)*invDet, + (a00*b09 - a01*b07 + a02*b06)*invDet, + (-a30*b03 + a31*b01 - a32*b00)*invDet, + (a20*b03 - a21*b01 + a22*b00)*invDet }; + + // Create quaternion from source point + Quaternion quat = { source.x, source.y, source.z, 1.0f }; + + // Multiply quat point by unprojected matrix + Quaternion qtransformed = { // QuaternionTransform(quat, matViewProjInv) + matViewProjInv.m0*quat.x + matViewProjInv.m4*quat.y + matViewProjInv.m8*quat.z + matViewProjInv.m12*quat.w, + matViewProjInv.m1*quat.x + matViewProjInv.m5*quat.y + matViewProjInv.m9*quat.z + matViewProjInv.m13*quat.w, + matViewProjInv.m2*quat.x + matViewProjInv.m6*quat.y + matViewProjInv.m10*quat.z + matViewProjInv.m14*quat.w, + matViewProjInv.m3*quat.x + matViewProjInv.m7*quat.y + matViewProjInv.m11*quat.z + matViewProjInv.m15*quat.w }; + + // Normalized world points in vectors + result.x = qtransformed.x/qtransformed.w; + result.y = qtransformed.y/qtransformed.w; + result.z = qtransformed.z/qtransformed.w; + + return result; +} + +// Get Vector3 as float array +RMAPI float3 Vector3ToFloatV(Vector3 v) +{ + float3 buffer = { 0 }; + + buffer.v[0] = v.x; + buffer.v[1] = v.y; + buffer.v[2] = v.z; + + return buffer; +} + +// Invert the given vector +RMAPI Vector3 Vector3Invert(Vector3 v) +{ + Vector3 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z }; + + return result; +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +RMAPI Vector3 Vector3Clamp(Vector3 v, Vector3 min, Vector3 max) +{ + Vector3 result = { 0 }; + + result.x = fminf(max.x, fmaxf(min.x, v.x)); + result.y = fminf(max.y, fmaxf(min.y, v.y)); + result.z = fminf(max.z, fmaxf(min.z, v.z)); + + return result; +} + +// Clamp the magnitude of the vector between two values +RMAPI Vector3 Vector3ClampValue(Vector3 v, float min, float max) +{ + Vector3 result = v; + + float length = (v.x*v.x) + (v.y*v.y) + (v.z*v.z); + if (length > 0.0f) + { + length = sqrtf(length); + + float scale = 1; // By default, 1 as the neutral element + if (length < min) scale = min/length; + else if (length > max) scale = max/length; + + result.x = v.x*scale; + result.y = v.y*scale; + result.z = v.z*scale; + } + + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector3Equals(Vector3 p, Vector3 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))); + + return result; +} + +// Compute the direction of a refracted ray +// v: normalized direction of the incoming ray +// n: normalized normal vector of the interface of two optical media +// r: ratio of the refractive index of the medium from where the ray comes +// to the refractive index of the medium on the other side of the surface +RMAPI Vector3 Vector3Refract(Vector3 v, Vector3 n, float r) +{ + Vector3 result = { 0 }; + + float dot = v.x*n.x + v.y*n.y + v.z*n.z; + float d = 1.0f - r*r*(1.0f - dot*dot); + + if (d >= 0.0f) + { + d = sqrtf(d); + v.x = r*v.x - (r*dot + d)*n.x; + v.y = r*v.y - (r*dot + d)*n.y; + v.z = r*v.z - (r*dot + d)*n.z; + + result = v; + } + + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector4 math +//---------------------------------------------------------------------------------- +// Get vector zero +RMAPI Vector4 Vector4Zero(void) +{ + Vector4 result = { 0.0f, 0.0f, 0.0f, 0.0f }; + return result; +} + +// Get vector one +RMAPI Vector4 Vector4One(void) +{ + Vector4 result = { 1.0f, 1.0f, 1.0f, 1.0f }; + return result; +} + +// Add two vectors +RMAPI Vector4 Vector4Add(Vector4 v1, Vector4 v2) +{ + Vector4 result = { + v1.x + v2.x, + v1.y + v2.y, + v1.z + v2.z, + v1.w + v2.w + }; + return result; +} + +// Add value to vector components +RMAPI Vector4 Vector4AddValue(Vector4 v, float add) +{ + Vector4 result = { + v.x + add, + v.y + add, + v.z + add, + v.w + add + }; + return result; +} + +// Substract vectors +RMAPI Vector4 Vector4Subtract(Vector4 v1, Vector4 v2) +{ + Vector4 result = { + v1.x - v2.x, + v1.y - v2.y, + v1.z - v2.z, + v1.w - v2.w + }; + return result; +} + +// Substract value from vector components +RMAPI Vector4 Vector4SubtractValue(Vector4 v, float add) +{ + Vector4 result = { + v.x - add, + v.y - add, + v.z - add, + v.w - add + }; + return result; +} + +// Vector length +RMAPI float Vector4Length(Vector4 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); + return result; +} + +// Vector square length +RMAPI float Vector4LengthSqr(Vector4 v) +{ + float result = (v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w); + return result; +} + +// Vectors dot product +RMAPI float Vector4DotProduct(Vector4 v1, Vector4 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w); + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector4Distance(Vector4 v1, Vector4 v2) +{ + float result = sqrtf( + (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w)); + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector4DistanceSqr(Vector4 v1, Vector4 v2) +{ + float result = + (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w); + + return result; +} + +// Scale vector components by value (multiply) +RMAPI Vector4 Vector4Scale(Vector4 v, float scale) +{ + Vector4 result = { v.x*scale, v.y*scale, v.z*scale, v.w*scale }; + return result; +} + +// Multiply vector by vector +RMAPI Vector4 Vector4Multiply(Vector4 v1, Vector4 v2) +{ + Vector4 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z, v1.w*v2.w }; + return result; +} + +// Negate vector +RMAPI Vector4 Vector4Negate(Vector4 v) +{ + Vector4 result = { -v.x, -v.y, -v.z, -v.w }; + return result; +} + +// Divide vector by vector +RMAPI Vector4 Vector4Divide(Vector4 v1, Vector4 v2) +{ + Vector4 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z, v1.w/v2.w }; + return result; +} + +// Normalize provided vector +RMAPI Vector4 Vector4Normalize(Vector4 v) +{ + Vector4 result = { 0 }; + float length = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); + + if (length > 0) + { + float ilength = 1.0f/length; + result.x = v.x*ilength; + result.y = v.y*ilength; + result.z = v.z*ilength; + result.w = v.w*ilength; + } + + return result; +} + +// Get min value for each pair of components +RMAPI Vector4 Vector4Min(Vector4 v1, Vector4 v2) +{ + Vector4 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + result.w = fminf(v1.w, v2.w); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector4 Vector4Max(Vector4 v1, Vector4 v2) +{ + Vector4 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + result.w = fmaxf(v1.w, v2.w); + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector4 Vector4Lerp(Vector4 v1, Vector4 v2, float amount) +{ + Vector4 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + result.w = v1.w + amount*(v2.w - v1.w); + + return result; +} + +// Move Vector towards target +RMAPI Vector4 Vector4MoveTowards(Vector4 v, Vector4 target, float maxDistance) +{ + Vector4 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float dz = target.z - v.z; + float dw = target.w - v.w; + float value = (dx*dx) + (dy*dy) + (dz*dz) + (dw*dw); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + result.z = v.z + dz/dist*maxDistance; + result.w = v.w + dw/dist*maxDistance; + + return result; +} + +// Invert the given vector +RMAPI Vector4 Vector4Invert(Vector4 v) +{ + Vector4 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z, 1.0f/v.w }; + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector4Equals(Vector4 p, Vector4 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w))))); + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix math +//---------------------------------------------------------------------------------- + +// Compute matrix determinant +RMAPI float MatrixDeterminant(Matrix mat) +{ + float result = 0.0f; +/* + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + // NOTE: It takes 72 multiplication to calculate 4x4 matrix determinant + result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + + a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + + a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + + a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + + a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + + a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; +*/ + // Using Laplace expansion (https://en.wikipedia.org/wiki/Laplace_expansion), + // previous operation can be simplified to 40 multiplications, decreasing matrix + // size from 4x4 to 2x2 using minors + + // Cache the matrix values (speed optimization) + float m0 = mat.m0, m1 = mat.m1, m2 = mat.m2, m3 = mat.m3; + float m4 = mat.m4, m5 = mat.m5, m6 = mat.m6, m7 = mat.m7; + float m8 = mat.m8, m9 = mat.m9, m10 = mat.m10, m11 = mat.m11; + float m12 = mat.m12, m13 = mat.m13, m14 = mat.m14, m15 = mat.m15; + + result = (m0*((m5*(m10*m15 - m11*m14) - m9*(m6*m15 - m7*m14) + m13*(m6*m11 - m7*m10))) - + m4*((m1*(m10*m15 - m11*m14) - m9*(m2*m15 - m3*m14) + m13*(m2*m11 - m3*m10))) + + m8*((m1*(m6*m15 - m7*m14) - m5*(m2*m15 - m3*m14) + m13*(m2*m7 - m3*m6))) - + m12*((m1*(m6*m11 - m7*m10) - m5*(m2*m11 - m3*m10) + m9*(m2*m7 - m3*m6)))); + + return result; +} + +// Get the trace of the matrix (sum of the values along the diagonal) +RMAPI float MatrixTrace(Matrix mat) +{ + float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); + + return result; +} + +// Transposes provided matrix +RMAPI Matrix MatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +RMAPI Matrix MatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + +// Get identity matrix +RMAPI Matrix MatrixIdentity(void) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Add two matrices +RMAPI Matrix MatrixAdd(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 + right.m0; + result.m1 = left.m1 + right.m1; + result.m2 = left.m2 + right.m2; + result.m3 = left.m3 + right.m3; + result.m4 = left.m4 + right.m4; + result.m5 = left.m5 + right.m5; + result.m6 = left.m6 + right.m6; + result.m7 = left.m7 + right.m7; + result.m8 = left.m8 + right.m8; + result.m9 = left.m9 + right.m9; + result.m10 = left.m10 + right.m10; + result.m11 = left.m11 + right.m11; + result.m12 = left.m12 + right.m12; + result.m13 = left.m13 + right.m13; + result.m14 = left.m14 + right.m14; + result.m15 = left.m15 + right.m15; + + return result; +} + +// Subtract two matrices (left - right) +RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 - right.m0; + result.m1 = left.m1 - right.m1; + result.m2 = left.m2 - right.m2; + result.m3 = left.m3 - right.m3; + result.m4 = left.m4 - right.m4; + result.m5 = left.m5 - right.m5; + result.m6 = left.m6 - right.m6; + result.m7 = left.m7 - right.m7; + result.m8 = left.m8 - right.m8; + result.m9 = left.m9 - right.m9; + result.m10 = left.m10 - right.m10; + result.m11 = left.m11 - right.m11; + result.m12 = left.m12 - right.m12; + result.m13 = left.m13 - right.m13; + result.m14 = left.m14 - right.m14; + result.m15 = left.m15 - right.m15; + + return result; +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + +#if defined(RAYMATH_SSE_ENABLED) + // Load left side and right side + __m128 c0 = _mm_set_ps(right.m12, right.m8, right.m4, right.m0); + __m128 c1 = _mm_set_ps(right.m13, right.m9, right.m5, right.m1); + __m128 c2 = _mm_set_ps(right.m14, right.m10, right.m6, right.m2); + __m128 c3 = _mm_set_ps(right.m15, right.m11, right.m7, right.m3); + + // Transpose so c0..c3 become *rows* of the right matrix in semantic order + _MM_TRANSPOSE4_PS(c0, c1, c2, c3); + + float tmp[4] = { 0 }; + __m128 row; + + // Row 0 of result: [m0, m1, m2, m3] + row = _mm_mul_ps(_mm_set1_ps(left.m0), c0); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m1), c1)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m2), c2)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m3), c3)); + _mm_storeu_ps(tmp, row); + result.m0 = tmp[0]; + result.m1 = tmp[1]; + result.m2 = tmp[2]; + result.m3 = tmp[3]; + + // Row 1 of result: [m4, m5, m6, m7] + row = _mm_mul_ps(_mm_set1_ps(left.m4), c0); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m5), c1)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m6), c2)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m7), c3)); + _mm_storeu_ps(tmp, row); + result.m4 = tmp[0]; + result.m5 = tmp[1]; + result.m6 = tmp[2]; + result.m7 = tmp[3]; + + // Row 2 of result: [m8, m9, m10, m11] + row = _mm_mul_ps(_mm_set1_ps(left.m8), c0); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m9), c1)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m10), c2)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m11), c3)); + _mm_storeu_ps(tmp, row); + result.m8 = tmp[0]; + result.m9 = tmp[1]; + result.m10 = tmp[2]; + result.m11 = tmp[3]; + + // Row 3 of result: [m12, m13, m14, m15] + row = _mm_mul_ps(_mm_set1_ps(left.m12), c0); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m13), c1)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m14), c2)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m15), c3)); + _mm_storeu_ps(tmp, row); + result.m12 = tmp[0]; + result.m13 = tmp[1]; + result.m14 = tmp[2]; + result.m15 = tmp[3]; +#else + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; +#endif + + return result; +} + +// Multiply matrix components by value +RMAPI Matrix MatrixMultiplyValue(Matrix left, float value) +{ + Matrix result = { + left.m0*value, left.m4*value, left.m8*value, left.m12*value, + left.m1*value, left.m5*value, left.m9*value, left.m13*value, + left.m2*value, left.m6*value, left.m10*value, left.m14*value, + left.m3*value, left.m7*value, left.m11*value, left.m15*value + }; + + return result; +} + +// Get translation matrix +RMAPI Matrix MatrixTranslate(float x, float y, float z) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Create rotation matrix from axis and angle +// NOTE: Angle should be provided in radians +RMAPI Matrix MatrixRotate(Vector3 axis, float angle) +{ + Matrix result = { 0 }; + + float x = axis.x, y = axis.y, z = axis.z; + + float lengthSquared = x*x + y*y + z*z; + + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float ilength = 1.0f/sqrtf(lengthSquared); + x *= ilength; + y *= ilength; + z *= ilength; + } + + float sinres = sinf(angle); + float cosres = cosf(angle); + float t = 1.0f - cosres; + + result.m0 = x*x*t + cosres; + result.m1 = y*x*t + z*sinres; + result.m2 = z*x*t - y*sinres; + result.m3 = 0.0f; + + result.m4 = x*y*t - z*sinres; + result.m5 = y*y*t + cosres; + result.m6 = z*y*t + x*sinres; + result.m7 = 0.0f; + + result.m8 = x*z*t + y*sinres; + result.m9 = y*z*t - x*sinres; + result.m10 = z*z*t + cosres; + result.m11 = 0.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = 0.0f; + result.m15 = 1.0f; + + return result; +} + +// Get x-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateX(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m5 = cosres; + result.m6 = sinres; + result.m9 = -sinres; + result.m10 = cosres; + + return result; +} + +// Get y-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateY(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m2 = -sinres; + result.m8 = sinres; + result.m10 = cosres; + + return result; +} + +// Get z-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateZ(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m1 = sinres; + result.m4 = -sinres; + result.m5 = cosres; + + return result; +} + + +// Get xyz-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateXYZ(Vector3 angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosz = cosf(-angle.z); + float sinz = sinf(-angle.z); + float cosy = cosf(-angle.y); + float siny = sinf(-angle.y); + float cosx = cosf(-angle.x); + float sinx = sinf(-angle.x); + + result.m0 = cosz*cosy; + result.m1 = (cosz*siny*sinx) - (sinz*cosx); + result.m2 = (cosz*siny*cosx) + (sinz*sinx); + + result.m4 = sinz*cosy; + result.m5 = (sinz*siny*sinx) + (cosz*cosx); + result.m6 = (sinz*siny*cosx) - (cosz*sinx); + + result.m8 = -siny; + result.m9 = cosy*sinx; + result.m10= cosy*cosx; + + return result; +} + +// Get zyx-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateZYX(Vector3 angle) +{ + Matrix result = { 0 }; + + float cz = cosf(angle.z); + float sz = sinf(angle.z); + float cy = cosf(angle.y); + float sy = sinf(angle.y); + float cx = cosf(angle.x); + float sx = sinf(angle.x); + + result.m0 = cz*cy; + result.m4 = cz*sy*sx - cx*sz; + result.m8 = sz*sx + cz*cx*sy; + result.m12 = 0; + + result.m1 = cy*sz; + result.m5 = cz*cx + sz*sy*sx; + result.m9 = cx*sz*sy - cz*sx; + result.m13 = 0; + + result.m2 = -sy; + result.m6 = cy*sx; + result.m10 = cy*cx; + result.m14 = 0; + + result.m3 = 0; + result.m7 = 0; + result.m11 = 0; + result.m15 = 1; + + return result; +} + +// Get scaling matrix +RMAPI Matrix MatrixScale(float x, float y, float z) +{ + Matrix result = { x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Get perspective projection matrix +RMAPI Matrix MatrixFrustum(double left, double right, double bottom, double top, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = ((float)nearPlane*2.0f)/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + + result.m4 = 0.0f; + result.m5 = ((float)nearPlane*2.0f)/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)farPlane + (float)nearPlane)/fn; + result.m11 = -1.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; + result.m15 = 0.0f; + + return result; +} + +// Get perspective projection matrix +// NOTE: Fovy angle must be provided in radians +RMAPI Matrix MatrixPerspective(double fovY, double aspect, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + double top = nearPlane*tan(fovY*0.5); + double bottom = -top; + double right = top*aspect; + double left = -right; + + // MatrixFrustum(-right, right, -top, top, near, far); + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = ((float)nearPlane*2.0f)/rl; + result.m5 = ((float)nearPlane*2.0f)/tb; + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)farPlane + (float)nearPlane)/fn; + result.m11 = -1.0f; + result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; + + return result; +} + +// Get orthographic projection matrix +RMAPI Matrix MatrixOrtho(double left, double right, double bottom, double top, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = 2.0f/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + result.m4 = 0.0f; + result.m5 = 2.0f/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + result.m8 = 0.0f; + result.m9 = 0.0f; + result.m10 = -2.0f/fn; + result.m11 = 0.0f; + result.m12 = -((float)left + (float)right)/rl; + result.m13 = -((float)top + (float)bottom)/tb; + result.m14 = -((float)farPlane + (float)nearPlane)/fn; + result.m15 = 1.0f; + + return result; +} + +// Get camera look-at matrix (view matrix) +RMAPI Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) +{ + Matrix result = { 0 }; + + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Subtract(eye, target) + Vector3 vz = { eye.x - target.x, eye.y - target.y, eye.z - target.z }; + + // Vector3Normalize(vz) + Vector3 v = vz; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vz.x *= ilength; + vz.y *= ilength; + vz.z *= ilength; + + // Vector3CrossProduct(up, vz) + Vector3 vx = { up.y*vz.z - up.z*vz.y, up.z*vz.x - up.x*vz.z, up.x*vz.y - up.y*vz.x }; + + // Vector3Normalize(x) + v = vx; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vx.x *= ilength; + vx.y *= ilength; + vx.z *= ilength; + + // Vector3CrossProduct(vz, vx) + Vector3 vy = { vz.y*vx.z - vz.z*vx.y, vz.z*vx.x - vz.x*vx.z, vz.x*vx.y - vz.y*vx.x }; + + result.m0 = vx.x; + result.m1 = vy.x; + result.m2 = vz.x; + result.m3 = 0.0f; + result.m4 = vx.y; + result.m5 = vy.y; + result.m6 = vz.y; + result.m7 = 0.0f; + result.m8 = vx.z; + result.m9 = vy.z; + result.m10 = vz.z; + result.m11 = 0.0f; + result.m12 = -(vx.x*eye.x + vx.y*eye.y + vx.z*eye.z); // Vector3DotProduct(vx, eye) + result.m13 = -(vy.x*eye.x + vy.y*eye.y + vy.z*eye.z); // Vector3DotProduct(vy, eye) + result.m14 = -(vz.x*eye.x + vz.y*eye.y + vz.z*eye.z); // Vector3DotProduct(vz, eye) + result.m15 = 1.0f; + + return result; +} + +// Get float array of matrix data +RMAPI float16 MatrixToFloatV(Matrix mat) +{ + float16 result = { 0 }; + + result.v[0] = mat.m0; + result.v[1] = mat.m1; + result.v[2] = mat.m2; + result.v[3] = mat.m3; + result.v[4] = mat.m4; + result.v[5] = mat.m5; + result.v[6] = mat.m6; + result.v[7] = mat.m7; + result.v[8] = mat.m8; + result.v[9] = mat.m9; + result.v[10] = mat.m10; + result.v[11] = mat.m11; + result.v[12] = mat.m12; + result.v[13] = mat.m13; + result.v[14] = mat.m14; + result.v[15] = mat.m15; + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Quaternion math +//---------------------------------------------------------------------------------- + +// Add two quaternions +RMAPI Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; + + return result; +} + +// Add quaternion and float value +RMAPI Quaternion QuaternionAddValue(Quaternion q, float add) +{ + Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; + + return result; +} + +// Subtract two quaternions +RMAPI Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; + + return result; +} + +// Subtract quaternion and float value +RMAPI Quaternion QuaternionSubtractValue(Quaternion q, float sub) +{ + Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; + + return result; +} + +// Get identity quaternion +RMAPI Quaternion QuaternionIdentity(void) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Computes the length of a quaternion +RMAPI float QuaternionLength(Quaternion q) +{ + float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + + return result; +} + +// Normalize provided quaternion +RMAPI Quaternion QuaternionNormalize(Quaternion q) +{ + Quaternion result = { 0 }; + + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Invert provided quaternion +RMAPI Quaternion QuaternionInvert(Quaternion q) +{ + Quaternion result = q; + + float lengthSq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w; + + if (lengthSq != 0.0f) + { + float invLength = 1.0f/lengthSq; + + result.x *= -invLength; + result.y *= -invLength; + result.z *= -invLength; + result.w *= invLength; + } + + return result; +} + +// Calculate two quaternion multiplication +RMAPI Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) +{ + Quaternion result = { 0 }; + + float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; + float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; + + result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; + result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; + result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; + result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; + + return result; +} + +// Scale quaternion by float value +RMAPI Quaternion QuaternionScale(Quaternion q, float mul) +{ + Quaternion result = { 0 }; + + result.x = q.x*mul; + result.y = q.y*mul; + result.z = q.z*mul; + result.w = q.w*mul; + + return result; +} + +// Divide two quaternions +RMAPI Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) +{ + Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; + + return result; +} + +// Calculate linear interpolation between two quaternions +RMAPI Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + return result; +} + +// Calculate slerp-optimized interpolation between two quaternions +RMAPI Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + // QuaternionLerp(q1, q2, amount) + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + // QuaternionNormalize(q); + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Calculates spherical linear interpolation between two quaternions +RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; + + if (cosHalfTheta < 0) + { + q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; + cosHalfTheta = -cosHalfTheta; + } + + if (fabsf(cosHalfTheta) >= 1.0f) result = q1; + else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); + else + { + float halfTheta = acosf(cosHalfTheta); + float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); + + if (fabsf(sinHalfTheta) < EPSILON) + { + result.x = (q1.x*0.5f + q2.x*0.5f); + result.y = (q1.y*0.5f + q2.y*0.5f); + result.z = (q1.z*0.5f + q2.z*0.5f); + result.w = (q1.w*0.5f + q2.w*0.5f); + } + else + { + float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; + float ratioB = sinf(amount*halfTheta)/sinHalfTheta; + + result.x = (q1.x*ratioA + q2.x*ratioB); + result.y = (q1.y*ratioA + q2.y*ratioB); + result.z = (q1.z*ratioA + q2.z*ratioB); + result.w = (q1.w*ratioA + q2.w*ratioB); + } + } + + return result; +} + +// Calculate quaternion cubic spline interpolation using Cubic Hermite Spline algorithm +// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic +RMAPI Quaternion QuaternionCubicHermiteSpline(Quaternion q1, Quaternion outTangent1, Quaternion q2, Quaternion inTangent2, float t) +{ + float t2 = t*t; + float t3 = t2*t; + float h00 = 2*t3 - 3*t2 + 1; + float h10 = t3 - 2*t2 + t; + float h01 = -2*t3 + 3*t2; + float h11 = t3 - t2; + + Quaternion p0 = QuaternionScale(q1, h00); + Quaternion m0 = QuaternionScale(outTangent1, h10); + Quaternion p1 = QuaternionScale(q2, h01); + Quaternion m1 = QuaternionScale(inTangent2, h11); + + Quaternion result = { 0 }; + + result = QuaternionAdd(p0, m0); + result = QuaternionAdd(result, p1); + result = QuaternionAdd(result, m1); + result = QuaternionNormalize(result); + + return result; +} + +// Calculate quaternion based on the rotation from one vector to another +RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) +{ + Quaternion result = { 0 }; + + float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) + Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) + + result.x = cross.x; + result.y = cross.y; + result.z = cross.z; + result.w = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z + cos2Theta*cos2Theta) + cos2Theta; + + // QuaternionNormalize(q); + // NOTE: Normalize to essentially nlerp the original and identity to 0.5 + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Get a quaternion for a given rotation matrix +RMAPI Quaternion QuaternionFromMatrix(Matrix mat) +{ + Quaternion result = { 0 }; + + float fourWSquaredMinus1 = mat.m0 + mat.m5 + mat.m10; + float fourXSquaredMinus1 = mat.m0 - mat.m5 - mat.m10; + float fourYSquaredMinus1 = mat.m5 - mat.m0 - mat.m10; + float fourZSquaredMinus1 = mat.m10 - mat.m0 - mat.m5; + + int biggestIndex = 0; + float fourBiggestSquaredMinus1 = fourWSquaredMinus1; + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourXSquaredMinus1; + biggestIndex = 1; + } + + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourYSquaredMinus1; + biggestIndex = 2; + } + + if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourZSquaredMinus1; + biggestIndex = 3; + } + + float biggestVal = sqrtf(fourBiggestSquaredMinus1 + 1.0f)*0.5f; + float mult = 0.25f/biggestVal; + + switch (biggestIndex) + { + case 0: + result.w = biggestVal; + result.x = (mat.m6 - mat.m9)*mult; + result.y = (mat.m8 - mat.m2)*mult; + result.z = (mat.m1 - mat.m4)*mult; + break; + case 1: + result.x = biggestVal; + result.w = (mat.m6 - mat.m9)*mult; + result.y = (mat.m1 + mat.m4)*mult; + result.z = (mat.m8 + mat.m2)*mult; + break; + case 2: + result.y = biggestVal; + result.w = (mat.m8 - mat.m2)*mult; + result.x = (mat.m1 + mat.m4)*mult; + result.z = (mat.m6 + mat.m9)*mult; + break; + case 3: + result.z = biggestVal; + result.w = (mat.m1 - mat.m4)*mult; + result.x = (mat.m8 + mat.m2)*mult; + result.y = (mat.m6 + mat.m9)*mult; + break; + } + + return result; +} + +// Get a matrix for a given quaternion +RMAPI Matrix QuaternionToMatrix(Quaternion q) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float a2 = q.x*q.x; + float b2 = q.y*q.y; + float c2 = q.z*q.z; + float ac = q.x*q.z; + float ab = q.x*q.y; + float bc = q.y*q.z; + float ad = q.w*q.x; + float bd = q.w*q.y; + float cd = q.w*q.z; + + result.m0 = 1 - 2*(b2 + c2); + result.m1 = 2*(ab + cd); + result.m2 = 2*(ac - bd); + + result.m4 = 2*(ab - cd); + result.m5 = 1 - 2*(a2 + c2); + result.m6 = 2*(bc + ad); + + result.m8 = 2*(ac + bd); + result.m9 = 2*(bc - ad); + result.m10 = 1 - 2*(a2 + b2); + + return result; +} + +// Get rotation quaternion for an angle and axis +// NOTE: Angle must be provided in radians +RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + float length = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + + if (length != 0.0f) + { + angle *= 0.5f; + + // Vector3Normalize(axis) + float ilength = 1.0f/length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + float sinres = sinf(angle); + float cosres = cosf(angle); + + result.x = axis.x*sinres; + result.y = axis.y*sinres; + result.z = axis.z*sinres; + result.w = cosres; + + // QuaternionNormalize(q); + Quaternion q = result; + length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + } + + return result; +} + +// Get the rotation angle and axis for a given quaternion +RMAPI void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) +{ + if (fabsf(q.w) > 1.0f) + { + // QuaternionNormalize(q); + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + q.x = q.x*ilength; + q.y = q.y*ilength; + q.z = q.z*ilength; + q.w = q.w*ilength; + } + + Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; + float resAngle = 2.0f*acosf(q.w); + float den = sqrtf(1.0f - q.w*q.w); + + if (den > EPSILON) + { + resAxis.x = q.x/den; + resAxis.y = q.y/den; + resAxis.z = q.z/den; + } + else + { + // This occurs when the angle is zero + // Not a problem, set an arbitrary normalized axis + resAxis.x = 1.0f; + } + + *outAxis = resAxis; + *outAngle = resAngle; +} + +// Get the quaternion equivalent to Euler angles +// NOTE: Rotation order is ZYX +RMAPI Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) +{ + Quaternion result = { 0 }; + + float x0 = cosf(pitch*0.5f); + float x1 = sinf(pitch*0.5f); + float y0 = cosf(yaw*0.5f); + float y1 = sinf(yaw*0.5f); + float z0 = cosf(roll*0.5f); + float z1 = sinf(roll*0.5f); + + result.x = x1*y0*z0 - x0*y1*z1; + result.y = x0*y1*z0 + x1*y0*z1; + result.z = x0*y0*z1 - x1*y1*z0; + result.w = x0*y0*z0 + x1*y1*z1; + + return result; +} + +// Get the Euler angles equivalent to quaternion (roll, pitch, yaw) +// NOTE: Angles are returned in a Vector3 struct in radians +RMAPI Vector3 QuaternionToEuler(Quaternion q) +{ + Vector3 result = { 0 }; + + // Roll (x-axis rotation) + float x0 = 2.0f*(q.w*q.x + q.y*q.z); + float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); + result.x = atan2f(x0, x1); + + // Pitch (y-axis rotation) + float y0 = 2.0f*(q.w*q.y - q.z*q.x); + y0 = y0 > 1.0f ? 1.0f : y0; + y0 = y0 < -1.0f ? -1.0f : y0; + result.y = asinf(y0); + + // Yaw (z-axis rotation) + float z0 = 2.0f*(q.w*q.z + q.x*q.y); + float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); + result.z = atan2f(z0, z1); + + return result; +} + +// Transform a quaternion given a transformation matrix +RMAPI Quaternion QuaternionTransform(Quaternion q, Matrix mat) +{ + Quaternion result = { 0 }; + + result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; + result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; + result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; + result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; + + return result; +} + +// Check whether two given quaternions are almost equal +RMAPI int QuaternionEquals(Quaternion p, Quaternion q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = (((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))) || + (((fabsf(p.x + q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y + q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z + q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w + q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))); + + return result; +} + +// Compose a transformation matrix from rotational, translational and scaling components +RMAPI Matrix MatrixCompose(Vector3 translation, Quaternion rotation, Vector3 scale) +{ + // Initialize vectors + Vector3 right = { 1.0f, 0.0f, 0.0f }; + Vector3 up = { 0.0f, 1.0f, 0.0f }; + Vector3 forward = { 0.0f, 0.0f, 1.0f }; + + // Scale vectors + right.x *= scale.x; + right.y *= scale.x; + right.z *= scale.x; + + up.x *= scale.y; + up.y *= scale.y; + up.z *= scale.y; + + forward.x *= scale.z; + forward.y *= scale.z; + forward.z *= scale.z; + + // Rotate vectors + right.x = right.x*(rotation.x*rotation.x + rotation.w*rotation.w - rotation.y*rotation.y - rotation.z*rotation.z) + right.y*(2*rotation.x*rotation.y - 2*rotation.w*rotation.z) + right.z*(2*rotation.x*rotation.z + 2*rotation.w*rotation.y); + right.y = right.x*(2*rotation.w*rotation.z + 2*rotation.x*rotation.y) + right.y*(rotation.w*rotation.w - rotation.x*rotation.x + rotation.y*rotation.y - rotation.z*rotation.z) + right.z*(-2*rotation.w*rotation.x + 2*rotation.y*rotation.z); + right.z = right.x*(-2*rotation.w*rotation.y + 2*rotation.x*rotation.z) + right.y*(2*rotation.w*rotation.x + 2*rotation.y*rotation.z)+ right.z*(rotation.w*rotation.w - rotation.x*rotation.x - rotation.y*rotation.y + rotation.z*rotation.z); + + up.x = up.x*(rotation.x*rotation.x + rotation.w*rotation.w - rotation.y*rotation.y - rotation.z*rotation.z) + up.y*(2*rotation.x*rotation.y - 2*rotation.w*rotation.z) + up.z*(2*rotation.x*rotation.z + 2*rotation.w*rotation.y); + up.y = up.x*(2*rotation.w*rotation.z + 2*rotation.x*rotation.y) + up.y*(rotation.w*rotation.w - rotation.x*rotation.x + rotation.y*rotation.y - rotation.z*rotation.z) + up.z*(-2*rotation.w*rotation.x + 2*rotation.y*rotation.z); + up.z = up.x*(-2*rotation.w*rotation.y + 2*rotation.x*rotation.z) + up.y*(2*rotation.w*rotation.x + 2*rotation.y*rotation.z)+ up.z*(rotation.w*rotation.w - rotation.x*rotation.x - rotation.y*rotation.y + rotation.z*rotation.z); + + forward.x = forward.x*(rotation.x*rotation.x + rotation.w*rotation.w - rotation.y*rotation.y - rotation.z*rotation.z) + forward.y*(2*rotation.x*rotation.y - 2*rotation.w*rotation.z) + forward.z*(2*rotation.x*rotation.z + 2*rotation.w*rotation.y); + forward.y = forward.x*(2*rotation.w*rotation.z + 2*rotation.x*rotation.y) + forward.y*(rotation.w*rotation.w - rotation.x*rotation.x + rotation.y*rotation.y - rotation.z*rotation.z) + forward.z*(-2*rotation.w*rotation.x + 2*rotation.y*rotation.z); + forward.z = forward.x*(-2*rotation.w*rotation.y + 2*rotation.x*rotation.z) + forward.y*(2*rotation.w*rotation.x + 2*rotation.y*rotation.z)+ forward.z*(rotation.w*rotation.w - rotation.x*rotation.x - rotation.y*rotation.y + rotation.z*rotation.z); + + // Set result matrix output + Matrix result = { + right.x, up.x, forward.x, translation.x, + right.y, up.y, forward.y, translation.y, + right.z, up.z, forward.z, translation.z, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + return result; +} + +// Decompose a transformation matrix into its rotational, translational and scaling components and remove shear +// TODO: This function is not following raymath conventions defined in header: NOT self-contained +RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale) +{ + float eps = (float)1e-9; + + // Extract Translation + translation->x = mat.m12; + translation->y = mat.m13; + translation->z = mat.m14; + + // Matrix Columns - Rotation will be extracted into here + Vector3 matColumns[3] = {{ mat.m0, mat.m4, mat.m8 }, + { mat.m1, mat.m5, mat.m9 }, + { mat.m2, mat.m6, mat.m10 }}; + + // Shear Parameters XY, XZ, and YZ (extract and ignored) + float shear[3] = { 0 }; + + // Normalized Scale Parameters + Vector3 scl = { 0 }; + + // Max-Normalizing helps numerical stability + float stabilizer = eps; + for (int i = 0; i < 3; i++) + { + stabilizer = fmaxf(stabilizer, fabsf(matColumns[i].x)); + stabilizer = fmaxf(stabilizer, fabsf(matColumns[i].y)); + stabilizer = fmaxf(stabilizer, fabsf(matColumns[i].z)); + } + matColumns[0] = Vector3Scale(matColumns[0], 1.0f/stabilizer); + matColumns[1] = Vector3Scale(matColumns[1], 1.0f/stabilizer); + matColumns[2] = Vector3Scale(matColumns[2], 1.0f/stabilizer); + + // X Scale + scl.x = Vector3Length(matColumns[0]); + if (scl.x > eps) matColumns[0] = Vector3Scale(matColumns[0], 1.0f/scl.x); + + // Compute XY shear and make col2 orthogonal + shear[0] = Vector3DotProduct(matColumns[0], matColumns[1]); + matColumns[1] = Vector3Subtract(matColumns[1], Vector3Scale(matColumns[0], shear[0])); + + // Y Scale + scl.y = Vector3Length(matColumns[1]); + if (scl.y > eps) + { + matColumns[1] = Vector3Scale(matColumns[1], 1.0f/scl.y); + shear[0] /= scl.y; // Correct XY shear + } + + // Compute XZ and YZ shears and make col3 orthogonal + shear[1] = Vector3DotProduct(matColumns[0], matColumns[2]); + matColumns[2] = Vector3Subtract(matColumns[2], Vector3Scale(matColumns[0], shear[1])); + shear[2] = Vector3DotProduct(matColumns[1], matColumns[2]); + matColumns[2] = Vector3Subtract(matColumns[2], Vector3Scale(matColumns[1], shear[2])); + + // Z Scale + scl.z = Vector3Length(matColumns[2]); + if (scl.z > eps) + { + matColumns[2] = Vector3Scale(matColumns[2], 1.0f/scl.z); + shear[1] /= scl.z; // Correct XZ shear + shear[2] /= scl.z; // Correct YZ shear + } + + // matColumns are now orthonormal in O(3). Now ensure its in SO(3) by enforcing det = 1 + if (Vector3DotProduct(matColumns[0], Vector3CrossProduct(matColumns[1], matColumns[2])) < 0) + { + scl = Vector3Negate(scl); + matColumns[0] = Vector3Negate(matColumns[0]); + matColumns[1] = Vector3Negate(matColumns[1]); + matColumns[2] = Vector3Negate(matColumns[2]); + } + + // Set Scale + *scale = Vector3Scale(scl, stabilizer); + + // Extract Rotation + Matrix rotationMatrix = { matColumns[0].x, matColumns[0].y, matColumns[0].z, 0, + matColumns[1].x, matColumns[1].y, matColumns[1].z, 0, + matColumns[2].x, matColumns[2].y, matColumns[2].z, 0, + 0, 0, 0, 1 }; + *rotation = QuaternionFromMatrix(rotationMatrix); +} + +#if defined(__cplusplus) && !defined(RAYMATH_DISABLE_CPP_OPERATORS) + +// Optional C++ math operators +//------------------------------------------------------------------------------- + +// Vector2 operators +static constexpr Vector2 Vector2Zeros = { 0, 0 }; +static constexpr Vector2 Vector2Ones = { 1, 1 }; +static constexpr Vector2 Vector2UnitX = { 1, 0 }; +static constexpr Vector2 Vector2UnitY = { 0, 1 }; + +inline Vector2 operator + (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Add(lhs, rhs); +} + +inline const Vector2& operator += (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Add(lhs, rhs); + return lhs; +} + +inline Vector2 operator - (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Subtract(lhs, rhs); +} + +inline const Vector2& operator -= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Subtract(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const float& lhs, const Vector2& rhs) +{ + return Vector2Scale(rhs, lhs); +} + +inline Vector2 operator * (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Multiply(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Multiply(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Matrix& rhs) +{ + return Vector2Transform(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const Matrix& rhs) +{ + lhs = Vector2Transform(lhs, rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, 1.0f/rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, 1.0f/rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Divide(lhs, rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector2& lhs, const Vector2& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y); +} + +inline bool operator != (const Vector2& lhs, const Vector2& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y); +} + +// Vector3 operators +static constexpr Vector3 Vector3Zeros = { 0, 0, 0 }; +static constexpr Vector3 Vector3Ones = { 1, 1, 1 }; +static constexpr Vector3 Vector3UnitX = { 1, 0, 0 }; +static constexpr Vector3 Vector3UnitY = { 0, 1, 0 }; +static constexpr Vector3 Vector3UnitZ = { 0, 0, 1 }; + +inline Vector3 operator + (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Add(lhs, rhs); +} + +inline const Vector3& operator += (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Add(lhs, rhs); + return lhs; +} + +inline Vector3 operator - (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Subtract(lhs, rhs); +} + +inline const Vector3& operator -= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Subtract(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const float& lhs, const Vector3& rhs) +{ + return Vector3Scale(rhs, lhs); +} + +inline Vector3 operator * (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Multiply(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Multiply(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Matrix& rhs) +{ + return Vector3Transform(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const Matrix& rhs) +{ + lhs = Vector3Transform(lhs, rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, 1.0f/rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, 1.0f/rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Divide(lhs, rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector3& lhs, const Vector3& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z); +} + +inline bool operator != (const Vector3& lhs, const Vector3& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z); +} + +// Vector4 operators +static constexpr Vector4 Vector4Zeros = { 0, 0, 0, 0 }; +static constexpr Vector4 Vector4Ones = { 1, 1, 1, 1 }; +static constexpr Vector4 Vector4UnitX = { 1, 0, 0, 0 }; +static constexpr Vector4 Vector4UnitY = { 0, 1, 0, 0 }; +static constexpr Vector4 Vector4UnitZ = { 0, 0, 1, 0 }; +static constexpr Vector4 Vector4UnitW = { 0, 0, 0, 1 }; + +inline Vector4 operator + (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Add(lhs, rhs); +} + +inline const Vector4& operator += (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Add(lhs, rhs); + return lhs; +} + +inline Vector4 operator - (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Subtract(lhs, rhs); +} + +inline const Vector4& operator -= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Subtract(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const float& lhs, const Vector4& rhs) +{ + return Vector4Scale(rhs, lhs); +} + +inline Vector4 operator * (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Multiply(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Multiply(lhs, rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, 1.0f/rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, 1.0f/rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Divide(lhs, rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector4& lhs, const Vector4& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z) && FloatEquals(lhs.w, rhs.w); +} + +inline bool operator != (const Vector4& lhs, const Vector4& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z) || !FloatEquals(lhs.w, rhs.w); +} + +// Quaternion operators +static constexpr Quaternion QuaternionZeros = { 0, 0, 0, 0 }; +static constexpr Quaternion QuaternionOnes = { 1, 1, 1, 1 }; +static constexpr Quaternion QuaternionUnitX = { 0, 0, 0, 1 }; + +inline Quaternion operator + (const Quaternion& lhs, const float& rhs) +{ + return QuaternionAddValue(lhs, rhs); +} + +inline const Quaternion& operator += (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionAddValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator - (const Quaternion& lhs, const float& rhs) +{ + return QuaternionSubtractValue(lhs, rhs); +} + +inline const Quaternion& operator -= (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionSubtractValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator * (const Quaternion& lhs, const Matrix& rhs) +{ + return QuaternionTransform(lhs, rhs); +} + +inline const Quaternion& operator *= (Quaternion& lhs, const Matrix& rhs) +{ + lhs = QuaternionTransform(lhs, rhs); + return lhs; +} + +// Matrix operators +static constexpr Matrix MatrixUnit = { 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 }; + +inline Matrix operator + (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixAdd(lhs, rhs); +} + +inline const Matrix& operator += (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixAdd(lhs, rhs); + return lhs; +} + +inline Matrix operator - (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixSubtract(lhs, rhs); +} + +inline const Matrix& operator -= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixSubtract(lhs, rhs); + return lhs; +} + +inline Matrix operator * (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixMultiply(lhs, rhs); +} + +inline const Matrix& operator *= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixMultiply(lhs, rhs); + return lhs; +} + +inline Matrix operator * (const Matrix& lhs, const float value) +{ + return MatrixMultiplyValue(lhs, value); +} + +inline const Matrix& operator *= (Matrix& lhs, const float value) +{ + lhs = MatrixMultiplyValue(lhs, value); + return lhs; +} + +//------------------------------------------------------------------------------- +#endif // C++ operators + +#endif // RAYMATH_H diff --git a/src/libraries/raylib/raylib/src/rcamera.h b/src/libraries/raylib/raylib/src/rcamera.h new file mode 100644 index 0000000..e6f5bba --- /dev/null +++ b/src/libraries/raylib/raylib/src/rcamera.h @@ -0,0 +1,562 @@ +/******************************************************************************************* +* +* rcamera - Basic camera system with support for multiple camera modes +* +* CONFIGURATION: +* #define RCAMERA_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RCAMERA_STANDALONE +* If defined, the library can be used as standalone as a camera system but some +* functions must be redefined to manage inputs accordingly. +* +* CONTRIBUTORS: +* Ramon Santamaria: Supervision, review, update and maintenance +* Christoph Wagner: Complete redesign, using raymath (2022) +* Marc Palau: Initial implementation (2014) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2022-2026 Christoph Wagner (@Crydsch) and Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RCAMERA_H +#define RCAMERA_H + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Function specifiers definition + +// Function specifiers in case library is build/used as a shared library (Windows) +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +#if defined(_WIN32) + #if defined(BUILD_LIBTYPE_SHARED) + #if defined(__TINYC__) + #define __declspec(x) __attribute__((x)) + #endif + #define RLAPI __declspec(dllexport) // Building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // Using the library as a Win32 shared library (.dll) + #endif +#endif + +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +#if defined(RCAMERA_STANDALONE) + #define CAMERA_CULL_DISTANCE_NEAR 0.05 + #define CAMERA_CULL_DISTANCE_FAR 4000.0 +#else + #define CAMERA_CULL_DISTANCE_NEAR RL_CULL_DISTANCE_NEAR + #define CAMERA_CULL_DISTANCE_FAR RL_CULL_DISTANCE_FAR +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Below types are required for standalone usage +//---------------------------------------------------------------------------------- +#if defined(RCAMERA_STANDALONE) + // Vector2, 2 components + typedef struct Vector2 { + float x; // Vector x component + float y; // Vector y component + } Vector2; + + // Vector3, 3 components + typedef struct Vector3 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component + } Vector3; + + // Matrix, 4x4 components, column major, OpenGL style, right-handed + typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) + } Matrix; + + // Camera type, defines a camera position/orientation in 3d space + typedef struct Camera3D { + Vector3 position; // Camera position + Vector3 target; // Camera target it looks-at + Vector3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view aperture in Y (degrees) in perspective, used as near plane width in orthographic + int projection; // Camera projection type: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC + } Camera3D; + + typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + + // Camera projection + typedef enum { + CAMERA_PERSPECTIVE = 0, // Perspective projection + CAMERA_ORTHOGRAPHIC // Orthographic projection + } CameraProjection; + + // Camera system modes + typedef enum { + CAMERA_CUSTOM = 0, // Camera custom, controlled by user (UpdateCamera() does nothing) + CAMERA_FREE, // Camera free mode + CAMERA_ORBITAL, // Camera orbital, around target, zoom supported + CAMERA_FIRST_PERSON, // Camera first person + CAMERA_THIRD_PERSON // Camera third person + } CameraMode; +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +RLAPI Vector3 GetCameraForward(Camera *camera); +RLAPI Vector3 GetCameraUp(Camera *camera); +RLAPI Vector3 GetCameraRight(Camera *camera); + +// Camera movement +RLAPI void CameraMoveForward(Camera *camera, float distance, bool moveInWorldPlane); +RLAPI void CameraMoveUp(Camera *camera, float distance); +RLAPI void CameraMoveRight(Camera *camera, float distance, bool moveInWorldPlane); +RLAPI void CameraMoveToTarget(Camera *camera, float delta); + +// Camera rotation +RLAPI void CameraYaw(Camera *camera, float angle, bool rotateAroundTarget); +RLAPI void CameraPitch(Camera *camera, float angle, bool lockView, bool rotateAroundTarget, bool rotateUp); +RLAPI void CameraRoll(Camera *camera, float angle); + +RLAPI Matrix GetCameraViewMatrix(Camera *camera); +RLAPI Matrix GetCameraProjectionMatrix(Camera *camera, float aspect); + +#if defined(__cplusplus) +} +#endif + +#endif // RCAMERA_H + +/*********************************************************************************** +* +* CAMERA IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RCAMERA_IMPLEMENTATION) + +#include "raymath.h" // Required for vector maths: + // Vector3Add() + // Vector3Subtract() + // Vector3Scale() + // Vector3Normalize() + // Vector3Distance() + // Vector3CrossProduct() + // Vector3RotateByAxisAngle() + // Vector3Angle() + // Vector3Negate() + // MatrixLookAt() + // MatrixPerspective() + // MatrixOrtho() + // MatrixIdentity() + +// raylib required functionality: + // GetMouseDelta() + // GetMouseWheelMove() + // IsKeyDown() + // IsKeyPressed() + // GetFrameTime() + +#include // Required for: fabsf() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define CAMERA_MOVE_SPEED 5.4f // Units per second +#define CAMERA_ROTATION_SPEED 0.03f +#define CAMERA_PAN_SPEED 2.0f + +// Camera mouse movement sensitivity +#define CAMERA_MOUSE_MOVE_SENSITIVITY 0.003f + +// Camera orbital speed in CAMERA_ORBITAL mode +#define CAMERA_ORBITAL_SPEED 0.5f // Radians per second + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +// Returns the cameras forward vector (normalized) +Vector3 GetCameraForward(Camera *camera) +{ + return Vector3Normalize(Vector3Subtract(camera->target, camera->position)); +} + +// Returns the cameras up vector (normalized) +// Note: The up vector might not be perpendicular to the forward vector +Vector3 GetCameraUp(Camera *camera) +{ + return Vector3Normalize(camera->up); +} + +// Returns the cameras right vector (normalized) +Vector3 GetCameraRight(Camera *camera) +{ + Vector3 forward = GetCameraForward(camera); + Vector3 up = GetCameraUp(camera); + + return Vector3Normalize(Vector3CrossProduct(forward, up)); +} + +// Moves the camera in its forward direction +void CameraMoveForward(Camera *camera, float distance, bool moveInWorldPlane) +{ + Vector3 forward = GetCameraForward(camera); + + if (moveInWorldPlane) + { + // Project vector onto world plane (the plane defined by the up vector) + if (fabsf(camera->up.z) > 0.7071f) forward.z = 0; + else if (fabsf(camera->up.x) > 0.7071f) forward.x = 0; + else forward.y = 0; + + forward = Vector3Normalize(forward); + } + + // Scale by distance + forward = Vector3Scale(forward, distance); + + // Move position and target + camera->position = Vector3Add(camera->position, forward); + camera->target = Vector3Add(camera->target, forward); +} + +// Moves the camera in its up direction +void CameraMoveUp(Camera *camera, float distance) +{ + Vector3 up = GetCameraUp(camera); + + // Scale by distance + up = Vector3Scale(up, distance); + + // Move position and target + camera->position = Vector3Add(camera->position, up); + camera->target = Vector3Add(camera->target, up); +} + +// Moves the camera target in its current right direction +void CameraMoveRight(Camera *camera, float distance, bool moveInWorldPlane) +{ + Vector3 right = GetCameraRight(camera); + + if (moveInWorldPlane) + { + // Project vector onto world plane (the plane defined by the up vector) + if (fabsf(camera->up.z) > 0.7071f) right.z = 0; + else if (fabsf(camera->up.x) > 0.7071f) right.x = 0; + else right.y = 0; + + right = Vector3Normalize(right); + } + + // Scale by distance + right = Vector3Scale(right, distance); + + // Move position and target + camera->position = Vector3Add(camera->position, right); + camera->target = Vector3Add(camera->target, right); +} + +// Moves the camera position closer/farther to/from the camera target +void CameraMoveToTarget(Camera *camera, float delta) +{ + float distance = Vector3Distance(camera->position, camera->target); + + // Apply delta + distance += delta; + + // Distance must be greater than 0 + if (distance <= 0) distance = 0.001f; + + // Set new distance by moving the position along the forward vector + Vector3 forward = GetCameraForward(camera); + camera->position = Vector3Add(camera->target, Vector3Scale(forward, -distance)); +} + +// Rotates the camera around its up vector +// Yaw is "looking left and right" +// If rotateAroundTarget is false, the camera rotates around its position +// Note: angle must be provided in radians +void CameraYaw(Camera *camera, float angle, bool rotateAroundTarget) +{ + // Rotation axis + Vector3 up = GetCameraUp(camera); + + // View vector + Vector3 targetPosition = Vector3Subtract(camera->target, camera->position); + + // Rotate view vector around up axis + targetPosition = Vector3RotateByAxisAngle(targetPosition, up, angle); + + if (rotateAroundTarget) + { + // Move position relative to target + camera->position = Vector3Subtract(camera->target, targetPosition); + } + else // rotate around camera.position + { + // Move target relative to position + camera->target = Vector3Add(camera->position, targetPosition); + } +} + +// Rotates the camera around its right vector, pitch is "looking up and down" +// - lockView prevents camera overrotation (aka "somersaults") +// - rotateAroundTarget defines if rotation is around target or around its position +// - rotateUp rotates the up direction as well (typically only useful in CAMERA_FREE) +// NOTE: [angle] must be provided in radians +void CameraPitch(Camera *camera, float angle, bool lockView, bool rotateAroundTarget, bool rotateUp) +{ + // Up direction + Vector3 up = GetCameraUp(camera); + + // View vector + Vector3 targetPosition = Vector3Subtract(camera->target, camera->position); + + if (lockView) + { + // In these camera modes, clamp the Pitch angle + // to allow only viewing straight up or down + + // Clamp view up + float maxAngleUp = Vector3Angle(up, targetPosition); + maxAngleUp -= 0.001f; // avoid numerical errors + if (angle > maxAngleUp) angle = maxAngleUp; + + // Clamp view down + float maxAngleDown = Vector3Angle(Vector3Negate(up), targetPosition); + maxAngleDown *= -1.0f; // downwards angle is negative + maxAngleDown += 0.001f; // avoid numerical errors + if (angle < maxAngleDown) angle = maxAngleDown; + } + + // Rotation axis + Vector3 right = GetCameraRight(camera); + + // Rotate view vector around right axis + targetPosition = Vector3RotateByAxisAngle(targetPosition, right, angle); + + if (rotateAroundTarget) + { + // Move position relative to target + camera->position = Vector3Subtract(camera->target, targetPosition); + } + else // Rotate around camera.position + { + // Move target relative to position + camera->target = Vector3Add(camera->position, targetPosition); + } + + if (rotateUp) + { + // Rotate up direction around right axis + camera->up = Vector3RotateByAxisAngle(camera->up, right, angle); + } +} + +// Rotates the camera around its forward vector +// Roll is "turning your head sideways to the left or right" +// Note: angle must be provided in radians +void CameraRoll(Camera *camera, float angle) +{ + // Rotation axis + Vector3 forward = GetCameraForward(camera); + + // Rotate up direction around forward axis + camera->up = Vector3RotateByAxisAngle(camera->up, forward, angle); +} + +// Returns the camera view matrix +Matrix GetCameraViewMatrix(Camera *camera) +{ + return MatrixLookAt(camera->position, camera->target, camera->up); +} + +// Returns the camera projection matrix +Matrix GetCameraProjectionMatrix(Camera *camera, float aspect) +{ + if (camera->projection == CAMERA_PERSPECTIVE) + { + return MatrixPerspective(camera->fovy*DEG2RAD, aspect, CAMERA_CULL_DISTANCE_NEAR, CAMERA_CULL_DISTANCE_FAR); + } + else if (camera->projection == CAMERA_ORTHOGRAPHIC) + { + double top = camera->fovy/2.0; + double right = top*aspect; + + return MatrixOrtho(-right, right, -top, top, CAMERA_CULL_DISTANCE_NEAR, CAMERA_CULL_DISTANCE_FAR); + } + + return MatrixIdentity(); +} + +#if !defined(RCAMERA_STANDALONE) +// Update camera position for selected mode +// Camera mode: CAMERA_FREE, CAMERA_FIRST_PERSON, CAMERA_THIRD_PERSON, CAMERA_ORBITAL or CUSTOM +void UpdateCamera(Camera *camera, int mode) +{ + Vector2 mousePositionDelta = GetMouseDelta(); + + bool moveInWorldPlane = ((mode == CAMERA_FIRST_PERSON) || (mode == CAMERA_THIRD_PERSON)); + bool rotateAroundTarget = ((mode == CAMERA_THIRD_PERSON) || (mode == CAMERA_ORBITAL)); + bool lockView = ((mode == CAMERA_FREE) || (mode == CAMERA_FIRST_PERSON) || (mode == CAMERA_THIRD_PERSON) || (mode == CAMERA_ORBITAL)); + bool rotateUp = false; + + // Camera speeds based on frame time + float cameraMoveSpeed = CAMERA_MOVE_SPEED*GetFrameTime(); + float cameraRotationSpeed = CAMERA_ROTATION_SPEED*GetFrameTime(); + float cameraPanSpeed = CAMERA_PAN_SPEED*GetFrameTime(); + float cameraOrbitalSpeed = CAMERA_ORBITAL_SPEED*GetFrameTime(); + + if (mode == CAMERA_CUSTOM) {} + else if (mode == CAMERA_ORBITAL) + { + Matrix rotation = MatrixRotate(GetCameraUp(camera), cameraOrbitalSpeed); + Vector3 view = Vector3Subtract(camera->position, camera->target); + view = Vector3Transform(view, rotation); + camera->position = Vector3Add(camera->target, view); + } + else + { + // Camera rotation + if (IsKeyDown(KEY_DOWN)) CameraPitch(camera, -cameraRotationSpeed, lockView, rotateAroundTarget, rotateUp); + if (IsKeyDown(KEY_UP)) CameraPitch(camera, cameraRotationSpeed, lockView, rotateAroundTarget, rotateUp); + if (IsKeyDown(KEY_RIGHT)) CameraYaw(camera, -cameraRotationSpeed, rotateAroundTarget); + if (IsKeyDown(KEY_LEFT)) CameraYaw(camera, cameraRotationSpeed, rotateAroundTarget); + if (IsKeyDown(KEY_Q)) CameraRoll(camera, -cameraRotationSpeed); + if (IsKeyDown(KEY_E)) CameraRoll(camera, cameraRotationSpeed); + + // Camera movement + // Camera pan (for CAMERA_FREE) + if ((mode == CAMERA_FREE) && (IsMouseButtonDown(MOUSE_BUTTON_MIDDLE))) + { + const Vector2 mouseDelta = GetMouseDelta(); + if (mouseDelta.x > 0.0f) CameraMoveRight(camera, cameraPanSpeed, moveInWorldPlane); + if (mouseDelta.x < 0.0f) CameraMoveRight(camera, -cameraPanSpeed, moveInWorldPlane); + if (mouseDelta.y > 0.0f) CameraMoveUp(camera, -cameraPanSpeed); + if (mouseDelta.y < 0.0f) CameraMoveUp(camera, cameraPanSpeed); + } + else + { + // Mouse support + CameraYaw(camera, -mousePositionDelta.x*CAMERA_MOUSE_MOVE_SENSITIVITY, rotateAroundTarget); + CameraPitch(camera, -mousePositionDelta.y*CAMERA_MOUSE_MOVE_SENSITIVITY, lockView, rotateAroundTarget, rotateUp); + } + + // Keyboard support + if (IsKeyDown(KEY_W)) CameraMoveForward(camera, cameraMoveSpeed, moveInWorldPlane); + if (IsKeyDown(KEY_A)) CameraMoveRight(camera, -cameraMoveSpeed, moveInWorldPlane); + if (IsKeyDown(KEY_S)) CameraMoveForward(camera, -cameraMoveSpeed, moveInWorldPlane); + if (IsKeyDown(KEY_D)) CameraMoveRight(camera, cameraMoveSpeed, moveInWorldPlane); + + // Gamepad movement + if (IsGamepadAvailable(0)) + { + // Gamepad controller support + CameraYaw(camera, -(GetGamepadAxisMovement(0, GAMEPAD_AXIS_RIGHT_X)*2)*CAMERA_MOUSE_MOVE_SENSITIVITY, rotateAroundTarget); + CameraPitch(camera, -(GetGamepadAxisMovement(0, GAMEPAD_AXIS_RIGHT_Y)*2)*CAMERA_MOUSE_MOVE_SENSITIVITY, lockView, rotateAroundTarget, rotateUp); + + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y) <= -0.25f) CameraMoveForward(camera, cameraMoveSpeed, moveInWorldPlane); + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X) <= -0.25f) CameraMoveRight(camera, -cameraMoveSpeed, moveInWorldPlane); + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y) >= 0.25f) CameraMoveForward(camera, -cameraMoveSpeed, moveInWorldPlane); + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X) >= 0.25f) CameraMoveRight(camera, cameraMoveSpeed, moveInWorldPlane); + } + + if (mode == CAMERA_FREE) + { + if (IsKeyDown(KEY_SPACE)) CameraMoveUp(camera, cameraMoveSpeed); + if (IsKeyDown(KEY_LEFT_CONTROL)) CameraMoveUp(camera, -cameraMoveSpeed); + } + } + + if ((mode == CAMERA_THIRD_PERSON) || (mode == CAMERA_ORBITAL) || (mode == CAMERA_FREE)) + { + // Zoom target distance + CameraMoveToTarget(camera, -GetMouseWheelMove()); + if (IsKeyPressed(KEY_KP_SUBTRACT)) CameraMoveToTarget(camera, 2.0f); + if (IsKeyPressed(KEY_KP_ADD)) CameraMoveToTarget(camera, -2.0f); + } +} +#endif // !RCAMERA_STANDALONE + +// Update camera movement, movement/rotation values should be provided by user +void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, float zoom) +{ + // Required values + // movement.x - Move forward/backward + // movement.y - Move right/left + // movement.z - Move up/down + // rotation.x - yaw + // rotation.y - pitch + // rotation.z - roll + // zoom - Move towards target + + bool lockView = true; + bool rotateAroundTarget = false; + bool rotateUp = false; + bool moveInWorldPlane = true; + + // Camera rotation + CameraPitch(camera, -rotation.y*DEG2RAD, lockView, rotateAroundTarget, rotateUp); + CameraYaw(camera, -rotation.x*DEG2RAD, rotateAroundTarget); + CameraRoll(camera, rotation.z*DEG2RAD); + + // Camera movement + CameraMoveForward(camera, movement.x, moveInWorldPlane); + CameraMoveRight(camera, movement.y, moveInWorldPlane); + CameraMoveUp(camera, movement.z); + + // Zoom target distance + CameraMoveToTarget(camera, zoom); +} + +#endif // RCAMERA_IMPLEMENTATION diff --git a/src/libraries/raylib/raylib/src/rcore.c b/src/libraries/raylib/raylib/src/rcore.c new file mode 100644 index 0000000..4f6396e --- /dev/null +++ b/src/libraries/raylib/raylib/src/rcore.c @@ -0,0 +1,4702 @@ +/********************************************************************************************** +* +* rcore - Window/display management, Graphic device/context management and input management +* +* PLATFORMS SUPPORTED: +* > PLATFORM_DESKTOP_GLFW (GLFW backend): +* - Windows (Win32, Win64) +* - Linux (X11/Wayland desktop mode) +* - macOS/OSX (x64, arm64) +* - FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop) +* > PLATFORM_DESKTOP_SDL (SDL backend): +* - Windows (Win32, Win64) +* - Linux (X11/Wayland desktop mode) +* - Others (not tested) +* > PLATFORM_DESKTOP_RGFW (RGFW backend): +* - Windows (Win32, Win64) +* - Linux (X11/Wayland desktop mode) +* - macOS/OSX (x64, arm64) +* - Others (not tested) +* > PLATFORM_DESKTOP_WIN32 (native Win32): +* - Windows (Win32, Win64) +* > PLATFORM_WEB (GLFW + Emscripten): +* - HTML5 (WebAssembly) +* > PLATFORM_WEB_EMSCRIPTEN (Emscripten): +* - HTML5 (WebAssembly) +* > PLATFORM_WEB_RGFW (Emscripten): +* - HTML5 (WebAssembly) +* > PLATFORM_DRM (native DRM): +* - Raspberry Pi 0-5 (DRM/KMS) +* - Linux DRM subsystem (KMS mode) +* - Embedded devices (with GPU) +* > PLATFORM_ANDROID (native NDK): +* - Android (ARM, ARM64) +* > PLATFORM_MEMORY +* - Memory framebuffer output, using software renderer, no OS required +* +* CONFIGURATION: +* #define SUPPORT_CAMERA_SYSTEM 1 +* Camera module is included (rcamera.h) and multiple predefined cameras are available: +* free, 1st/3rd person, orbital, custom +* +* #define SUPPORT_GESTURES_SYSTEM 1 +* Gestures module is included (rgestures.h) to support gestures detection: tap, hold, swipe, drag +* +* #define SUPPORT_MOUSE_GESTURES 1 +* Mouse gestures are directly mapped like touches and processed by gestures system +* +* #define SUPPORT_BUSY_WAIT_LOOP 1 +* Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used +* +* #define SUPPORT_PARTIALBUSY_WAIT_LOOP 0 +* 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 +* +* #define SUPPORT_SCREEN_CAPTURE 1 +* Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() +* +* #define SUPPORT_COMPRESSION_API 1 +* Support CompressData() and DecompressData() functions, those functions use zlib implementation +* provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module +* for linkage +* +* #define SUPPORT_AUTOMATION_EVENTS 1 +* Support automatic events recording and playing, useful for automated testing systems or AI based game playing +* +* DEPENDENCIES: +* raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion) +* camera - Multiple 3D camera modes (free, orbital, 1st person, 3rd person) +* gestures - Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +//---------------------------------------------------------------------------------- +// Feature Test Macros required for this module +//---------------------------------------------------------------------------------- +#if (defined(__linux__) || defined(PLATFORM_WEB) || defined(PLATFORM_WEB_RGFW)) && (_XOPEN_SOURCE < 500) + #undef _XOPEN_SOURCE + #define _XOPEN_SOURCE 500 // Required for: readlink if compiled with c99 without GNU extensions +#endif + +#if (defined(__linux__) || defined(PLATFORM_WEB) || defined(PLATFORM_WEB_RGFW)) && (_POSIX_C_SOURCE < 199309L) + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without GNU extensions +#endif + +#include "raylib.h" // Declares module functions + +#include "config.h" // Defines module configuration flags + +#include // Required for: srand(), rand(), exit() +#include // Required for: FILE, fopen(), fseek(), ftell(), fread(), fwrite(), fprintf(), vprintf(), fclose(), sprintf() [Used in OpenURL()] +#include // Required for: strlen(), strcmp(), strrchr(), memset(), memcpy(), strcat() +#include // Required for: va_list, va_start(), va_end() [Used in TraceLog()] + +#ifndef PICO_RP2350 +#include // Required for: time() [Used in InitTimer()] +#else +#include "pico/rand.h" // Pico doesn't implement time, and will implement its own hardware timer elsewhere. However, we need something to get a random seed from... +#endif +#include // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] + +#if defined(PLATFORM_MEMORY) || defined(PLATFORM_WEB) + #define SW_GL_FRAMEBUFFER_COPY_BGRA false +#endif +#define RLGL_IMPLEMENTATION +#include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 + +#define RAYMATH_IMPLEMENTATION +#include "raymath.h" // Vector2, Vector3, Quaternion and Matrix functionality + +#if SUPPORT_GESTURES_SYSTEM + #define RGESTURES_IMPLEMENTATION + #include "rgestures.h" // Gestures detection functionality +#endif + +#if SUPPORT_CAMERA_SYSTEM + #define RCAMERA_IMPLEMENTATION + #include "rcamera.h" // Camera system functionality +#endif + +#if SUPPORT_COMPRESSION_API + #define SINFL_IMPLEMENTATION + #define SINFL_NO_SIMD + #include "external/sinfl.h" // Deflate (RFC 1951) decompressor + + #define SDEFL_IMPLEMENTATION + #include "external/sdefl.h" // Deflate (RFC 1951) compressor +#endif + +#if SUPPORT_RPRAND_GENERATOR + #define RPRAND_IMPLEMENTATION + #include "external/rprand.h" +#endif + +#if defined(__linux__) && !defined(_GNU_SOURCE) + #define _GNU_SOURCE +#endif + +// Platform specific defines to handle GetApplicationDirectory() +#if defined(_WIN32) + #if !defined(MAX_PATH) + #define MAX_PATH 260 + #endif + + struct HINSTANCE__; + #if defined(__cplusplus) + extern "C" { + #endif + __declspec(dllimport) unsigned long __stdcall GetFileAttributesA(const char *lpFileName); + __declspec(dllimport) unsigned long __stdcall GetModuleFileNameA(struct HINSTANCE__ *hModule, char *lpFilename, unsigned long nSize); + __declspec(dllimport) unsigned long __stdcall GetModuleFileNameW(struct HINSTANCE__ *hModule, wchar_t *lpFilename, unsigned long nSize); + __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + __declspec(dllimport) unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); + __declspec(dllimport) unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); + #if defined(__cplusplus) + } + #endif +#elif defined(__linux__) + #include +#elif defined(__FreeBSD__) + #include + #include + #include +#elif defined(__APPLE__) + #include + #include +#endif + +#define _CRT_INTERNAL_NONSTDC_NAMES 1 +#include // Required for: stat(), S_ISREG [Used in GetFileModTime(), IsFilePath()] + +#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) + #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif + +#if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) + #define DIRENT_MALLOC RL_MALLOC + #define DIRENT_FREE RL_FREE + + #include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()] +#else + #include // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()] +#endif + +#if defined(_WIN32) + #include // Required for: _access() [Used in FileExists()] + #include // Required for: _getch(), _chdir(), _mkdir() + #define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir() + #define CHDIR _chdir + #define MKDIR(dir) _mkdir(dir) + #define ACCESS(fn) _access(fn, 0) +#else + #include // Required for: getch(), chdir(), mkdir(), access() + #define GETCWD getcwd + #define CHDIR chdir + #define MKDIR(dir) mkdir(dir, 0777) + #define ACCESS(fn) access(fn, F_OK) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef MAX_TRACELOG_MSG_LENGTH + #define MAX_TRACELOG_MSG_LENGTH 256 // Max length of one trace-log message +#endif + +#ifndef MAX_FILEPATH_CAPACITY + #define MAX_FILEPATH_CAPACITY 8192 // Maximum capacity for filepath +#endif +#ifndef MAX_FILEPATH_LENGTH + #if defined(_WIN32) + #define MAX_FILEPATH_LENGTH 256 // On Win32, MAX_PATH = 260 (limits.h) but Windows 10, Version 1607 enables long paths... + #else + #define MAX_FILEPATH_LENGTH 4096 // On Linux, PATH_MAX = 4096 by default (limits.h) + #endif +#endif + +#ifndef MAX_KEYBOARD_KEYS + #define MAX_KEYBOARD_KEYS 512 // Maximum number of keyboard keys supported +#endif +#ifndef MAX_MOUSE_BUTTONS + #define MAX_MOUSE_BUTTONS 8 // Maximum number of mouse buttons supported +#endif +#ifndef MAX_GAMEPADS + #define MAX_GAMEPADS 4 // Maximum number of gamepads supported +#endif +#ifndef MAX_GAMEPAD_NAME_LENGTH + #define MAX_GAMEPAD_NAME_LENGTH 128 // Maximum number of characters in a gamepad name (byte size) +#endif +#ifndef MAX_GAMEPAD_AXES + #define MAX_GAMEPAD_AXES 8 // Maximum number of axes supported (per gamepad) +#endif +#ifndef MAX_GAMEPAD_BUTTONS + #define MAX_GAMEPAD_BUTTONS 32 // Maximum number of buttons supported (per gamepad) +#endif +#ifndef MAX_GAMEPAD_VIBRATION_TIME + #define MAX_GAMEPAD_VIBRATION_TIME 2.0f // Maximum vibration time in seconds +#endif +#ifndef MAX_TOUCH_POINTS + #define MAX_TOUCH_POINTS 8 // Maximum number of touch points supported +#endif +#ifndef MAX_KEY_PRESSED_QUEUE + #define MAX_KEY_PRESSED_QUEUE 16 // Maximum number of keys in the key input queue +#endif +#ifndef MAX_CHAR_PRESSED_QUEUE + #define MAX_CHAR_PRESSED_QUEUE 16 // Maximum number of characters in the char input queue +#endif + +#ifndef MAX_DECOMPRESSION_SIZE + #define MAX_DECOMPRESSION_SIZE 64 // Maximum size allocated for decompression in MB +#endif + +#ifndef MAX_AUTOMATION_EVENTS + #define MAX_AUTOMATION_EVENTS 16384 // Maximum number of automation events to record +#endif + +// File and directory scan filters +// NOTE: Used in ScanDirectoryFiles(), LoadDirectoryFilesEx() and GetDirectoryFileCountEx() +// WARNING: Custom file filters can be specified but following raylib IsFileExtension() convention: ".png;.wav;.glb" +#ifndef FILE_FILTER_TAG_ALL + #define FILE_FILTER_TAG_ALL "*.*" // Filter to include all file types and directories on scan +#endif +#ifndef FILE_FILTER_TAG_FILE_ONLY + #define FILE_FILTER_TAG_FILE_ONLY "FILES*" // Filter to include all file types on scan (no directories) +#endif +#ifndef FILE_FILTER_TAG_DIR_ONLY + #define FILE_FILTER_TAG_DIR_ONLY "DIRS*" // Filter to include only directories on scan +#endif + +// Flags bitwise operation macros +#define FLAG_SET(n, f) ((n) |= (f)) +#define FLAG_CLEAR(n, f) ((n) &= ~(f)) +#define FLAG_TOGGLE(n, f) ((n) ^= (f)) +#define FLAG_IS_SET(n, f) (((n) & (f)) == (f)) + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct { int x; int y; } Point; +typedef struct { unsigned int width; unsigned int height; } Size; + +// Core global state context data +typedef struct CoreData { + struct { + const char *title; // Window text title const pointer + unsigned int flags; // Configuration flags (bit based), keeps window state + bool ready; // Check if window has been initialized successfully + bool shouldClose; // Check if window set for closing + bool resizedLastFrame; // Check if window has been resized last frame + bool eventWaiting; // Wait for events before ending frame + bool usingFbo; // Using FBO (RenderTexture) for rendering instead of default framebuffer + + Size display; // Display width and height (monitor, device-screen, LCD, ...) + Size screen; // Screen current width and height + Point position; // Window current position + Size previousScreen; // Screen previous width and height (required on fullscreen/borderless-windowed toggle) + Point previousPosition; // Window previous position (required on fullscreen/borderless-windowed toggle) + Size render; // Screen framebuffer width and height + Point renderOffset; // Screen framebuffer render offset (Not required anymore?) + Size currentFbo; // Current framebuffer render width and height (depends on active render texture) + Size screenMin; // Screen minimum width and height (for resizable window) + Size screenMax; // Screen maximum width and height (for resizable window) + Matrix screenScale; // Matrix to scale screen (framebuffer rendering) + + char **dropFilepaths; // Store dropped files paths pointers (provided by GLFW) + unsigned int dropFileCount; // Count dropped files strings + + } Window; + struct { + const char *basePath; // Base path for data storage + + } Storage; + struct { + struct { + int exitKey; // Default exit key + char currentKeyState[MAX_KEYBOARD_KEYS]; // Registers current frame key state + char previousKeyState[MAX_KEYBOARD_KEYS]; // Registers previous frame key state + + // NOTE: Since key press logic involves comparing previous vs current key state, + // key repeats needs to be handled specially + char keyRepeatInFrame[MAX_KEYBOARD_KEYS]; // Registers key repeats for current frame + + int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input keys queue + int keyPressedQueueCount; // Input keys queue count + + int charPressedQueue[MAX_CHAR_PRESSED_QUEUE]; // Input characters queue (unicode) + int charPressedQueueCount; // Input characters queue count + + } Keyboard; + struct { + Vector2 offset; // Mouse offset + Vector2 scale; // Mouse scaling + Vector2 currentPosition; // Mouse position on screen + Vector2 previousPosition; // Previous mouse position + Vector2 lockedPosition; // Mouse position when locked + + int cursor; // Tracks current mouse cursor + bool cursorHidden; // Track if cursor is hidden + bool cursorLocked; // Track if cursor is locked (disabled) + bool cursorOnScreen; // Tracks if cursor is inside client area + + char currentButtonState[MAX_MOUSE_BUTTONS]; // Registers current mouse button state + char previousButtonState[MAX_MOUSE_BUTTONS]; // Registers previous mouse button state + Vector2 currentWheelMove; // Registers current mouse wheel variation + Vector2 previousWheelMove; // Registers previous mouse wheel variation + + } Mouse; + struct { + int pointCount; // Number of touch points active + int pointId[MAX_TOUCH_POINTS]; // Point identifiers + Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen + Vector2 previousPosition[MAX_TOUCH_POINTS]; // Previous touch position on screen + char currentTouchState[MAX_TOUCH_POINTS]; // Registers current touch state + char previousTouchState[MAX_TOUCH_POINTS]; // Registers previous touch state + + } Touch; + struct { + int lastButtonPressed; // Register last gamepad button pressed + int axisCount[MAX_GAMEPADS]; // Register number of available gamepad axes + bool ready[MAX_GAMEPADS]; // Flag to know if gamepad is ready + char name[MAX_GAMEPADS][MAX_GAMEPAD_NAME_LENGTH]; // Gamepad name holder + char currentButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state + char previousButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state + float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXES]; // Gamepad axes state + + } Gamepad; + } Input; + struct { + double current; // Current time measure (seconds) + double previous; // Previous time measure (seconds) + double update; // Time measure for frame update (seconds) + double draw; // Time measure for frame draw (seconds) + double frame; // Time measure for one frame (seconds) + double target; // Desired time for one frame, if 0 not applied (seconds) + unsigned long long base; // Base time measure for hi-res timer (ticks or nanoseconds) + unsigned int frameCounter; // Frame counter (frames) + + } Time; +} CoreData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +RLAPI const char *raylib_version = RAYLIB_VERSION; // raylib version exported symbol, required for some bindings + +CoreData CORE = { 0 }; // Global CORE state context + +static int logTypeLevel = LOG_INFO; // Minimum log type level + +static TraceLogCallback traceLog = NULL; // TraceLog callback function pointer +static LoadFileDataCallback loadFileData = NULL; // LoadFileData callback function pointer +static SaveFileDataCallback saveFileData = NULL; // SaveFileText callback function pointer +static LoadFileTextCallback loadFileText = NULL; // LoadFileText callback function pointer +static SaveFileTextCallback saveFileText = NULL; // SaveFileText callback function pointer + +#if SUPPORT_SCREEN_CAPTURE +static int screenshotCounter = 0; // Screenshots counter +#endif + +#if SUPPORT_AUTOMATION_EVENTS +// Automation events type +typedef enum AutomationEventType { + EVENT_NONE = 0, + // Input events + INPUT_KEY_UP, // param[0]: key + INPUT_KEY_DOWN, // param[0]: key + INPUT_KEY_PRESSED, // param[0]: key + INPUT_KEY_RELEASED, // param[0]: key + INPUT_MOUSE_BUTTON_UP, // param[0]: button + INPUT_MOUSE_BUTTON_DOWN, // param[0]: button + INPUT_MOUSE_POSITION, // param[0]: x, param[1]: y + INPUT_MOUSE_WHEEL_MOTION, // param[0]: x delta, param[1]: y delta + INPUT_GAMEPAD_CONNECT, // param[0]: gamepad + INPUT_GAMEPAD_DISCONNECT, // param[0]: gamepad + INPUT_GAMEPAD_BUTTON_UP, // param[0]: button + INPUT_GAMEPAD_BUTTON_DOWN, // param[0]: button + INPUT_GAMEPAD_AXIS_MOTION, // param[0]: axis, param[1]: delta + INPUT_TOUCH_UP, // param[0]: id + INPUT_TOUCH_DOWN, // param[0]: id + INPUT_TOUCH_POSITION, // param[0]: x, param[1]: y + INPUT_GESTURE, // param[0]: gesture + // Window events + WINDOW_CLOSE, // no params + WINDOW_MAXIMIZE, // no params + WINDOW_MINIMIZE, // no params + WINDOW_RESIZE, // param[0]: width, param[1]: height + // Custom events + ACTION_TAKE_SCREENSHOT, // no params + ACTION_SETTARGETFPS // param[0]: fps +} AutomationEventType; + +// Event type name strings, required for export +static const char *autoEventTypeName[] = { + "EVENT_NONE", + "INPUT_KEY_UP", + "INPUT_KEY_DOWN", + "INPUT_KEY_PRESSED", + "INPUT_KEY_RELEASED", + "INPUT_MOUSE_BUTTON_UP", + "INPUT_MOUSE_BUTTON_DOWN", + "INPUT_MOUSE_POSITION", + "INPUT_MOUSE_WHEEL_MOTION", + "INPUT_GAMEPAD_CONNECT", + "INPUT_GAMEPAD_DISCONNECT", + "INPUT_GAMEPAD_BUTTON_UP", + "INPUT_GAMEPAD_BUTTON_DOWN", + "INPUT_GAMEPAD_AXIS_MOTION", + "INPUT_TOUCH_UP", + "INPUT_TOUCH_DOWN", + "INPUT_TOUCH_POSITION", + "INPUT_GESTURE", + "WINDOW_CLOSE", + "WINDOW_MAXIMIZE", + "WINDOW_MINIMIZE", + "WINDOW_RESIZE", + "ACTION_TAKE_SCREENSHOT", + "ACTION_SETTARGETFPS" +}; + +static AutomationEventList *currentEventList = NULL; // Current automation events list, set by user, keep internal pointer +static bool automationEventRecording = false; // Recording automation events flag +//static short automationEventEnabled = 0b0000001111111111; // TODO: Automation events enabled for recording/playing +#endif +//----------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +// NOTE: Those functions are common for all platforms! +//---------------------------------------------------------------------------------- +#if SUPPORT_MODULE_RTEXT +extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow() +extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory +#endif + +extern int InitPlatform(void); // Initialize platform (graphics, inputs and more) +extern void ClosePlatform(void); // Close platform + +static void InitTimer(void); // Initialize timer, hi-resolution if available (required by InitPlatform()) +static void SetupViewport(int width, int height); // Set viewport for a provided width and height + +static void ScanDirectoryFiles(const char *basePath, FilePathList *list, const char *filter, unsigned int expectedFileCount, bool scanSubdirs); // Scan all files and directories in a base path + +#if SUPPORT_AUTOMATION_EVENTS +static void RecordAutomationEvent(void); // Record frame events (to internal events array) +#endif + +#if defined(_WIN32) && !defined(PLATFORM_DESKTOP_RGFW) +// NOTE: Declaring Sleep() function symbol to avoid including windows.h (kernel32.lib linkage required) +__declspec(dllimport) void __stdcall Sleep(unsigned long msTimeout); // Required for: WaitTime() +#endif + +#if !SUPPORT_MODULE_RTEXT +const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' +#endif + +// NOTE: PLATFORM_DESKTOP defaults to GLFW backend +#if defined(PLATFORM_DESKTOP) + #define PLATFORM_DESKTOP_GLFW +#endif + +// Include platform-specific submodules +#if defined(PLATFORM_DESKTOP_GLFW) + #include "platforms/rcore_desktop_glfw.c" +#elif defined(PLATFORM_DESKTOP_SDL) + #include "platforms/rcore_desktop_sdl.c" +#elif (defined(PLATFORM_DESKTOP_RGFW) || defined(PLATFORM_WEB_RGFW)) + #include "platforms/rcore_desktop_rgfw.c" +#elif defined(PLATFORM_DESKTOP_WIN32) + #include "platforms/rcore_desktop_win32.c" +#elif defined(PLATFORM_WEB) + #include "platforms/rcore_web.c" +#elif defined(PLATFORM_DRM) + #include "platforms/rcore_drm.c" +#elif defined(PLATFORM_ANDROID) + #include "platforms/rcore_android.c" +#elif defined(PLATFORM_MEMORY) + #include "platforms/rcore_memory.c" +#else + // TODO: Include your custom platform backend! + // i.e software rendering backend or console backend! + #pragma message ("WARNING: No [rcore] platform defined") +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// NOTE: Functions with a platform-specific implementation on rcore_.c +//bool WindowShouldClose(void) +//void ToggleFullscreen(void) +//void ToggleBorderlessWindowed(void) +//void MaximizeWindow(void) +//void MinimizeWindow(void) +//void RestoreWindow(void) + +//void SetWindowState(unsigned int flags) +//void ClearWindowState(unsigned int flags) + +//void SetWindowIcon(Image image) +//void SetWindowIcons(Image *images, int count) +//void SetWindowTitle(const char *title) +//void SetWindowPosition(int x, int y) +//void SetWindowMonitor(int monitor) +//void SetWindowMinSize(int width, int height) +//void SetWindowMaxSize(int width, int height) +//void SetWindowSize(int width, int height) +//void SetWindowOpacity(float opacity) +//void SetWindowFocused(void) +//void *GetWindowHandle(void) +//Vector2 GetWindowPosition(void) +//Vector2 GetWindowScaleDPI(void) + +//int GetMonitorCount(void) +//int GetCurrentMonitor(void) +//int GetMonitorWidth(int monitor) +//int GetMonitorHeight(int monitor) +//int GetMonitorPhysicalWidth(int monitor) +//int GetMonitorPhysicalHeight(int monitor) +//int GetMonitorRefreshRate(int monitor) +//Vector2 GetMonitorPosition(int monitor) +//const char *GetMonitorName(int monitor) + +//void SetClipboardText(const char *text) +//const char *GetClipboardText(void) + +//void ShowCursor(void) +//void HideCursor(void) +//void EnableCursor(void) +//void DisableCursor(void) + +// Initialize window and OpenGL context +void InitWindow(int width, int height, const char *title) +{ + TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION); + +#if defined(PLATFORM_DESKTOP_GLFW) + TRACELOG(LOG_INFO, "Platform backend: DESKTOP (GLFW)"); +#elif defined(PLATFORM_DESKTOP_SDL) + TRACELOG(LOG_INFO, "Platform backend: DESKTOP (SDL)"); +#elif defined(PLATFORM_DESKTOP_RGFW) + TRACELOG(LOG_INFO, "Platform backend: DESKTOP (RGFW)"); +#elif defined(PLATFORM_DESKTOP_WIN32) + TRACELOG(LOG_INFO, "Platform backend: DESKTOP (WIN32)"); +#elif defined(PLATFORM_WEB_RGFW) + TRACELOG(LOG_INFO, "Platform backend: WEB (RGFW) (HTML5)"); +#elif defined(PLATFORM_WEB) + TRACELOG(LOG_INFO, "Platform backend: WEB (HTML5)"); +#elif defined(PLATFORM_DRM) + TRACELOG(LOG_INFO, "Platform backend: NATIVE DRM"); +#elif defined(PLATFORM_ANDROID) + TRACELOG(LOG_INFO, "Platform backend: ANDROID"); +#elif defined(PLATFORM_MEMORY) + TRACELOG(LOG_INFO, "Platform backend: MEMORY (No OS)"); +#else + // TODO: Include your custom platform backend! + // i.e software rendering backend or console backend! + TRACELOG(LOG_INFO, "Platform backend: CUSTOM"); +#endif + + TRACELOG(LOG_INFO, "Supported raylib modules:"); + TRACELOG(LOG_INFO, " > rcore:..... loaded (mandatory)"); + TRACELOG(LOG_INFO, " > rlgl:...... loaded (mandatory)"); +#if SUPPORT_MODULE_RSHAPES + TRACELOG(LOG_INFO, " > rshapes:... loaded (optional)"); +#else + TRACELOG(LOG_INFO, " > rshapes:... not loaded (optional)"); +#endif +#if SUPPORT_MODULE_RTEXTURES + TRACELOG(LOG_INFO, " > rtextures:. loaded (optional)"); +#else + TRACELOG(LOG_INFO, " > rtextures:. not loaded (optional)"); +#endif +#if SUPPORT_MODULE_RTEXT + TRACELOG(LOG_INFO, " > rtext:..... loaded (optional)"); +#else + TRACELOG(LOG_INFO, " > rtext:..... not loaded (optional)"); +#endif +#if SUPPORT_MODULE_RMODELS + TRACELOG(LOG_INFO, " > rmodels:... loaded (optional)"); +#else + TRACELOG(LOG_INFO, " > rmodels:... not loaded (optional)"); +#endif +#if SUPPORT_MODULE_RAUDIO + TRACELOG(LOG_INFO, " > raudio:.... loaded (optional)"); +#else + TRACELOG(LOG_INFO, " > raudio:.... not loaded (optional)"); +#endif + + // Initialize window data + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + + CORE.Window.eventWaiting = false; + CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default + if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title; + + // Initialize global input state + memset(&CORE.Input, 0, sizeof(CORE.Input)); // Reset CORE.Input structure to 0 + CORE.Input.Keyboard.exitKey = KEY_ESCAPE; + CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f }; + CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW; + CORE.Input.Gamepad.lastButtonPressed = GAMEPAD_BUTTON_UNKNOWN; + + // Initialize platform + //-------------------------------------------------------------- + int result = InitPlatform(); + + if (result != 0) + { + TRACELOG(LOG_WARNING, "SYSTEM: Failed to initialize platform"); + return; + } + + // Initialize render dimensions for embedded platforms + // NOTE: On desktop platforms (GLFW, SDL, etc.), CORE.Window.render.width/height are set during window creation + // On embedded platforms with no window manager, InitPlatform() doesn't set these values, so they should be initialized + // here from screen dimensions (which are set from the InitWindow parameters) + if ((CORE.Window.render.width == 0) || (CORE.Window.render.height == 0)) + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + } + //-------------------------------------------------------------- + + // Initialize rlgl default data (buffers and shaders) + // NOTE: Current fbo size stored as globals in rlgl for convenience + rlglInit(CORE.Window.render.width, CORE.Window.render.height); + + // Setup default viewport + SetupViewport(CORE.Window.render.width, CORE.Window.render.height); + +#if SUPPORT_MODULE_RTEXT + // Load default font + // WARNING: External function: Module required: rtext + LoadFontDefault(); + #if SUPPORT_MODULE_RSHAPES + // Set font white rectangle for shapes drawing, so shapes and text can be batched together + // WARNING: rshapes module is required, if not available, default internal white rectangle is used + Rectangle rec = GetFontDefault().recs[95]; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) + { + // NOTE: Try to maximize rec padding to avoid pixel bleeding on MSAA filtering + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 }); + } + else + { + // NOTE: Set up a 1px padding on char rectangle to avoid pixel bleeding + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); + } + #endif +#else + #if SUPPORT_MODULE_RSHAPES + // Set default texture and rectangle to be used for shapes drawing + // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 + Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; + SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); // WARNING: Module required: rshapes + #endif +#endif + + CORE.Time.frameCounter = 0; + CORE.Window.shouldClose = false; + + // Initialize random seed using available timer source instead of standard time() on embedded platforms + #ifndef PICO_RP2350 + SetRandomSeed((unsigned int)time(NULL)); + #else + SetRandomSeed(get_rand_32()); + #endif + + TRACELOG(LOG_INFO, "SYSTEM: Working Directory: %s", GetWorkingDirectory()); +} + +// Close window and unload OpenGL context +void CloseWindow(void) +{ +#if SUPPORT_MODULE_RTEXT + UnloadFontDefault(); // WARNING: Module required: rtext +#endif + + rlglClose(); // De-init rlgl + + // De-initialize platform + //-------------------------------------------------------------- + ClosePlatform(); + //-------------------------------------------------------------- + + CORE.Window.ready = false; + TRACELOG(LOG_INFO, "Window closed successfully"); +} + +// Check if window has been initialized successfully +bool IsWindowReady(void) +{ + return CORE.Window.ready; +} + +// Check if window is currently fullscreen +bool IsWindowFullscreen(void) +{ + return FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); +} + +// Check if window is currently hidden +bool IsWindowHidden(void) +{ + return FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); +} + +// Check if window has been minimized +bool IsWindowMinimized(void) +{ + return FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); +} + +// Check if window has been maximized +bool IsWindowMaximized(void) +{ + return FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); +} + +// Check if window has the focus +bool IsWindowFocused(void) +{ + return !FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); +} + +// Check if window has been resizedLastFrame +bool IsWindowResized(void) +{ + return CORE.Window.resizedLastFrame; +} + +// Check if one specific window flag is enabled +bool IsWindowState(unsigned int flag) +{ + return FLAG_IS_SET(CORE.Window.flags, flag); +} + +// Get current screen width +int GetScreenWidth(void) +{ + return CORE.Window.screen.width; +} + +// Get current screen height +int GetScreenHeight(void) +{ + return CORE.Window.screen.height; +} + +// Get current render width which is equal to screen width*dpi scale +int GetRenderWidth(void) +{ + int width = 0; + + if (CORE.Window.usingFbo) width = CORE.Window.currentFbo.width; + else width = CORE.Window.render.width; + + return width; +} + +// Get current screen height which is equal to screen height*dpi scale +int GetRenderHeight(void) +{ + int height = 0; + + if (CORE.Window.usingFbo) height = CORE.Window.currentFbo.height; + else height = CORE.Window.render.height; + + return height; +} + +// Enable waiting for events on EndDrawing(), no automatic event polling +void EnableEventWaiting(void) +{ + CORE.Window.eventWaiting = true; +} + +// Disable waiting for events on EndDrawing(), automatic events polling +void DisableEventWaiting(void) +{ + CORE.Window.eventWaiting = false; +} + +// Check if cursor is not visible +bool IsCursorHidden(void) +{ + return CORE.Input.Mouse.cursorHidden; +} + +// Check if cursor is on the current screen +bool IsCursorOnScreen(void) +{ + return CORE.Input.Mouse.cursorOnScreen; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Screen Drawing +//---------------------------------------------------------------------------------- + +// Clear background (framebuffer) to color +void ClearBackground(Color color) +{ + rlClearColor(color.r, color.g, color.b, color.a); // Set clear color + rlClearScreenBuffers(); // Clear current framebuffers +} + +// Begin canvas (framebuffer) drawing +void BeginDrawing(void) +{ + // WARNING: Previously to BeginDrawing() other render textures drawing could happen, + // consequently the measure for update vs draw is not accurate (only the total frame time is accurate) + + CORE.Time.current = GetTime(); // Number of elapsed seconds since InitTimer() + CORE.Time.update = CORE.Time.current - CORE.Time.previous; + CORE.Time.previous = CORE.Time.current; + + rlLoadIdentity(); // Reset current matrix (modelview) + rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling + + //rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 + // NOTE: Not required with OpenGL 3.3+ +} + +// End canvas (framebuffer) drawing and swap buffers (double buffering) +void EndDrawing(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + +#if SUPPORT_AUTOMATION_EVENTS + if (automationEventRecording) RecordAutomationEvent(); // Event recording +#endif + +#if !SUPPORT_CUSTOM_FRAME_CONTROL + SwapScreenBuffer(); // Copy back buffer to front buffer (screen) + + // Frame time control system + CORE.Time.current = GetTime(); + CORE.Time.draw = CORE.Time.current - CORE.Time.previous; + CORE.Time.previous = CORE.Time.current; + + CORE.Time.frame = CORE.Time.update + CORE.Time.draw; + + // Wait for some milliseconds... + if (CORE.Time.frame < CORE.Time.target) + { + WaitTime(CORE.Time.target - CORE.Time.frame); + + CORE.Time.current = GetTime(); + double waitTime = CORE.Time.current - CORE.Time.previous; + CORE.Time.previous = CORE.Time.current; + + CORE.Time.frame += waitTime; // Total frame time: update + draw + wait + } + + PollInputEvents(); // Poll user events (before next frame update) +#endif + +#if SUPPORT_SCREEN_CAPTURE + if (IsKeyPressed(KEY_F12)) + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } +#endif + + CORE.Time.frameCounter++; +} + +// Initialize 2D mode with custom camera (2D) +void BeginMode2D(Camera2D camera) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlLoadIdentity(); // Reset current matrix (modelview) + + // Apply 2d camera transformation to modelview + rlMultMatrixf(MatrixToFloat(GetCameraMatrix2D(camera))); +} + +// End 2D mode with custom camera +void EndMode2D(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlLoadIdentity(); // Reset current matrix (modelview) + + if (rlGetActiveFramebuffer() == 0) rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required +} + +// Initializes 3D mode with custom camera (3D) +void BeginMode3D(Camera camera) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection + rlLoadIdentity(); // Reset current matrix (projection) + + float aspect = (float)CORE.Window.currentFbo.width/(float)CORE.Window.currentFbo.height; + + // NOTE: zNear and zFar values are important when computing depth buffer values + if (camera.projection == CAMERA_PERSPECTIVE) + { + // Setup perspective projection + double top = rlGetCullDistanceNear()*tan(camera.fovy*0.5*DEG2RAD); + double right = top*aspect; + + rlFrustum(-right, right, -top, top, rlGetCullDistanceNear(), rlGetCullDistanceFar()); + } + else if (camera.projection == CAMERA_ORTHOGRAPHIC) + { + // Setup orthographic projection + double top = camera.fovy/2.0; + double right = top*aspect; + + rlOrtho(-right, right, -top,top, rlGetCullDistanceNear(), rlGetCullDistanceFar()); + } + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) + + // Setup Camera view + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + rlMultMatrixf(MatrixToFloat(matView)); // Multiply modelview matrix by view matrix (camera) + + rlEnableDepthTest(); // Enable DEPTH_TEST for 3D +} + +// End 3D mode and returns to default 2D orthographic mode +void EndMode3D(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlPopMatrix(); // Restore previous matrix (projection) from matrix stack + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) + + if (rlGetActiveFramebuffer() == 0) rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required + + rlDisableDepthTest(); // Disable DEPTH_TEST for 2D +} + +// Initializes render texture for drawing +void BeginTextureMode(RenderTexture2D target) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlEnableFramebuffer(target.id); // Enable render target + + // Set viewport and RLGL internal framebuffer size + rlViewport(0, 0, target.texture.width, target.texture.height); + rlSetFramebufferWidth(target.texture.width); + rlSetFramebufferHeight(target.texture.height); + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlLoadIdentity(); // Reset current matrix (projection) + + // Set orthographic projection to current framebuffer size + // NOTE: Configured top-left corner as (0, 0) + rlOrtho(0, target.texture.width, target.texture.height, 0, 0.0f, 1.0f); + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) + + //rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?) + + // Setup current width/height for proper aspect ratio + // calculation when using BeginTextureMode() + CORE.Window.currentFbo.width = target.texture.width; + CORE.Window.currentFbo.height = target.texture.height; + CORE.Window.usingFbo = true; +} + +// End drawing to render texture +void EndTextureMode(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlDisableFramebuffer(); // Disable render target (fbo) + + // Set viewport to default framebuffer size + SetupViewport(CORE.Window.render.width, CORE.Window.render.height); + + // Go back to the modelview state from BeginDrawing, back to the main framebuffer + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) + rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required + + // Reset current fbo to screen size + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + CORE.Window.usingFbo = false; +} + +// Begin custom shader mode +void BeginShaderMode(Shader shader) +{ + rlSetShader(shader.id, shader.locs); +} + +// End custom shader mode (returns to default shader) +void EndShaderMode(void) +{ + rlSetShader(rlGetShaderIdDefault(), rlGetShaderLocsDefault()); +} + +// Begin blending mode (alpha, additive, multiplied, subtract, custom) +// NOTE: Blend modes supported are enumerated in BlendMode enum +void BeginBlendMode(int mode) +{ + rlSetBlendMode(mode); +} + +// End blending mode (reset to default: alpha blending) +void EndBlendMode(void) +{ + rlSetBlendMode(BLEND_ALPHA); +} + +// Begin scissor mode (define screen area for following drawing) +// NOTE: Scissor rec refers to bottom-left corner, changing it to upper-left +void BeginScissorMode(int x, int y, int width, int height) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + + rlEnableScissorTest(); + +#if defined(__APPLE__) + if (!CORE.Window.usingFbo) + { + Vector2 scale = GetWindowScaleDPI(); + rlScissor((int)(x*scale.x), (int)(GetScreenHeight()*scale.y - (((y + height)*scale.y))), (int)(width*scale.x), (int)(height*scale.y)); + } +#else + if (!CORE.Window.usingFbo && FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + Vector2 scale = GetWindowScaleDPI(); + rlScissor((int)(x*scale.x), (int)(CORE.Window.currentFbo.height - (y + height)*scale.y), (int)(width*scale.x), (int)(height*scale.y)); + } +#endif + else + { + rlScissor(x, CORE.Window.currentFbo.height - (y + height), width, height); + } +} + +// End scissor mode +void EndScissorMode(void) +{ + rlDrawRenderBatchActive(); // Update and draw internal render batch + rlDisableScissorTest(); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: VR Stereo Rendering +//---------------------------------------------------------------------------------- + +// Begin VR drawing configuration +void BeginVrStereoMode(VrStereoConfig config) +{ + rlEnableStereoRender(); + + // Set stereo render matrices + rlSetMatrixProjectionStereo(config.projection[0], config.projection[1]); + rlSetMatrixViewOffsetStereo(config.viewOffset[0], config.viewOffset[1]); +} + +// End VR drawing process (and desktop mirror) +void EndVrStereoMode(void) +{ + rlDisableStereoRender(); +} + +// Load VR stereo config for VR simulator device parameters +VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device) +{ + VrStereoConfig config = { 0 }; + + if (rlGetVersion() != RL_OPENGL_11) + { + // Compute aspect ratio + float aspect = ((float)device.hResolution*0.5f)/(float)device.vResolution; + + // Compute lens parameters + float lensShift = (device.hScreenSize*0.25f - device.lensSeparationDistance*0.5f)/device.hScreenSize; + config.leftLensCenter[0] = 0.25f + lensShift; + config.leftLensCenter[1] = 0.5f; + config.rightLensCenter[0] = 0.75f - lensShift; + config.rightLensCenter[1] = 0.5f; + config.leftScreenCenter[0] = 0.25f; + config.leftScreenCenter[1] = 0.5f; + config.rightScreenCenter[0] = 0.75f; + config.rightScreenCenter[1] = 0.5f; + + // Compute distortion scale parameters + // NOTE: To get lens max radius, lensShift must be normalized to [-1..1] + float lensRadius = fabsf(-1.0f - 4.0f*lensShift); + float lensRadiusSq = lensRadius*lensRadius; + float distortionScale = device.lensDistortionValues[0] + + device.lensDistortionValues[1]*lensRadiusSq + + device.lensDistortionValues[2]*lensRadiusSq*lensRadiusSq + + device.lensDistortionValues[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq; + + float normScreenWidth = 0.5f; + float normScreenHeight = 1.0f; + config.scaleIn[0] = 2.0f/normScreenWidth; + config.scaleIn[1] = 2.0f/normScreenHeight/aspect; + config.scale[0] = normScreenWidth*0.5f/distortionScale; + config.scale[1] = normScreenHeight*0.5f*aspect/distortionScale; + + // Fovy is normally computed with: 2*atan2f(device.vScreenSize, 2*device.eyeToScreenDistance) + // ...but with lens distortion it is increased (see Oculus SDK Documentation) + float fovy = 2.0f*atan2f(device.vScreenSize*0.5f*distortionScale, device.eyeToScreenDistance); // Really need distortionScale? + // float fovy = 2.0f*(float)atan2f(device.vScreenSize*0.5f, device.eyeToScreenDistance); + + // Compute camera projection matrices + float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1] + Matrix proj = MatrixPerspective(fovy, aspect, rlGetCullDistanceNear(), rlGetCullDistanceFar()); + + config.projection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f)); + config.projection[1] = MatrixMultiply(proj, MatrixTranslate(-projOffset, 0.0f, 0.0f)); + + // Compute camera transformation matrices + // NOTE: Camera movement might seem more natural if modelling the head + // Axis of rotation is the base of the head, so adding some y (base of head to eye level + // and -z (center of head to eye protrusion) to the camera positions + config.viewOffset[0] = MatrixTranslate(device.interpupillaryDistance*0.5f, 0.075f, 0.045f); + config.viewOffset[1] = MatrixTranslate(-device.interpupillaryDistance*0.5f, 0.075f, 0.045f); + + // Compute eyes Viewports + /* + config.eyeViewportRight[0] = 0; + config.eyeViewportRight[1] = 0; + config.eyeViewportRight[2] = device.hResolution/2; + config.eyeViewportRight[3] = device.vResolution; + + config.eyeViewportLeft[0] = device.hResolution/2; + config.eyeViewportLeft[1] = 0; + config.eyeViewportLeft[2] = device.hResolution/2; + config.eyeViewportLeft[3] = device.vResolution; + */ + } + else TRACELOG(LOG_WARNING, "RLGL: VR Simulator not supported on OpenGL 1.1"); + + return config; +} + +// Unload VR stereo config properties +void UnloadVrStereoConfig(VrStereoConfig config) +{ + TRACELOG(LOG_INFO, "UnloadVrStereoConfig not implemented in rcore.c"); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Shaders Management +//---------------------------------------------------------------------------------- + +// Load shader from files and bind default locations +// NOTE: If shader filename is NULL, using default vertex/fragment shaders +Shader LoadShader(const char *vsFileName, const char *fsFileName) +{ + Shader shader = { 0 }; + + char *vShaderStr = NULL; + char *fShaderStr = NULL; + + if (vsFileName != NULL) vShaderStr = LoadFileText(vsFileName); + if (fsFileName != NULL) fShaderStr = LoadFileText(fsFileName); + + if ((vShaderStr == NULL) && (fShaderStr == NULL)) TRACELOG(LOG_WARNING, "SHADER: Shader files provided are not valid, using default shader"); + + shader = LoadShaderFromMemory(vShaderStr, fShaderStr); + + UnloadFileText(vShaderStr); + UnloadFileText(fShaderStr); + + return shader; +} + +// Load shader from code strings and bind default locations +Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) +{ + Shader shader = { 0 }; + + shader.id = rlLoadShaderProgram(vsCode, fsCode); + + if (shader.id == 0) + { + // Shader could not be loaded but still loading the location points to avoid potential crashes + // NOTE: All locations set to -1 (no location found) + shader.locs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); + for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; + } + else if (shader.id == rlGetShaderIdDefault()) shader.locs = rlGetShaderLocsDefault(); + else if (shader.id > 0) + { + // After custom shader loading, trying to set default location names + // Default shader attribute locations have been binded before linking: + // - vertex position location = 0 + // - vertex texcoord location = 1 + // - vertex normal location = 2 + // - vertex color location = 3 + // - vertex tangent location = 4 + // - vertex texcoord2 location = 5 + // - vertex boneIndices location = 6 + // - vertex boneWeights location = 7 + + // NOTE: If any location is not found, loc point becomes -1 + + // Load shader locations array + // NOTE: All locations set to -1 (no location) + shader.locs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); + for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; + + // Get handles to GLSL input attribute locations + shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + shader.locs[SHADER_LOC_VERTEX_BONEIDS] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEINDICES); + shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); + shader.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_INSTANCETRANSFORM); + + // Get handles to GLSL uniform locations (vertex shader) + shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); + shader.locs[SHADER_LOC_MATRIX_VIEW] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW); + shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION); + shader.locs[SHADER_LOC_MATRIX_MODEL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL); + shader.locs[SHADER_LOC_MATRIX_NORMAL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL); + shader.locs[SHADER_LOC_MATRIX_BONETRANSFORMS] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_BONEMATRICES); + + // Get handles to GLSL uniform locations (fragment shader) + shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); + shader.locs[SHADER_LOC_MAP_DIFFUSE] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); // SHADER_LOC_MAP_ALBEDO + shader.locs[SHADER_LOC_MAP_SPECULAR] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1); // SHADER_LOC_MAP_METALNESS + shader.locs[SHADER_LOC_MAP_NORMAL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2); + } + + return shader; +} + +// Check if shader is valid (loaded on GPU) +bool IsShaderValid(Shader shader) +{ + return ((shader.id > 0) && // Validate shader id (GPU loaded successfully) + (shader.locs != NULL)); // Validate memory has been allocated for default shader locations + + // The following locations are tried to be set automatically (locs[i] >= 0), + // any of them can be checked for validation but the only mandatory one is, afaik, SHADER_LOC_VERTEX_POSITION + // NOTE: Users can also setup manually their own attributes/uniforms and do not used the default raylib ones + + // Vertex shader attribute locations (default) + // shader.locs[SHADER_LOC_VERTEX_POSITION] // Set by default internal shader + // shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] // Set by default internal shader + // shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] + // shader.locs[SHADER_LOC_VERTEX_NORMAL] + // shader.locs[SHADER_LOC_VERTEX_TANGENT] + // shader.locs[SHADER_LOC_VERTEX_COLOR] // Set by default internal shader + + // Vertex shader uniform locations (default) + // shader.locs[SHADER_LOC_MATRIX_MVP] // Set by default internal shader + // shader.locs[SHADER_LOC_MATRIX_VIEW] + // shader.locs[SHADER_LOC_MATRIX_PROJECTION] + // shader.locs[SHADER_LOC_MATRIX_MODEL] + // shader.locs[SHADER_LOC_MATRIX_NORMAL] + + // Fragment shader uniform locations (default) + // shader.locs[SHADER_LOC_COLOR_DIFFUSE] // Set by default internal shader + // shader.locs[SHADER_LOC_MAP_DIFFUSE] // Set by default internal shader + // shader.locs[SHADER_LOC_MAP_SPECULAR] + // shader.locs[SHADER_LOC_MAP_NORMAL] +} + +// Unload shader from GPU memory (VRAM) +void UnloadShader(Shader shader) +{ + if (shader.id != rlGetShaderIdDefault()) + { + rlUnloadShaderProgram(shader.id); + + // NOTE: If shader loading failed, it should be 0 + RL_FREE(shader.locs); + } +} + +// Get shader uniform location +int GetShaderLocation(Shader shader, const char *uniformName) +{ + return rlGetLocationUniform(shader.id, uniformName); +} + +// Get shader attribute location +int GetShaderLocationAttrib(Shader shader, const char *attribName) +{ + return rlGetLocationAttrib(shader.id, attribName); +} + +// Set shader uniform value +void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType) +{ + SetShaderValueV(shader, locIndex, value, uniformType, 1); +} + +// Set shader uniform value vector +void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count) +{ + if (locIndex > -1) + { + rlEnableShader(shader.id); + rlSetUniform(locIndex, value, uniformType, count); + //rlDisableShader(); // Avoid resetting current shader program, in case other uniforms are set + } +} + +// Set shader uniform value (matrix 4x4) +void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat) +{ + if (locIndex > -1) + { + rlEnableShader(shader.id); + rlSetUniformMatrix(locIndex, mat); + //rlDisableShader(); + } +} + +// Set shader uniform value for texture +void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture) +{ + if (locIndex > -1) + { + rlEnableShader(shader.id); + rlSetUniformSampler(locIndex, texture.id); + //rlDisableShader(); + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Screen-space Queries +//---------------------------------------------------------------------------------- + +// Get a ray trace from screen position (i.e mouse) +Ray GetScreenToWorldRay(Vector2 position, Camera camera) +{ + Ray ray = GetScreenToWorldRayEx(position, camera, GetScreenWidth(), GetScreenHeight()); + + return ray; +} + +// Get a ray trace from the screen position (i.e mouse) within a specific section of the screen +Ray GetScreenToWorldRayEx(Vector2 position, Camera camera, int width, int height) +{ + Ray ray = { 0 }; + + // Calculate normalized device coordinates + // NOTE: y value is negative + float x = (2.0f*position.x)/(float)width - 1.0f; + float y = 1.0f - (2.0f*position.y)/(float)height; + float z = 1.0f; + + // Store values in a vector + Vector3 deviceCoords = { x, y, z }; + + // Calculate view matrix from camera look at + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + + Matrix matProj = MatrixIdentity(); + + if (camera.projection == CAMERA_PERSPECTIVE) + { + // Calculate projection matrix from perspective + matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)width/(double)height), rlGetCullDistanceNear(), rlGetCullDistanceFar()); + } + else if (camera.projection == CAMERA_ORTHOGRAPHIC) + { + double aspect = (double)width/(double)height; + double top = camera.fovy/2.0; + double right = top*aspect; + + // Calculate projection matrix from orthographic + matProj = MatrixOrtho(-right, right, -top, top, rlGetCullDistanceNear(), rlGetCullDistanceFar()); + } + + // Unproject far/near points + Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); + Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); + + // Unproject the mouse cursor in the near plane + // It is needed as the source position because orthographic projects, + // compared to perspective doesn't have a convergence point, + // meaning that the "eye" of the camera is more like a plane than a point + Vector3 cameraPlanePointerPos = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, -1.0f }, matProj, matView); + + // Calculate normalized direction vector + Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); + + if (camera.projection == CAMERA_PERSPECTIVE) ray.position = camera.position; + else if (camera.projection == CAMERA_ORTHOGRAPHIC) ray.position = cameraPlanePointerPos; + + // Apply calculated vectors to ray + ray.direction = direction; + + return ray; +} + +// Get transform matrix for camera +Matrix GetCameraMatrix(Camera camera) +{ + Matrix mat = MatrixLookAt(camera.position, camera.target, camera.up); + + return mat; +} + +// Get camera 2d transform matrix +Matrix GetCameraMatrix2D(Camera2D camera) +{ + Matrix matTransform = { 0 }; + // The camera in world-space is set by + // 1. Move it to target + // 2. Rotate by -rotation and scale by (1/zoom) + // When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller), + // not for the camera getting bigger, hence the invert. Same deal with rotation + // 3. Move it by (-offset); + // Offset defines target transform relative to screen, but since effectively "moving" screen (camera) + // it needs to be moved into opposite direction (inverse transform) + + // Having camera transform in world-space, inverse of it gives the modelview transform + // Since (A*B*C)' = C'*B'*A', the modelview is + // 1. Move to offset + // 2. Rotate and Scale + // 3. Move by -target + Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f); + Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD); + Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f); + Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f); + + matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation); + + return matTransform; +} + +// Get screen space position from a 3d world space position +Vector2 GetWorldToScreen(Vector3 position, Camera camera) +{ + Vector2 screenPosition = GetWorldToScreenEx(position, camera, GetScreenWidth(), GetScreenHeight()); + + return screenPosition; +} + +// Get sized screen space position for a 3d world space position (useful for texture drawing) +Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height) +{ + // Calculate projection matrix (from perspective instead of frustum + Matrix matProj = MatrixIdentity(); + + if (camera.projection == CAMERA_PERSPECTIVE) + { + // Calculate projection matrix from perspective + matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)width/(double)height), rlGetCullDistanceNear(), rlGetCullDistanceFar()); + } + else if (camera.projection == CAMERA_ORTHOGRAPHIC) + { + double aspect = (double)width/(double)height; + double top = camera.fovy/2.0; + double right = top*aspect; + + // Calculate projection matrix from orthographic + matProj = MatrixOrtho(-right, right, -top, top, rlGetCullDistanceNear(), rlGetCullDistanceFar()); + } + + // Calculate view matrix from camera look at (and transpose it) + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + + // Convert world position vector to quaternion + Quaternion worldPos = { position.x, position.y, position.z, 1.0f }; + + // Transform world position to view + worldPos = QuaternionTransform(worldPos, matView); + + // Transform result to projection (clip space position) + worldPos = QuaternionTransform(worldPos, matProj); + + // Calculate normalized device coordinates (inverted y) + Vector3 ndcPos = { worldPos.x/worldPos.w, -worldPos.y/worldPos.w, worldPos.z/worldPos.w }; + + // Calculate 2d screen position vector + Vector2 screenPosition = { (ndcPos.x + 1.0f)/2.0f*(float)width, (ndcPos.y + 1.0f)/2.0f*(float)height }; + + return screenPosition; +} + +// Get screen space position for a 2d camera world space position +Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera) +{ + Matrix matCamera = GetCameraMatrix2D(camera); + Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, matCamera); + + return (Vector2){ transform.x, transform.y }; +} + +// Get world space position for a 2d camera screen space position +Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera) +{ + Matrix invMatCamera = MatrixInvert(GetCameraMatrix2D(camera)); + Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, invMatCamera); + + return (Vector2){ transform.x, transform.y }; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Timing +//---------------------------------------------------------------------------------- + +// NOTE: Functions with a platform-specific implementation on rcore_.c +//double GetTime(void) + +// Set target FPS (maximum) +void SetTargetFPS(int fps) +{ + if (fps < 1) CORE.Time.target = 0.0; + else CORE.Time.target = 1.0/(double)fps; + + TRACELOG(LOG_INFO, "TIMER: Target time per frame: %02.03f milliseconds", (float)CORE.Time.target*1000.0f); +} + +// Get current FPS +// NOTE: Calculating an average framerate +int GetFPS(void) +{ + int fps = 0; + +#if !SUPPORT_CUSTOM_FRAME_CONTROL + #define FPS_CAPTURE_FRAMES_COUNT 30 // 30 captures + #define FPS_AVERAGE_TIME_SECONDS 0.5f // 500 milliseconds + #define FPS_STEP (FPS_AVERAGE_TIME_SECONDS/FPS_CAPTURE_FRAMES_COUNT) + + static int index = 0; + static float history[FPS_CAPTURE_FRAMES_COUNT] = { 0 }; + static float average = 0, last = 0; + float fpsFrame = GetFrameTime(); + + // If reseting the window, reset the FPS info + if (CORE.Time.frameCounter == 0) + { + average = 0; + last = 0; + index = 0; + + for (int i = 0; i < FPS_CAPTURE_FRAMES_COUNT; i++) history[i] = 0; + } + + if (fpsFrame != 0) + { + if ((GetTime() - last) > FPS_STEP) + { + last = (float)GetTime(); + index = (index + 1)%FPS_CAPTURE_FRAMES_COUNT; + average -= history[index]; + history[index] = fpsFrame/FPS_CAPTURE_FRAMES_COUNT; + average += history[index]; + } + + fps = (int)roundf(1.0f/average); + } + else fps = 0; +#endif + + return fps; +} + +// Get time in seconds for last frame drawn (delta time) +float GetFrameTime(void) +{ + return (float)CORE.Time.frame; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Custom frame control +//---------------------------------------------------------------------------------- + +// NOTE: Functions with a platform-specific implementation on rcore_.c +//void SwapScreenBuffer(void); +//void PollInputEvents(void); + +// Wait for some time (stop program execution) +// NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could +// take longer than expected... for that reason a busy wait loop is used +// REF: http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected +// REF: http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timing on Win32! +void WaitTime(double seconds) +{ + if (seconds < 0) return; // Security check + +#if SUPPORT_BUSY_WAIT_LOOP || SUPPORT_PARTIALBUSY_WAIT_LOOP + double destinationTime = GetTime() + seconds; +#endif + +#if SUPPORT_BUSY_WAIT_LOOP + while (GetTime() < destinationTime) { } +#else + #if SUPPORT_PARTIALBUSY_WAIT_LOOP + double sleepSeconds = seconds - seconds*0.05; // NOTE: Reserve a percentage of the time for busy waiting + #else + double sleepSeconds = seconds; + #endif + + // System halt functions + #if defined(_WIN32) + Sleep((unsigned long)(sleepSeconds*1000.0)); + #endif + #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) + struct timespec req = { 0 }; + time_t sec = sleepSeconds; + long nsec = (sleepSeconds - sec)*1000000000L; + req.tv_sec = sec; + req.tv_nsec = nsec; + + // NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated + while (nanosleep(&req, &req) == -1) continue; + #endif + #if defined(__APPLE__) + usleep(sleepSeconds*1000000.0); + #endif + + #if SUPPORT_PARTIALBUSY_WAIT_LOOP + while (GetTime() < destinationTime) { } + #endif +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// NOTE: Functions with a platform-specific implementation on rcore_.c +//void OpenURL(const char *url) + +// Set the seed for the random number generator +void SetRandomSeed(unsigned int seed) +{ +#if SUPPORT_RPRAND_GENERATOR + rprand_set_seed(seed); +#else + srand(seed); +#endif +} + +// Get a random value between min and max included +int GetRandomValue(int min, int max) +{ + int value = 0; + + if (min > max) + { + int tmp = max; + max = min; + min = tmp; + } + +#if SUPPORT_RPRAND_GENERATOR + value = rprand_get_value(min, max); +#else + // WARNING: Ranges higher than RAND_MAX will return invalid results + // More specifically, if (max - min) > INT_MAX there will be an overflow, + // and otherwise if (max - min) > RAND_MAX the random value will incorrectly never exceed a certain threshold + // NOTE: Depending on the library it can be as low as 32767 + if ((unsigned int)(max - min) > (unsigned int)RAND_MAX) + { + TRACELOG(LOG_WARNING, "Invalid GetRandomValue() arguments, range should not be higher than %i", RAND_MAX); + } + + // NOTE: This one-line approach produces a non-uniform distribution, + // as stated by Donald Knuth in the book The Art of Programming, so + // using below approach for more uniform results + //value = (rand()%(abs(max - min) + 1) + min); + + // More uniform range solution + int range = (max - min) + 1; + + // Degenerate/overflow case: fall back to min (same behavior as "always min" instead of UB) + if (range <= 0) value = min; + else + { + // Rejection sampling to get a uniform integer in [min, max] + unsigned long c = (unsigned long)RAND_MAX + 1UL; // Number of possible results + unsigned long m = (unsigned long)range; // Size of the target interval + unsigned long t = c - (c%m); // Largest multiple of m <= c + unsigned long r = 0; + + for (;;) + { + r = (unsigned long)rand(); + if (r < t) break; // Only accept values within the fair region + } + + value = min + (int)(r%m); + } +#endif + + return value; +} + +// Load random values sequence, no values repeated, min and max included +int *LoadRandomSequence(unsigned int count, int min, int max) +{ + int *values = NULL; + +#if SUPPORT_RPRAND_GENERATOR + values = rprand_load_sequence(count, min, max); +#else + if (count > ((unsigned int)abs(max - min) + 1)) return values; // Security check + + values = (int *)RL_CALLOC(count, sizeof(int)); + + int value = 0; + bool dupValue = false; + + for (int i = 0; i < (int)count;) + { + value = GetRandomValue(min, max); + dupValue = false; + + for (int j = 0; j < i; j++) + { + if (values[j] == value) + { + dupValue = true; + break; + } + } + + if (!dupValue) + { + values[i] = value; + i++; + } + } +#endif + + return values; +} + +// Unload random values sequence +void UnloadRandomSequence(int *sequence) +{ +#if SUPPORT_RPRAND_GENERATOR + rprand_unload_sequence(sequence); +#else + RL_FREE(sequence); +#endif +} + +// Takes a screenshot of current screen +void TakeScreenshot(const char *fileName) +{ +#if SUPPORT_MODULE_RTEXTURES + // Security check to (partially) avoid malicious code + if (strchr(fileName, '\'') != NULL) { TRACELOG(LOG_WARNING, "SYSTEM: Provided fileName could be potentially malicious, avoid [\'] character"); return; } + + // Apply content scaling if required + Vector2 scale = { 1.0f, 1.0f }; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) scale = GetWindowScaleDPI(); + + unsigned char *imgData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); + Image image = { imgData, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y), 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; + + char path[MAX_FILEPATH_LENGTH] = { 0 }; + if (!IsPathAbsolute(fileName)) snprintf(path, MAX_FILEPATH_LENGTH, "%s", TextFormat("%s/%s", CORE.Storage.basePath, fileName)); + else snprintf(path, MAX_FILEPATH_LENGTH, "%s", fileName); + + ExportImage(image, path); // WARNING: Module required: rtextures + RL_FREE(imgData); + + if (FileExists(path)) TRACELOG(LOG_INFO, "SYSTEM: [%s] Screenshot taken successfully", path); + else TRACELOG(LOG_WARNING, "SYSTEM: [%s] Screenshot could not be saved", path); +#else + TRACELOG(LOG_WARNING,"IMAGE: ExportImage() requires module: rtextures"); +#endif +} + +// Set up window configuration flags (view FLAGS) +// NOTE: This function is expected to be called before window creation, +// because it sets up some flags for the window creation process +// To configure window states after creation, use SetWindowState() +void SetConfigFlags(unsigned int flags) +{ + if (CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetConfigFlags called after window initialization, Use \"SetWindowState\" to set flags instead"); + + // Selected flags are set but not evaluated at this point, + // flag evaluation happens at InitWindow() or SetWindowState() + FLAG_SET(CORE.Window.flags, flags); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Logging system +//---------------------------------------------------------------------------------- +// Set the current threshold (minimum) log level +void SetTraceLogLevel(int logType) { logTypeLevel = logType; } + +// Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) +void TraceLog(int logType, const char *text, ...) +{ +#if SUPPORT_TRACELOG + // Message has level below current threshold, don't emit + if ((logType < logTypeLevel) || (text == NULL)) return; + + va_list args; + va_start(args, text); + + if (traceLog) + { + traceLog(logType, text, args); + va_end(args); + return; + } + +#if defined(PLATFORM_ANDROID) + switch (logType) + { + case LOG_TRACE: __android_log_vprint(ANDROID_LOG_VERBOSE, "raylib", text, args); break; + case LOG_DEBUG: __android_log_vprint(ANDROID_LOG_DEBUG, "raylib", text, args); break; + case LOG_INFO: __android_log_vprint(ANDROID_LOG_INFO, "raylib", text, args); break; + case LOG_WARNING: __android_log_vprint(ANDROID_LOG_WARN, "raylib", text, args); break; + case LOG_ERROR: __android_log_vprint(ANDROID_LOG_ERROR, "raylib", text, args); break; + case LOG_FATAL: __android_log_vprint(ANDROID_LOG_FATAL, "raylib", text, args); break; + default: break; + } +#else + char buffer[MAX_TRACELOG_MSG_LENGTH] = { 0 }; + + switch (logType) + { + case LOG_TRACE: memcpy(buffer, "TRACE: ", 7); break; + case LOG_DEBUG: memcpy(buffer, "DEBUG: ", 7); break; + case LOG_INFO: memcpy(buffer, "INFO: ", 6); break; + case LOG_WARNING: memcpy(buffer, "WARNING: ", 9); break; + case LOG_ERROR: memcpy(buffer, "ERROR: ", 7); break; + case LOG_FATAL: memcpy(buffer, "FATAL: ", 7); break; + default: break; + } + + unsigned int textLength = (unsigned int)strlen(text); + memcpy(buffer + strlen(buffer), text, (textLength < (MAX_TRACELOG_MSG_LENGTH - 12))? textLength : (MAX_TRACELOG_MSG_LENGTH - 12)); + strcat(buffer, "\n"); + vprintf(buffer, args); + fflush(stdout); +#endif + + va_end(args); + + if (logType == LOG_FATAL) exit(EXIT_FAILURE); // If fatal logging, exit program +#endif +} + +// Set custom trace log +void SetTraceLogCallback(TraceLogCallback callback) +{ + traceLog = callback; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Memory management +//---------------------------------------------------------------------------------- +// Internal memory allocator +// NOTE: Initializes to zero by default +void *MemAlloc(unsigned int size) +{ + void *ptr = RL_CALLOC(size, 1); + return ptr; +} + +// Internal memory reallocator +void *MemRealloc(void *ptr, unsigned int size) +{ + void *ret = RL_REALLOC(ptr, size); + return ret; +} + +// Internal memory free +void MemFree(void *ptr) +{ + RL_FREE(ptr); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: File System management +//---------------------------------------------------------------------------------- +// Load data from file into a buffer +unsigned char *LoadFileData(const char *fileName, int *dataSize) +{ + unsigned char *data = NULL; + *dataSize = 0; + + if (fileName != NULL) + { + if (loadFileData) return loadFileData(fileName, dataSize); + + FILE *file = fopen(fileName, "rb"); + + if (file != NULL) + { + // WARNING: On binary streams SEEK_END could not be found, + // using fseek() and ftell() could not work in some (rare) cases + fseek(file, 0, SEEK_END); + int size = ftell(file); // WARNING: ftell() returns 'long int', maximum size returned is INT_MAX (2147483647 bytes) + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + data = (unsigned char *)RL_CALLOC(size, sizeof(unsigned char)); + + if (data != NULL) + { + // NOTE: fread() returns number of read elements instead of bytes, so reading [1 byte, size elements] + size_t count = fread(data, sizeof(unsigned char), size, file); + + // WARNING: fread() returns a size_t value, usually 'unsigned int' (32bit compilation) and 'unsigned long long' (64bit compilation) + // dataSize is unified along raylib as a 'int' type, so, for file-sizes >INT_MAX (2147483647 bytes) there is a limitation + if (count > 2147483647) + { + TRACELOG(LOG_WARNING, "FILEIO: [%s] File is bigger than 2147483647 bytes, avoid using LoadFileData()", fileName); + + RL_FREE(data); + data = NULL; + } + else + { + *dataSize = (int)count; + + if ((*dataSize) != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded (%i bytes out of %i)", fileName, *dataSize, size); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName); + } + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to allocated memory for file reading", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return data; +} + +// Unload file data allocated by LoadFileData() +void UnloadFileData(unsigned char *data) +{ + RL_FREE(data); +} + +// Save data to file from buffer +bool SaveFileData(const char *fileName, const void *data, int dataSize) +{ + bool result = false; + + if (fileName != NULL) + { + if (saveFileData) return saveFileData(fileName, data, dataSize); + + FILE *file = fopen(fileName, "wb"); + + if (file != NULL) + { + // WARNING: fwrite() returns a size_t value, usually 'unsigned int' (32bit compilation) and 'unsigned long long' (64bit compilation) + // and expects a size_t input value but as dataSize is limited to INT_MAX (2147483647 bytes), there shouldn't be a problem + int count = (int)fwrite(data, sizeof(unsigned char), dataSize, file); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName); + else if (count != dataSize) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName); + + int closed = fclose(file); + if (closed == 0) result = true; + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return result; +} + +// Export data to code (.h), returns true on success +bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName) +{ + bool result = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + // NOTE: Text data buffer size is estimated considering raw data size in bytes + // and requiring 6 char bytes for every byte: "0x00, " + char *txtData = (char *)RL_CALLOC(dataSize*6 + 2000, sizeof(char)); + + int byteCount = 0; + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// DataAsCode exporter v1.0 - Raw data exported as an array of bytes //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2022-2026 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path + char varFileName[256] = { 0 }; + snprintf(varFileName, 256, "%s", GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) + { + // Convert variable name to uppercase + if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } + // Replace non valid character for C identifier with '_' + else if (varFileName[i] == '.' || varFileName[i] == '-' || varFileName[i] == '?' || varFileName[i] == '!' || varFileName[i] == '+') { varFileName[i] = '_'; } + } + + byteCount += sprintf(txtData + byteCount, "#define %s_DATA_SIZE %i\n\n", varFileName, dataSize); + + byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%s_DATA_SIZE] = { ", varFileName, varFileName); + for (int i = 0; i < (dataSize - 1); i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), data[i]); + byteCount += sprintf(txtData + byteCount, "0x%x };\n", data[dataSize - 1]); + + // NOTE: Text data size exported is determined by '\0' (NULL) character + result = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + if (result != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Data as code exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export data as code", fileName); + + return result; +} + +// Load text data from file, returns a '\0' terminated string +// NOTE: text chars array should be freed manually +char *LoadFileText(const char *fileName) +{ + char *text = NULL; + + if (fileName != NULL) + { + if (loadFileText) return loadFileText(fileName); + + FILE *file = fopen(fileName, "rt"); + + if (file != NULL) + { + // WARNING: When reading a file as 'text' file, + // text mode causes carriage return-linefeed translation... + // ...but using fseek() should return correct byte-offset + fseek(file, 0, SEEK_END); + unsigned int size = (unsigned int)ftell(file); + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + text = (char *)RL_CALLOC(size + 1, sizeof(char)); + + if (text != NULL) + { + unsigned int count = (unsigned int)fread(text, sizeof(char), size, file); + + // WARNING: \r\n is converted to \n on reading, so, + // read bytes count gets reduced by the number of lines + if (count < size) text = (char *)RL_REALLOC(text, count + 1); + + // Zero-terminate the string + text[count] = '\0'; + + TRACELOG(LOG_INFO, "FILEIO: [%s] Text file loaded successfully", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to allocated memory for file reading", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read text file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return text; +} + +// Unload file text data allocated by LoadFileText() +void UnloadFileText(char *text) +{ + RL_FREE(text); +} + +// Save text data to file (write), string must be '\0' terminated +bool SaveFileText(const char *fileName, const char *text) +{ + bool result = false; + + if (fileName != NULL) + { + if (saveFileText) return saveFileText(fileName, text); + + FILE *file = fopen(fileName, "wt"); + + if (file != NULL) + { + int count = fprintf(file, "%s", text); + + if (count < 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); + + int closed = fclose(file); + if (closed == 0) result = true; + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return result; +} + +// File access custom callbacks +// WARNING: Callbacks setup is intended for advanced users + +// Set custom file binary data loader +void SetLoadFileDataCallback(LoadFileDataCallback callback) +{ + loadFileData = callback; +} + +// Set custom file binary data saver +void SetSaveFileDataCallback(SaveFileDataCallback callback) +{ + saveFileData = callback; +} + +// Set custom file text data loader +void SetLoadFileTextCallback(LoadFileTextCallback callback) +{ + loadFileText = callback; +} + +// Set custom file text data saver +void SetSaveFileTextCallback(SaveFileTextCallback callback) +{ + saveFileText = callback; +} + +// Rename file (if exists) +// NOTE: Only rename file name required, not full path +int FileRename(const char *fileName, const char *fileRename) +{ + int result = -1; + + if (FileExists(fileName)) + { + result = rename(fileName, fileRename); + } + + return result; +} + +// Remove file (if exists) +int FileRemove(const char *fileName) +{ + int result = -1; + + if (FileExists(fileName)) + { + result = remove(fileName); + } + + return result; +} + +// Copy file from one path to another +// NOTE: If destination path does not exist, it is created! +int FileCopy(const char *srcPath, const char *dstPath) +{ + int result = -1; + int srcDataSize = 0; + unsigned char *srcFileData = LoadFileData(srcPath, &srcDataSize); + + // Create required paths if they do not exist + if (DirectoryExists(GetDirectoryPath(dstPath))) result = 0; // Already exists + else result = MakeDirectory(GetDirectoryPath(dstPath)); + + if (result == 0) // Directory created successfully or already exists + { + if ((srcFileData != NULL) && (srcDataSize > 0)) + { + bool saved = SaveFileData(dstPath, srcFileData, srcDataSize); + if (saved) result = 0; + } + } + + UnloadFileData(srcFileData); + + return result; +} + +// Move file from one directory to another +// NOTE: If dst directories do not exists they are created +int FileMove(const char *srcPath, const char *dstPath) +{ + int result = -1; + + if (FileExists(srcPath)) + { + result = FileCopy(srcPath, dstPath); + + if (result == 0) + { + // Make sure file has been correctly copied before removing + if (FileExists(dstPath) && (GetFileLength(srcPath) == GetFileLength(dstPath))) + { + result = FileRemove(srcPath); + if (result != 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to remove source file after copy", srcPath); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to copy file to [%s]", srcPath, dstPath); + } + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Source file does not exist", srcPath); + + return result; +} + +// Replace text in an existing file +// WARNING: DEPENDENCY: [rtext] module +int FileTextReplace(const char *fileName, const char *search, const char *replacement) +{ + int result = -1; + +#if SUPPORT_MODULE_RTEXT + char *fileText = NULL; + char *fileTextUpdated = { 0 }; + + if (FileExists(fileName)) + { + fileText = LoadFileText(fileName); + fileTextUpdated = TextReplaceAlloc(fileText, search, replacement); + bool saved = SaveFileText(fileName, fileTextUpdated); + if (saved) result = 0; + MemFree(fileTextUpdated); + UnloadFileText(fileText); + } +#else + TRACELOG(LOG_WARNING, "FILE: File text replace requires [rtext] module"); +#endif + + return result; +} + +// Find text index position in existing file +// WARNING: DEPENDENCY: [rtext] module +int FileTextFindIndex(const char *fileName, const char *search) +{ + int result = -1; + + if (FileExists(fileName)) + { + char *fileText = LoadFileText(fileName); + char *ptr = strstr(fileText, search); + if (ptr != NULL) result = (int)(ptr - fileText); + UnloadFileText(fileText); + } + + return result; +} + +// Check if the file exists +bool FileExists(const char *fileName) +{ + bool result = false; + + if ((fileName != NULL) && (ACCESS(fileName) != -1)) result = true; + + // NOTE: Alternatively, stat() can be used instead of access() + //#include + //struct stat statbuf; + //if (stat(filename, &statbuf) == 0) result = true; + + return result; +} + +// Check file extension +bool IsFileExtension(const char *fileName, const char *ext) +{ + #define MAX_FILE_EXTENSIONS 32 + + bool result = false; + const char *fileExt = GetFileExtension(fileName); + + // WARNING: fileExt points to last '.' on fileName string but it could happen + // that fileName is not correct: "myfile.png more text following\n" + + if (fileExt != NULL) + { + int fileExtLength = (int)strlen(fileExt); + char fileExtLower[16] = { 0 }; + for (int i = 0; (i < fileExtLength) && (i < 15); i++) + { + // Copy and convert to lower-case + if ((fileExt[i] >= 'A') && (fileExt[i] <= 'Z')) fileExtLower[i] = fileExt[i] + 32; + else fileExtLower[i] = fileExt[i]; + } + + int extCount = 1; + int extLength = (int)strlen(ext); + char *extList = (char *)RL_CALLOC(extLength + 1, 1); + char *extListPtrs[MAX_FILE_EXTENSIONS] = { 0 }; + memcpy(extList, ext, extLength); + extListPtrs[0] = extList; + + for (int i = 0; i < extLength; i++) + { + // Convert to lower-case if extension is upper-case + if ((extList[i] >= 'A') && (extList[i] <= 'Z')) extList[i] += 32; + + // Get pointer to next extension and add null-terminator + if (extList[i] == ';') + { + extList[i] = '\0'; + + if (extCount < MAX_FILE_EXTENSIONS) + { + extListPtrs[extCount] = extList + i + 1; + extCount++; + } + } + } + + for (int i = 0; i < extCount; i++) + { + // Consider the case where extension provided + // does not start with the '.' + char *fileExtLowerPtr = fileExtLower; + if (extListPtrs[i][0] != '.') fileExtLowerPtr++; + + if (strcmp(fileExtLowerPtr, extListPtrs[i]) == 0) + { + result = true; + break; + } + } + + RL_FREE(extList); + } + + return result; +} + +// Check if file path (file or directory) is hidden by OS +bool IsFileHidden(const char *filePath) +{ + bool result = false; +#if defined(_WIN32) + unsigned long attribs = GetFileAttributesA(filePath); + + // Check !INVALID_FILE_ATTRIBUTES and FILE_ATTRIBUTE_HIDDEN + if ((attribs != -1) && ((attribs & 0x2UL) != 0)) result = true; +#else + const char *basePath = strrchr(filePath, '/'); + basePath = (basePath? basePath + 1 : filePath); + + if ((basePath[0] == '.') && (strcmp(basePath, ".") != 0) && (strcmp(basePath, "..") != 0)) result = true; +#endif + return result; +} + +// Check if directory path exists +bool DirectoryExists(const char *dirPath) +{ + bool result = false; + DIR *dir = opendir(dirPath); + + if (dir != NULL) + { + result = true; + closedir(dir); + } + + return result; +} + +// Get file length in bytes +// NOTE: GetFileSize() conflicts with windows.h +int GetFileLength(const char *fileName) +{ + int size = 0; + + // NOTE: On Unix-like systems, it can by used the POSIX system call: stat(), + // but depending on the platform that call could not be available + //struct stat result = { 0 }; + //stat(fileName, &result); + //return result.st_size; + + FILE *file = fopen(fileName, "rb"); + + if (file != NULL) + { + fseek(file, 0L, SEEK_END); + long int fileSize = ftell(file); + + // Check for size overflow (INT_MAX) + if (fileSize > 2147483647) TRACELOG(LOG_WARNING, "[%s] File size overflows expected limit, do not use GetFileLength()", fileName); + else size = (int)fileSize; + + fclose(file); + } + + return size; +} + +// Get file modification time (last write time) +long GetFileModTime(const char *fileName) +{ + struct stat result = { 0 }; + long modTime = 0; + + if (stat(fileName, &result) == 0) + { + time_t mod = result.st_mtime; + modTime = (long)mod; + } + + return modTime; +} + +// Get pointer to extension for a filename string (includes the dot: .png) +// WARNING: Getting the pointer to the input string extension position (not a string copy) +const char *GetFileExtension(const char *fileName) +{ + const char *dot = strrchr(fileName, '.'); + + if (!dot || (dot == fileName)) return NULL; + + return dot; +} + +// String pointer reverse break: returns right-most occurrence of charset in s +static const char *strprbrk(const char *text, const char *charset) +{ + const char *latestMatch = NULL; + + for (; (text != NULL) && (text = strpbrk(text, charset)); latestMatch = text++) { } + + return latestMatch; +} + +// Get pointer to filename for a path string +const char *GetFileName(const char *filePath) +{ + const char *fileName = NULL; + + if (filePath != NULL) fileName = strprbrk(filePath, "\\/"); + + if (fileName == NULL) return filePath; + + return fileName + 1; +} + +// Get filename string without extension (uses static string) +const char *GetFileNameWithoutExt(const char *filePath) +{ + #define MAX_FILENAME_LENGTH 256 + + static char fileName[MAX_FILENAME_LENGTH] = { 0 }; + memset(fileName, 0, MAX_FILENAME_LENGTH); + + if (filePath != NULL) + { + snprintf(fileName, MAX_FILENAME_LENGTH, "%s", GetFileName(filePath)); // Get filename.ext without path + int fileNameLength = (int)strlen(fileName); // Get size in bytes + + for (int i = fileNameLength; i > 0; i--) // Reverse search '.' + { + if (fileName[i] == '.') + { + // NOTE: Break on first '.' found + fileName[i] = '\0'; + break; + } + } + } + + return fileName; +} + +// Get directory for a provided filePath +const char *GetDirectoryPath(const char *filePath) +{ + /* + // NOTE: Directory separator is different in Windows and other platforms, + // fortunately, Windows also support the '/' separator, that's the one should be used + #if defined(_WIN32) + char separator = '\\'; + #else + char separator = '/'; + #endif + */ + const char *lastSlash = NULL; + static char dirPath[MAX_FILEPATH_LENGTH] = { 0 }; + memset(dirPath, 0, MAX_FILEPATH_LENGTH); + + // In case provided path does not contain a root drive letter (C:\, D:\) + // nor leading path separator (\, /), add the current directory path to dirPath + if ((filePath[1] != ':') && (filePath[0] != '\\') && (filePath[0] != '/')) + { + // For security, set starting path to current directory, + // obtained path will be concatenated to this + dirPath[0] = '.'; + dirPath[1] = '/'; + } + + lastSlash = strprbrk(filePath, "\\/"); + if (lastSlash) + { + if (lastSlash == filePath) + { + // The last and only slash is the leading one: path is in a root directory + dirPath[0] = filePath[0]; + dirPath[1] = '\0'; + } + else + { + char *dirPathPtr = dirPath; + if ((filePath[1] != ':') && (filePath[0] != '\\') && (filePath[0] != '/')) dirPathPtr += 2; // Skip drive letter, "C:" + memcpy(dirPathPtr, filePath, strlen(filePath) - (strlen(lastSlash) - 1)); + dirPath[strlen(filePath) - strlen(lastSlash) + (((filePath[1] != ':') && (filePath[0] != '\\') && (filePath[0] != '/'))? 2 : 0)] = '\0'; // Add '\0' manually + } + } + + return dirPath; +} + +// Get previous directory path for a provided path +const char *GetPrevDirectoryPath(const char *dirPath) +{ + static char prevDirPath[MAX_FILEPATH_LENGTH] = { 0 }; + memset(prevDirPath, 0, MAX_FILEPATH_LENGTH); + + const int lastIndex = (int)strlen(dirPath) - 1; + bool isFile = IsPathFile(dirPath); + for (int i = lastIndex; i >= 0; i--) + { + // If the character is a path separator. + if ((dirPath[i] == '\\') || (dirPath[i] == '/')) + { + // If this character is a leading '/' (e.g. the '/' in "/usr") or + // part of a drive root (e.g. "C:\"), include it with the result. + if ((i == 0) || ((i == 2) && (dirPath[1] == ':'))) i += 1; + // If this character is a trailing path separator (e.g. the last + // '/' in "/usr/bin/" or the last '\' in "C:\raylib\"), continue. + else if (i == lastIndex) continue; + + if (!isFile) + { + memcpy(prevDirPath, dirPath, i); + break; + } + else isFile = false; + } + } + + return prevDirPath; +} + +// Get current working directory +const char *GetWorkingDirectory(void) +{ + static char currentDir[MAX_FILEPATH_LENGTH] = { 0 }; + memset(currentDir, 0, MAX_FILEPATH_LENGTH); + + char *path = GETCWD(currentDir, MAX_FILEPATH_LENGTH); + + return path; +} + +const char *GetApplicationDirectory(void) +{ + static char appDir[MAX_FILEPATH_LENGTH] = { 0 }; + memset(appDir, 0, MAX_FILEPATH_LENGTH); + +#if defined(_WIN32) + int len = 0; + + #if defined(UNICODE) + unsigned short widePath[MAX_PATH]; + len = GetModuleFileNameW(NULL, (wchar_t *)widePath, MAX_PATH); + len = WideCharToMultiByte(0, 0, (wchar_t *)widePath, len, appDir, MAX_PATH, NULL, NULL); + #else + len = GetModuleFileNameA(NULL, appDir, MAX_PATH); + #endif + + if (len > 0) + { + for (int i = len; i >= 0; i--) + { + if (appDir[i] == '\\') + { + appDir[i + 1] = '\0'; + break; + } + } + } + else + { + appDir[0] = '.'; + appDir[1] = '\\'; + } + +#elif defined(__linux__) + + unsigned int size = sizeof(appDir); + ssize_t len = readlink("/proc/self/exe", appDir, size); + + if (len > 0) + { + for (int i = len; i >= 0; i--) + { + if (appDir[i] == '/') + { + appDir[i + 1] = '\0'; + break; + } + } + } + else + { + appDir[0] = '.'; + appDir[1] = '/'; + } + +#elif defined(__APPLE__) + + uint32_t size = sizeof(appDir); + + if (_NSGetExecutablePath(appDir, &size) == 0) + { + int appDirLength = (int)strlen(appDir); + for (int i = appDirLength; i >= 0; i--) + { + if (appDir[i] == '/') + { + appDir[i + 1] = '\0'; + break; + } + } + } + else + { + appDir[0] = '.'; + appDir[1] = '/'; + } + +#elif defined(__FreeBSD__) + + size_t size = sizeof(appDir); + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + + if (sysctl(mib, 4, appDir, &size, NULL, 0) == 0) + { + int appDirLength = (int)strlen(appDir); + for (int i = appDirLength; i >= 0; i--) + { + if (appDir[i] == '/') + { + appDir[i + 1] = '\0'; + break; + } + } + } + else + { + appDir[0] = '.'; + appDir[1] = '/'; + } + +#elif defined(__wasm__) + + appDir[0] = '/'; +#endif + + return appDir; +} + +// Load directory filepaths +// NOTE: Base path is prepended to the scanned filepaths +// WARNING: Directory is scanned twice, first time to get paths count +// Scanneed files and directories, no recursive/subdirs scanning +FilePathList LoadDirectoryFiles(const char *dirPath) +{ + return LoadDirectoryFilesEx(dirPath, FILE_FILTER_TAG_ALL, false); +} + +// Load directory filepaths with extension filtering and recursive directory scan +// Use "*.*" to include all files and directories on scan +// Use "FILES*" to include only files on scan +// Use "DIRS*" to include only directories on scan +// WARNING: Directory is scanned twice, first time to get paths count +FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs) +{ + FilePathList files = { 0 }; + + if (DirectoryExists(basePath)) // It's a directory + { + if ((filter != NULL) && (filter[0] == '\0')) filter = NULL; + + // SCAN 1: Count files + unsigned int fileCounter = GetDirectoryFileCountEx(basePath, filter, scanSubdirs); + + // Memory allocation for dirFileCount + files.paths = (char **)RL_CALLOC(fileCounter, sizeof(char *)); + for (unsigned int i = 0; i < fileCounter; i++) files.paths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + + // SCAN 2: Read filepaths + // WARNING: basePath is always prepended to scanned paths + ScanDirectoryFiles(basePath, &files, filter, fileCounter, scanSubdirs); + + // Security check: read files.count should match fileCounter + if (files.count != fileCounter) + { + TRACELOG(LOG_WARNING, "FILEIO: Read files count (%u) does not match capacity allocated (%u)", files.count, fileCounter); + files.count = fileCounter; // Avoid memory leak when unloading this FilePathList + } + } + else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); // Maybe it's a file... + + return files; +} + +// Unload directory filepaths +// WARNING: files.count is not reseted to 0 after unloading +void UnloadDirectoryFiles(FilePathList files) +{ + if (files.paths != NULL) + { + for (unsigned int i = 0; i < files.count; i++) RL_FREE(files.paths[i]); + + RL_FREE(files.paths); + } +} + +// Create directories (including full path requested), returns 0 on success +int MakeDirectory(const char *dirPath) +{ + if ((dirPath == NULL) || (dirPath[0] == '\0')) return -1; // Path is not valid + if (DirectoryExists(dirPath)) return 0; // Path already exists (is valid) + + // Copy path string to avoid modifying original + int dirPathLength = (int)strlen(dirPath) + 1; + char *pathcpy = (char *)RL_CALLOC(dirPathLength, 1); + memcpy(pathcpy, dirPath, dirPathLength); + + // Iterate over pathcpy, create each subdirectory as needed + for (int i = 0; (i < dirPathLength) && (pathcpy[i] != '\0'); i++) + { + if (pathcpy[i] == ':') i++; + else + { + if ((pathcpy[i] == '\\') || (pathcpy[i] == '/')) + { + pathcpy[i] = '\0'; + if (!DirectoryExists(pathcpy)) MKDIR(pathcpy); + pathcpy[i] = '/'; + } + } + } + + // Create final directory + if (!DirectoryExists(pathcpy)) MKDIR(pathcpy); + RL_FREE(pathcpy); + + // In case something failed and requested directory + // was not successfully created, return -1 + if (!DirectoryExists(dirPath)) return -1; + + return 0; +} + +// Change working directory, returns true on success +int ChangeDirectory(const char *dirPath) +{ + // NOTE: On success, CHDIR() return 0; on error, returns -1 and errno is set to indicate the error, + // depending on the filesystem, other errors can be returned + int result = CHDIR(dirPath); + + if (result != 0) TRACELOG(LOG_WARNING, "SYSTEM: Failed to change to directory: %s", dirPath); + else TRACELOG(LOG_INFO, "SYSTEM: Working Directory: %s", dirPath); + + return result; +} + +// Check if provided path point to a file +bool IsPathFile(const char *path) +{ + bool result = false; + + struct stat info = { 0 }; + stat(path, &info); + + if (S_ISREG(info.st_mode)) result = true; + + return result; +} + +// Check if provided path point to a directory +bool IsPathDirectory(const char *path) +{ + bool result = false; + + if (!IsPathFile(path)) result = true; + + return result; +} + +// Check if provided path is an absolute path +bool IsPathAbsolute(const char *path) +{ + int result = false; + + if ((path != NULL) && (path[0] != '\0')) + { +#if defined(_WIN32) + // Check UNC path (\\server\share) + if ((path[0] == '\\') && (path[1] == '\\')) result = true; + // Check path starts with a drive letter (e.g. C:\ or D:/) + else if ((((path[0] >= 'A') && (path[0] <= 'Z')) || ((path[0] >= 'a') && (path[0] <= 'z'))) && + (path[1] != '\0') && (path[1] == ':') && (path[2] != '\0') && ((path[2] == '\\') || (path[2] == '/'))) result = true; +#else + // Check POSIX path, must start with / + if (path[0] == '/') result = true; +#endif + } + + return result; +} + +// Check if fileName is valid for the platform/OS +bool IsFileNameValid(const char *fileName) +{ + bool valid = true; + + if ((fileName != NULL) && (fileName[0] != '\0')) + { + int fileNameLength = (int)strlen(fileName); + bool allPeriods = true; + + for (int i = 0; i < fileNameLength; i++) + { + // Check invalid characters + if ((fileName[i] == '<') || + (fileName[i] == '>') || + (fileName[i] == ':') || + (fileName[i] == '\"') || + (fileName[i] == '/') || + (fileName[i] == '\\') || + (fileName[i] == '|') || + (fileName[i] == '?') || + (fileName[i] == '*')) { valid = false; break; } + + // Check non-glyph characters + if ((unsigned char)fileName[i] < 32) { valid = false; break; } + + // Check if filename is not all periods + if (fileName[i] != '.') allPeriods = false; + } + + if (allPeriods) valid = false; + +/* + if (valid) + { + // Check invalid DOS names + if (fileNameLength >= 3) + { + if (((fileName[0] == 'C') && (fileName[1] == 'O') && (fileName[2] == 'N')) || // CON + ((fileName[0] == 'P') && (fileName[1] == 'R') && (fileName[2] == 'N')) || // PRN + ((fileName[0] == 'A') && (fileName[1] == 'U') && (fileName[2] == 'X')) || // AUX + ((fileName[0] == 'N') && (fileName[1] == 'U') && (fileName[2] == 'L'))) valid = false; // NUL + } + + if (fileNameLength >= 4) + { + if (((fileName[0] == 'C') && (fileName[1] == 'O') && (fileName[2] == 'M') && ((fileName[3] >= '0') && (fileName[3] <= '9'))) || // COM0-9 + ((fileName[0] == 'L') && (fileName[1] == 'P') && (fileName[2] == 'T') && ((fileName[3] >= '0') && (fileName[3] <= '9')))) valid = false; // LPT0-9 + } + } +*/ + } + + return valid; +} + +// Check if file has been dropped into window +bool IsFileDropped(void) +{ + bool result = false; + + if (CORE.Window.dropFileCount > 0) result = true; + + return result; +} + +// Load dropped filepaths +FilePathList LoadDroppedFiles(void) +{ + FilePathList files = { 0 }; + + files.count = CORE.Window.dropFileCount; + files.paths = CORE.Window.dropFilepaths; + + return files; +} + +// Unload dropped filepaths +void UnloadDroppedFiles(FilePathList files) +{ + // WARNING: files pointers are the same as internal ones + + if (files.count > 0) + { + for (unsigned int i = 0; i < files.count; i++) RL_FREE(files.paths[i]); + + RL_FREE(files.paths); + + CORE.Window.dropFileCount = 0; + CORE.Window.dropFilepaths = NULL; + } +} + +// Get the file count in a directory +unsigned int GetDirectoryFileCount(const char *dirPath) +{ + return GetDirectoryFileCountEx(dirPath, FILE_FILTER_TAG_ALL, false); +} + +// Get the file count in a directory with extension filtering and recursive directory scan. Use 'FILE_FILTER_TAG_DIR_ONLY' in the filter string to include directories in the result +unsigned int GetDirectoryFileCountEx(const char *basePath, const char *filter, bool scanSubdirs) +{ + unsigned int fileCounter = 0; + + // WARNING: Path can not be static or it will be reused between recursive function calls! + char path[MAX_FILEPATH_LENGTH] = { 0 }; + memset(path, 0, MAX_FILEPATH_LENGTH); + + struct dirent *entity; + DIR *dir = opendir(basePath); + + if (dir != NULL) // It's a directory + { + while ((entity = readdir(dir)) != NULL) + { + // NOTE: Skipping '.' (current dir) and '..' (parent dir) filepaths + if ((strcmp(entity->d_name, ".") != 0) && (strcmp(entity->d_name, "..") != 0)) + { + // Construct new path from our base path + #if defined(_WIN32) + int pathLength = snprintf(path, MAX_FILEPATH_LENGTH, "%s\\%s", basePath, entity->d_name); + #else + int pathLength = snprintf(path, MAX_FILEPATH_LENGTH, "%s/%s", basePath, entity->d_name); + #endif + // Don't add to count if path too long + if ((pathLength < 0) || (pathLength >= MAX_FILEPATH_LENGTH)) + { + TRACELOG(LOG_WARNING, "FILEIO: Path longer than %d characters (%s...)", MAX_FILEPATH_LENGTH, basePath); + } + else if (IsPathFile(path)) + { + if ((filter == NULL) || (strstr(filter, FILE_FILTER_TAG_ALL) != NULL) || + (strstr(filter, FILE_FILTER_TAG_FILE_ONLY) != NULL) || IsFileExtension(path, filter)) fileCounter++; + } + else + { + if ((filter != NULL) && ((strstr(filter, FILE_FILTER_TAG_ALL) != NULL) || (strstr(filter, FILE_FILTER_TAG_DIR_ONLY) != NULL))) fileCounter++; + if (scanSubdirs) fileCounter += GetDirectoryFileCountEx(path, filter, scanSubdirs); + } + } + } + closedir(dir); + } + else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); // Maybe it's a file... + return fileCounter; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Compression and Encoding +//---------------------------------------------------------------------------------- + +// Compress data (DEFLATE algorithm) +unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize) +{ + #define COMPRESSION_QUALITY_DEFLATE 8 + + unsigned char *compData = NULL; + +#if SUPPORT_COMPRESSION_API + // Compress data and generate a valid DEFLATE stream + struct sdefl *sdefl = (struct sdefl *)RL_CALLOC(1, sizeof(struct sdefl)); // WARNING: Possible stack overflow, struct sdefl is almost 1MB + int bounds = sdefl_bound(dataSize); + compData = (unsigned char *)RL_CALLOC(bounds, 1); + + *compDataSize = sdeflate(sdefl, compData, data, dataSize, COMPRESSION_QUALITY_DEFLATE); // Compression level 8, same as stbiw + RL_FREE(sdefl); + + TRACELOG(LOG_INFO, "SYSTEM: Compress data: Original size: %i -> Comp. size: %i", dataSize, *compDataSize); +#endif + + return compData; +} + +// Decompress data (DEFLATE algorithm) +unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize) +{ + unsigned char *data = NULL; + +#if SUPPORT_COMPRESSION_API + // Decompress data from a valid DEFLATE stream + unsigned char *data0 = (unsigned char *)RL_CALLOC(MAX_DECOMPRESSION_SIZE*1024*1024, 1); + int size = sinflate(data0, MAX_DECOMPRESSION_SIZE*1024*1024, compData, compDataSize); + + // WARNING: RL_REALLOC can make (and leave) data copies in memory, + // that can be a security concern in case of compression of sensitive data + // So, using a second buffer to copy data manually, wiping original buffer memory + data = (unsigned char *)RL_CALLOC(size, 1); + memcpy(data, data0, size); + memset(data0, 0, MAX_DECOMPRESSION_SIZE*1024*1024); // Wipe memory, is memset() safe? + RL_FREE(data0); + + TRACELOG(LOG_INFO, "SYSTEM: Decompress data: Comp. size: %i -> Original size: %i", compDataSize, size); + + *dataSize = size; +#endif + + return data; +} + +// Encode data to Base64 string +// NOTE: Returned string includes NULL terminator, considered on outputSize +char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) +{ + // Base64 conversion table from RFC 4648 [0..63] + // NOTE: They represent 64 values (6 bits), to encode 3 bytes of data into 4 "sextets" (6bit characters) + static const char base64EncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + // Compute expected size and padding + int paddedSize = dataSize; + while (paddedSize%3 != 0) paddedSize++; // Padding bytes to round 4*(dataSize/3) to 4 bytes + int estimatedOutputSize = 4*(paddedSize/3); + int padding = paddedSize - dataSize; + + // Adding null terminator to string + estimatedOutputSize += 1; + + // Load some memory to store encoded string + char *encodedData = (char *)RL_CALLOC(estimatedOutputSize, 1); + if (encodedData == NULL) return NULL; + + int outputCount = 0; + for (int i = 0; i < dataSize;) + { + unsigned int octetA = 0; + unsigned int octetB = 0; + unsigned int octetC = 0; + unsigned int octetPack = 0; + + octetA = data[i]; // Generates 2 sextets + octetB = ((i + 1) < dataSize)? data[i + 1] : 0; // Generates 3 sextets + octetC = ((i + 2) < dataSize)? data[i + 2] : 0; // Generates 4 sextets + + octetPack = (octetA << 16) | (octetB << 8) | octetC; + + encodedData[outputCount + 0] = (unsigned char)(base64EncodeTable[(octetPack >> 18) & 0x3f]); + encodedData[outputCount + 1] = (unsigned char)(base64EncodeTable[(octetPack >> 12) & 0x3f]); + encodedData[outputCount + 2] = (unsigned char)(base64EncodeTable[(octetPack >> 6) & 0x3f]); + encodedData[outputCount + 3] = (unsigned char)(base64EncodeTable[octetPack & 0x3f]); + outputCount += 4; + i += 3; + } + + // Add required padding bytes + for (int p = 0; p < padding; p++) encodedData[outputCount - p - 1] = '='; + + // Add null terminator to string + encodedData[outputCount] = '\0'; + outputCount++; + + if (outputCount != estimatedOutputSize) TRACELOG(LOG_WARNING, "BASE64: Output size differs from estimation"); + + *outputSize = estimatedOutputSize; + return encodedData; +} + +// Decode Base64 string (expected NULL terminated) +unsigned char *DecodeDataBase64(const char *text, int *outputSize) +{ + // Base64 decode table + // NOTE: Following ASCII order [0..255] assigning the expected sixtet value to + // every character in the corresponding ASCII position + static const unsigned char base64DecodeTable[256] = { + ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7, + ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, + ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, ['Z'] = 25, + ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, + ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41, + ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, + ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, + ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63 + }; + + *outputSize = 0; + if (text == NULL) return NULL; + + // Compute expected size and padding + int dataSize = (int)strlen(text); // WARNING: Expecting NULL terminated strings! + int ending = dataSize - 1; + int padding = 0; + while (text[ending] == '=') { padding++; ending--; } + int estimatedOutputSize = 3*(dataSize/4) - padding; + int maxOutputSize = 3*(dataSize/4); + + // Load some memory to store decoded data + // NOTE: Allocated enough size to include padding + unsigned char *decodedData = (unsigned char *)RL_CALLOC(maxOutputSize, 1); + if (decodedData == NULL) return NULL; + + int outputCount = 0; + for (int i = 0; i < dataSize;) + { + // Every 4 sextets must generate 3 octets + if ((i + 2) >= dataSize) + { + TRACELOG(LOG_WARNING, "BASE64: Decoding error: Input data size is not valid"); + break; + } + + unsigned int sixtetA = base64DecodeTable[(unsigned char)text[i]]; + unsigned int sixtetB = base64DecodeTable[(unsigned char)text[i + 1]]; + unsigned int sixtetC = (((i + 2) < dataSize) && (unsigned char)text[i + 2] != '=')? base64DecodeTable[(unsigned char)text[i + 2]] : 0; + unsigned int sixtetD = (((i + 3) < dataSize) && (unsigned char)text[i + 3] != '=')? base64DecodeTable[(unsigned char)text[i + 3]] : 0; + + unsigned int octetPack = (sixtetA << 18) | (sixtetB << 12) | (sixtetC << 6) | sixtetD; + + if ((outputCount + 3) > maxOutputSize) + { + TRACELOG(LOG_WARNING, "BASE64: Decoding error: Output data size is too small"); + break; + } + + decodedData[outputCount + 0] = (octetPack >> 16) & 0xff; + decodedData[outputCount + 1] = (octetPack >> 8) & 0xff; + decodedData[outputCount + 2] = octetPack & 0xff; + outputCount += 3; + i += 4; + } + + if (estimatedOutputSize != (outputCount - padding)) TRACELOG(LOG_WARNING, "BASE64: Decoded size differs from estimation"); + + *outputSize = estimatedOutputSize; + return decodedData; +} + +// Compute CRC32 hash code +unsigned int ComputeCRC32(const unsigned char *data, int dataSize) +{ + static unsigned int crcTable[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + unsigned int crc = ~0u; + + for (int i = 0; i < dataSize; i++) crc = (crc >> 8) ^ crcTable[data[i] ^ (crc & 0xff)]; + + return ~crc; +} + +// Compute MD5 hash code +// NOTE: Returns a static int[4] array (16 bytes) +unsigned int *ComputeMD5(const unsigned char *data, int dataSize) +{ + #define ROTATE_LEFT(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) + + static unsigned int hash[4] = { 0 }; // Hash to be returned + + // WARNING: All variables are unsigned 32 bit and wrap modulo 2^32 when calculating + + // NOTE: r specifies the per-round shift amounts + unsigned int r[] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + }; + + // Using binary integer part of the sines of integers (in radians) as constants + unsigned int k[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + hash[0] = 0x67452301; + hash[1] = 0xefcdab89; + hash[2] = 0x98badcfe; + hash[3] = 0x10325476; + + // Pre-processing: adding a single 1 bit + // Append '1' bit to message + // NOTE: The input bytes are considered as bits strings, + // where the first bit is the most significant bit of the byte + + // Pre-processing: padding with zeros + // Append '0' bit until message length in bit 448 (mod 512) + // Append length mod (2 pow 64) to message + + int newDataSize = ((((dataSize + 8)/64) + 1)*64) - 8; + + unsigned char *msg = (unsigned char *)RL_CALLOC(newDataSize + 64, 1); // Initialize with '0' bits, allocating 64 extra bytes + memcpy(msg, data, dataSize); + msg[dataSize] = 128; // Write the '1' bit + + unsigned int bitsLen = 8*dataSize; + memcpy(msg + newDataSize, &bitsLen, 4); // Append the len in bits at the end of the buffer + + // Process the message in successive 512-bit chunks for each 512-bit chunk of message + for (int offset = 0; offset < newDataSize; offset += 64) // 512/8 + { + // Break chunk into sixteen 32-bit words w[j], 0 <= j <= 15 + unsigned int *w = (unsigned int *)(msg + offset); + + // Initialize hash value for this chunk + unsigned int a = hash[0]; + unsigned int b = hash[1]; + unsigned int c = hash[2]; + unsigned int d = hash[3]; + + for (int i = 0; i < 64; i++) + { + unsigned int f = 0; + unsigned int g = 0; + + if (i < 16) + { + f = (b & c) | ((~b) & d); + g = i; + } + else if (i < 32) + { + f = (d & b) | ((~d) & c); + g = (5*i + 1)%16; + } + else if (i < 48) + { + f = b ^ c ^ d; + g = (3*i + 5)%16; + } + else + { + f = c ^ (b | (~d)); + g = (7*i)%16; + } + + unsigned int temp = d; + d = c; + c = b; + b = b + ROTATE_LEFT((a + f + k[i] + w[g]), r[i]); + a = temp; + } + + // Add chunk's hash to result so far + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; + } + + RL_FREE(msg); + + return hash; +} + +// Compute SHA-1 hash code +// NOTE: Returns a static int[5] array (20 bytes) +unsigned int *ComputeSHA1(const unsigned char *data, int dataSize) +{ + #define SHA1_ROTATE_LEFT(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) + + static unsigned int hash[5] = { 0 }; // Hash to be returned + + // Initialize hash values + hash[0] = 0x67452301; + hash[1] = 0xEFCDAB89; + hash[2] = 0x98BADCFE; + hash[3] = 0x10325476; + hash[4] = 0xC3D2E1F0; + + // Pre-processing: adding a single 1 bit + // Append '1' bit to message + // NOTE: The input bytes are considered as bits strings, + // where the first bit is the most significant bit of the byte + + // Pre-processing: padding with zeros + // Append '0' bit until message length in bit 448 (mod 512) + // Append length mod (2 pow 64) to message + + int newDataSize = ((((dataSize + 8)/64) + 1)*64); + + unsigned char *msg = (unsigned char *)RL_CALLOC(newDataSize, 1); // Initialize with '0' bits + memcpy(msg, data, dataSize); + msg[dataSize] = 128; // Write the '1' bit + + unsigned long long bitsLen = 8ULL*dataSize; + msg[newDataSize - 1] = (unsigned char)(bitsLen); + msg[newDataSize - 2] = (unsigned char)(bitsLen >> 8); + msg[newDataSize - 3] = (unsigned char)(bitsLen >> 16); + msg[newDataSize - 4] = (unsigned char)(bitsLen >> 24); + msg[newDataSize - 5] = (unsigned char)(bitsLen >> 32); + msg[newDataSize - 6] = (unsigned char)(bitsLen >> 40); + msg[newDataSize - 7] = (unsigned char)(bitsLen >> 48); + msg[newDataSize - 8] = (unsigned char)(bitsLen >> 56); + + // Process the message in successive 512-bit chunks + for (int offset = 0; offset < newDataSize; offset += 64) // 512/8 + { + // Break chunk into sixteen 32-bit words w[j], 0 <= j <= 15 + unsigned int w[80] = { 0 }; + for (int i = 0; i < 16; i++) + { + w[i] = ((unsigned int)msg[offset + (i*4) + 0] << 24) | + ((unsigned int)msg[offset + (i*4) + 1] << 16) | + ((unsigned int)msg[offset + (i*4) + 2] << 8) | + ((unsigned int)msg[offset + (i*4) + 3]); + } + + // Message schedule: extend the sixteen 32-bit words into eighty 32-bit words: + for (int i = 16; i < 80; i++) w[i] = SHA1_ROTATE_LEFT(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1); + + // Initialize hash value for this chunk + unsigned int a = hash[0]; + unsigned int b = hash[1]; + unsigned int c = hash[2]; + unsigned int d = hash[3]; + unsigned int e = hash[4]; + + for (int i = 0; i < 80; i++) + { + unsigned int f = 0; + unsigned int k = 0; + + if (i < 20) + { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } + else if (i < 40) + { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if (i < 60) + { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } + else + { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + + unsigned int temp = SHA1_ROTATE_LEFT(a, 5) + f + e + k + w[i]; + e = d; + d = c; + c = SHA1_ROTATE_LEFT(b, 30); + b = a; + a = temp; + } + + // Add this chunk's hash to result so far + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; + hash[4] += e; + } + + RL_FREE(msg); + + return hash; +} + +// Compute SHA-256 hash code +// NOTE: Returns a static int[8] array (32 bytes) +unsigned int *ComputeSHA256(const unsigned char *data, int dataSize) +{ + #define SHA256_ROTATE_RIGHT(x, c) ((x >> c) | (x << ((sizeof(unsigned int)*8) - c))) + #define SHA256_A0(x) (SHA256_ROTATE_RIGHT(x, 7) ^ SHA256_ROTATE_RIGHT(x, 18) ^ (x >> 3)) + #define SHA256_A1(x) (SHA256_ROTATE_RIGHT(x, 17) ^ SHA256_ROTATE_RIGHT(x, 19) ^ (x >> 10)) + + static const unsigned int k[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; + + static unsigned int hash[8] = { 0 }; + hash[0] = 0x6A09e667; + hash[1] = 0xbb67ae85; + hash[2] = 0x3c6ef372; + hash[3] = 0xa54ff53a; + hash[4] = 0x510e527f; + hash[5] = 0x9b05688c; + hash[6] = 0x1f83d9ab; + hash[7] = 0x5be0cd19; + + const unsigned long long bitLen = 8ULL*dataSize; + unsigned long long paddedSize = dataSize + sizeof(dataSize); + paddedSize += (64 - (paddedSize%64)); + unsigned char *buffer = (unsigned char *)RL_CALLOC(paddedSize, sizeof(unsigned char)); + + memcpy(buffer, data, dataSize); + buffer[dataSize] = 0x80; + for (int i = 1; i <= sizeof(bitLen); i++) + { + buffer[(paddedSize - sizeof(bitLen)) + (i - 1)] = (bitLen >> (8*(sizeof(bitLen) - i))) & 0xFF; + } + + for (unsigned long long blockN = 0; blockN < paddedSize/64; blockN++) + { + unsigned int a = hash[0]; + unsigned int b = hash[1]; + unsigned int c = hash[2]; + unsigned int d = hash[3]; + unsigned int e = hash[4]; + unsigned int f = hash[5]; + unsigned int g = hash[6]; + unsigned int h = hash[7]; + + unsigned char *block = buffer + (blockN*64); + unsigned int w[64] = { 0 }; + for (int i = 0; i < 16; i++) + { + w[i] = ((unsigned int)block[i*4 + 0] << 24) | + ((unsigned int)block[i*4 + 1] << 16) | + ((unsigned int)block[i*4 + 2] << 8) | + ((unsigned int)block[i*4 + 3]); + } + for (int t = 16; t < 64; t++) w[t] = SHA256_A1(w[t - 2]) + w[t - 7] + SHA256_A0(w[t - 15]) + w[t - 16]; + + for (int t = 0; t < 64; t++) + { + unsigned int e1 = (SHA256_ROTATE_RIGHT(e, 6) ^ SHA256_ROTATE_RIGHT(e, 11) ^ SHA256_ROTATE_RIGHT(e, 25)); + unsigned int ch = ((e & f) ^ (~e & g)); + unsigned int t1 = (h + e1 + ch + k[t] + w[t]); + unsigned int e0 = (SHA256_ROTATE_RIGHT(a, 2) ^ SHA256_ROTATE_RIGHT(a, 13) ^ SHA256_ROTATE_RIGHT(a, 22)); + unsigned int maj = ((a & b) ^ (a & c) ^ (b & c)); + unsigned int t2 = e0 + maj; + + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; + hash[4] += e; + hash[5] += f; + hash[6] += g; + hash[7] += h; + } + + RL_FREE(buffer); + + return hash; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Automation Events Recording and Playing +//---------------------------------------------------------------------------------- + +// Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS +AutomationEventList LoadAutomationEventList(const char *fileName) +{ + AutomationEventList list = { 0 }; + +#if SUPPORT_AUTOMATION_EVENTS + // Allocate and empty automation event list, ready to record new events + list.events = (AutomationEvent *)RL_CALLOC(MAX_AUTOMATION_EVENTS, sizeof(AutomationEvent)); + list.capacity = MAX_AUTOMATION_EVENTS; + + if (fileName == NULL) TRACELOG(LOG_INFO, "AUTOMATION: New empty events list loaded successfully"); + else + { + // Load automation events file (binary) + /* + //int dataSize = 0; + //unsigned char *data = LoadFileData(fileName, &dataSize); + + FILE *raeFile = fopen(fileName, "rb"); + unsigned char fileId[4] = { 0 }; + + fread(fileId, 1, 4, raeFile); + + if ((fileId[0] == 'r') && (fileId[1] == 'A') && (fileId[2] == 'E') && (fileId[1] == ' ')) + { + fread(&eventCount, sizeof(int), 1, raeFile); + TRACELOG(LOG_WARNING, "Events loaded: %i\n", eventCount); + fread(events, sizeof(AutomationEvent), eventCount, raeFile); + } + + fclose(raeFile); + */ + + // Load events file (text) + //unsigned char *buffer = LoadFileText(fileName); + FILE *raeFile = fopen(fileName, "rt"); + + if (raeFile != NULL) + { + unsigned int counter = 0; + char buffer[256] = { 0 }; + char eventDesc[64] = { 0 }; + + char *result = fgets(buffer, 256, raeFile); + if (result != buffer) TRACELOG(LOG_WARNING, "AUTOMATION: [%s] Issue reading line to buffer", fileName); + + while (!feof(raeFile)) + { + switch (buffer[0]) + { + case 'c': sscanf(buffer, "c %i", &list.count); break; + case 'e': + { + if (counter < list.capacity) + { + sscanf(buffer, "e %d %d %d %d %d %d %63[^\n]s", &list.events[counter].frame, &list.events[counter].type, + &list.events[counter].params[0], &list.events[counter].params[1], &list.events[counter].params[2], &list.events[counter].params[3], eventDesc); + + counter++; + } + else TRACELOG(LOG_WARNING, "AUTOMATION: Event goes beyond automated list capacity (MAX: %u): %s", list.capacity, buffer); + } break; + default: break; + } + + result = fgets(buffer, 256, raeFile); + if (result != buffer) TRACELOG(LOG_WARNING, "AUTOMATION: [%s] Issue reading line to buffer", fileName); + } + + if (counter != list.count) + { + TRACELOG(LOG_WARNING, "AUTOMATION: Events read from file [%i] do not mach event count specified [%i]", counter, list.count); + list.count = counter; + } + + fclose(raeFile); + + TRACELOG(LOG_INFO, "AUTOMATION: Events file loaded successfully"); + } + + TRACELOG(LOG_INFO, "AUTOMATION: Events loaded from file: %i", list.count); + } +#endif + return list; +} + +// Unload automation events list from file +void UnloadAutomationEventList(AutomationEventList list) +{ +#if SUPPORT_AUTOMATION_EVENTS + RL_FREE(list.events); +#endif +} + +// Export automation events list as text file +bool ExportAutomationEventList(AutomationEventList list, const char *fileName) +{ + bool result = false; + +#if SUPPORT_AUTOMATION_EVENTS + // Export events as binary file + // NOTE: Code not used, only for reference if required in the future + /* + if (list.count > 0) + { + int binarySize = 4 + sizeof(int) + sizeof(AutomationEvent)*list.count; + unsigned char *binBuffer = (unsigned char *)RL_CALLOC(binarySize, 1); + int offset = 0; + memcpy(binBuffer + offset, "rAE ", 4); + offset += 4; + memcpy(binBuffer + offset, &list.count, sizeof(int)); + offset += sizeof(int); + memcpy(binBuffer + offset, list.events, sizeof(AutomationEvent)*list.count); + offset += sizeof(AutomationEvent)*list.count; + + result = SaveFileData(TextFormat("%s.rae",fileName), binBuffer, binarySize); + RL_FREE(binBuffer); + } + */ + + // Export events as text + // NOTE: Save to memory buffer and SaveFileText() + char *txtData = (char *)RL_CALLOC(256*list.count + 2048, sizeof(char)); // 256 characters per line plus some header + + int byteCount = 0; + byteCount += sprintf(txtData + byteCount, "#\n"); + byteCount += sprintf(txtData + byteCount, "# Automation events exporter v1.0 - raylib automation events list\n"); + byteCount += sprintf(txtData + byteCount, "#\n"); + byteCount += sprintf(txtData + byteCount, "# c \n"); + byteCount += sprintf(txtData + byteCount, "# e // \n"); + byteCount += sprintf(txtData + byteCount, "#\n"); + byteCount += sprintf(txtData + byteCount, "# more info and bugs-report: github.com/raysan5/raylib\n"); + byteCount += sprintf(txtData + byteCount, "# feedback and support: ray[at]raylib.com\n"); + byteCount += sprintf(txtData + byteCount, "#\n"); + byteCount += sprintf(txtData + byteCount, "# Copyright (c) 2023-2026 Ramon Santamaria (@raysan5)\n"); + byteCount += sprintf(txtData + byteCount, "#\n\n"); + + // Add events data + byteCount += sprintf(txtData + byteCount, "c %i\n", list.count); + for (unsigned int i = 0; i < list.count; i++) + { + byteCount += snprintf(txtData + byteCount, 256, "e %i %i %i %i %i %i // Event: %s\n", list.events[i].frame, list.events[i].type, + 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]); + } + + // NOTE: Text data size exported is determined by '\0' (NULL) character + result = SaveFileText(fileName, txtData); + + RL_FREE(txtData); +#endif + + return result; +} + +// Setup automation event list to record to +void SetAutomationEventList(AutomationEventList *list) +{ +#if SUPPORT_AUTOMATION_EVENTS + currentEventList = list; +#endif +} + +// Set automation event internal base frame to start recording +void SetAutomationEventBaseFrame(int frame) +{ + CORE.Time.frameCounter = frame; +} + +// Start recording automation events (AutomationEventList must be set) +void StartAutomationEventRecording(void) +{ +#if SUPPORT_AUTOMATION_EVENTS + automationEventRecording = true; +#endif +} + +// Stop recording automation events +void StopAutomationEventRecording(void) +{ +#if SUPPORT_AUTOMATION_EVENTS + automationEventRecording = false; +#endif +} + +// Play a recorded automation event +void PlayAutomationEvent(AutomationEvent event) +{ +#if SUPPORT_AUTOMATION_EVENTS + // WARNING: When should event be played? After/before/replace PollInputEvents()? -> Up to the user! + + if (!automationEventRecording) + { + switch (event.type) + { + // Input event + case INPUT_KEY_UP: CORE.Input.Keyboard.currentKeyState[event.params[0]] = false; break; // param[0]: key + case INPUT_KEY_DOWN: { // param[0]: key + CORE.Input.Keyboard.currentKeyState[event.params[0]] = true; + + if (CORE.Input.Keyboard.previousKeyState[event.params[0]] == false) + { + if (CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) + { + // Add character to the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = event.params[0]; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + } + } break; + case INPUT_MOUSE_BUTTON_UP: CORE.Input.Mouse.currentButtonState[event.params[0]] = false; break; // param[0]: key + case INPUT_MOUSE_BUTTON_DOWN: CORE.Input.Mouse.currentButtonState[event.params[0]] = true; break; // param[0]: key + case INPUT_MOUSE_POSITION: // param[0]: x, param[1]: y + { + CORE.Input.Mouse.currentPosition.x = (float)event.params[0]; + CORE.Input.Mouse.currentPosition.y = (float)event.params[1]; + } break; + case INPUT_MOUSE_WHEEL_MOTION: // param[0]: x delta, param[1]: y delta + { + CORE.Input.Mouse.currentWheelMove.x = (float)event.params[0]; + CORE.Input.Mouse.currentWheelMove.y = (float)event.params[1]; + } break; + case INPUT_TOUCH_UP: CORE.Input.Touch.currentTouchState[event.params[0]] = false; break; // param[0]: id + case INPUT_TOUCH_DOWN: CORE.Input.Touch.currentTouchState[event.params[0]] = true; break; // param[0]: id + case INPUT_TOUCH_POSITION: // param[0]: id, param[1]: x, param[2]: y + { + CORE.Input.Touch.position[event.params[0]].x = (float)event.params[1]; + CORE.Input.Touch.position[event.params[0]].y = (float)event.params[2]; + } break; + case INPUT_GAMEPAD_CONNECT: CORE.Input.Gamepad.ready[event.params[0]] = true; break; // param[0]: gamepad + case INPUT_GAMEPAD_DISCONNECT: CORE.Input.Gamepad.ready[event.params[0]] = false; break; // param[0]: gamepad + case INPUT_GAMEPAD_BUTTON_UP: CORE.Input.Gamepad.currentButtonState[event.params[0]][event.params[1]] = false; break; // param[0]: gamepad, param[1]: button + case INPUT_GAMEPAD_BUTTON_DOWN: CORE.Input.Gamepad.currentButtonState[event.params[0]][event.params[1]] = true; break; // param[0]: gamepad, param[1]: button + case INPUT_GAMEPAD_AXIS_MOTION: // param[0]: gamepad, param[1]: axis, param[2]: delta + { + CORE.Input.Gamepad.axisState[event.params[0]][event.params[1]] = ((float)event.params[2]/32768.0f); + } break; + #if SUPPORT_GESTURES_SYSTEM + case INPUT_GESTURE: GESTURES.current = event.params[0]; break; // param[0]: gesture (enum Gesture) -> rgestures.h: GESTURES.current + #endif + // Window event + case WINDOW_CLOSE: CORE.Window.shouldClose = true; break; + case WINDOW_MAXIMIZE: MaximizeWindow(); break; + case WINDOW_MINIMIZE: MinimizeWindow(); break; + case WINDOW_RESIZE: SetWindowSize(event.params[0], event.params[1]); break; + // Custom event + #if SUPPORT_SCREEN_CAPTURE + case ACTION_TAKE_SCREENSHOT: + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } break; + #endif + case ACTION_SETTARGETFPS: SetTargetFPS(event.params[0]); break; + default: break; + } + + 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]); + } +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Input Handling: Keyboard +//---------------------------------------------------------------------------------- + +// Check if key has been pressed once +bool IsKeyPressed(int key) +{ + bool pressed = false; + + if ((key > 0) && (key < MAX_KEYBOARD_KEYS)) + { + if ((CORE.Input.Keyboard.previousKeyState[key] == 0) && (CORE.Input.Keyboard.currentKeyState[key] == 1)) pressed = true; + } + + return pressed; +} + +// Check if key has been pressed again +bool IsKeyPressedRepeat(int key) +{ + bool repeat = false; + + if ((key > 0) && (key < MAX_KEYBOARD_KEYS)) + { + if (CORE.Input.Keyboard.keyRepeatInFrame[key] == 1) repeat = true; + } + + return repeat; +} + +// Check if key is being pressed (key held down) +bool IsKeyDown(int key) +{ + bool down = false; + + if ((key > 0) && (key < MAX_KEYBOARD_KEYS)) + { + if (CORE.Input.Keyboard.currentKeyState[key] == 1) down = true; + } + + return down; +} + +// Check if key has been released once +bool IsKeyReleased(int key) +{ + bool released = false; + + if ((key > 0) && (key < MAX_KEYBOARD_KEYS)) + { + if ((CORE.Input.Keyboard.previousKeyState[key] == 1) && (CORE.Input.Keyboard.currentKeyState[key] == 0)) released = true; + } + + return released; +} + +// Check if key is NOT being pressed (key not held down) +bool IsKeyUp(int key) +{ + bool up = false; + + if ((key > 0) && (key < MAX_KEYBOARD_KEYS)) + { + if (CORE.Input.Keyboard.currentKeyState[key] == 0) up = true; + } + + return up; +} + +// Get the last key pressed +int GetKeyPressed(void) +{ + int value = 0; + + if (CORE.Input.Keyboard.keyPressedQueueCount > 0) + { + // Get character from the queue head + value = CORE.Input.Keyboard.keyPressedQueue[0]; + + // Shift elements 1 step toward the head + for (int i = 0; i < (CORE.Input.Keyboard.keyPressedQueueCount - 1); i++) + CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1]; + + // Reset last character in the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount - 1] = 0; + CORE.Input.Keyboard.keyPressedQueueCount--; + } + + return value; +} + +// Get the last char pressed +int GetCharPressed(void) +{ + int value = 0; + + if (CORE.Input.Keyboard.charPressedQueueCount > 0) + { + // Get character from the queue head + value = CORE.Input.Keyboard.charPressedQueue[0]; + + // Shift elements 1 step toward the head + for (int i = 0; i < (CORE.Input.Keyboard.charPressedQueueCount - 1); i++) + CORE.Input.Keyboard.charPressedQueue[i] = CORE.Input.Keyboard.charPressedQueue[i + 1]; + + // Reset last character in the queue + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount - 1] = 0; + CORE.Input.Keyboard.charPressedQueueCount--; + } + + return value; +} + +// Set a custom key to exit program +// NOTE: default exitKey is set to ESCAPE +void SetExitKey(int key) +{ + CORE.Input.Keyboard.exitKey = key; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Input Handling: Gamepad +//---------------------------------------------------------------------------------- + +// NOTE: Functions with a platform-specific implementation on rcore_.c +//int SetGamepadMappings(const char *mappings) + +// Check if gamepad is available +bool IsGamepadAvailable(int gamepad) +{ + bool result = false; + + if ((gamepad >= 0) && (gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad]) result = true; + + return result; +} + +// Get gamepad internal name id +const char *GetGamepadName(int gamepad) +{ + const char *name = NULL; + + if ((gamepad >= 0) && (gamepad < MAX_GAMEPADS)) name = CORE.Input.Gamepad.name[gamepad]; + + return name; +} + +// Check if gamepad button has been pressed once +bool IsGamepadButtonPressed(int gamepad, int button) +{ + bool pressed = false; + + if ((gamepad >= 0) && (gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS)) + { + if ((CORE.Input.Gamepad.previousButtonState[gamepad][button] == 0) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) pressed = true; + } + + return pressed; +} + +// Check if gamepad button is being pressed +bool IsGamepadButtonDown(int gamepad, int button) +{ + bool down = false; + + if ((gamepad >= 0) && (gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS)) + { + if (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1) down = true; + } + + return down; +} + +// Check if gamepad button has NOT been pressed once +bool IsGamepadButtonReleased(int gamepad, int button) +{ + bool released = false; + + if ((gamepad >= 0) && (gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS)) + { + if ((CORE.Input.Gamepad.previousButtonState[gamepad][button] == 1) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) released = true; + } + + return released; +} + +// Check if gamepad button is NOT being pressed +bool IsGamepadButtonUp(int gamepad, int button) +{ + bool up = false; + + if ((gamepad >= 0) && (gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS)) + { + if (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0) up = true; + } + + return up; +} + +// Get the last gamepad button pressed +// NOTE: Returns last gamepad button down, down->up change not considered +int GetGamepadButtonPressed(void) +{ + return CORE.Input.Gamepad.lastButtonPressed; +} + +// Get gamepad axis count +int GetGamepadAxisCount(int gamepad) +{ + int result = 0; + + if ((gamepad >= 0) && (gamepad < MAX_GAMEPADS)) result = CORE.Input.Gamepad.axisCount[gamepad]; + + return result; +} + +// Get axis movement vector for a gamepad +float GetGamepadAxisMovement(int gamepad, int axis) +{ + float value = ((axis == GAMEPAD_AXIS_LEFT_TRIGGER) || (axis == GAMEPAD_AXIS_RIGHT_TRIGGER))? -1.0f : 0.0f; + + if ((gamepad >= 0) && (gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXES)) + { + float movement = (value < 0.0f)? CORE.Input.Gamepad.axisState[gamepad][axis] : fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]); + + if (movement > value) value = CORE.Input.Gamepad.axisState[gamepad][axis]; + } + + return value; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Input Handling: Mouse +//---------------------------------------------------------------------------------- + +// NOTE: Functions with a platform-specific implementation on rcore_.c +//void SetMousePosition(int x, int y) +//void SetMouseCursor(int cursor) + +// Check if mouse button has been pressed once +bool IsMouseButtonPressed(int button) +{ + bool pressed = false; + + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) + { + if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) pressed = true; + + // Map touches to mouse buttons checking + if ((CORE.Input.Touch.currentTouchState[button] == 1) && (CORE.Input.Touch.previousTouchState[button] == 0)) pressed = true; + } + + return pressed; +} + +// Check if mouse button is being pressed +bool IsMouseButtonDown(int button) +{ + bool down = false; + + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) + { + if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true; + + // NOTE: Touches are considered like mouse buttons + if (CORE.Input.Touch.currentTouchState[button] == 1) down = true; + } + + return down; +} + +// Check if mouse button has been released once +bool IsMouseButtonReleased(int button) +{ + bool released = false; + + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) + { + if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) released = true; + + // Map touches to mouse buttons checking + if ((CORE.Input.Touch.currentTouchState[button] == 0) && (CORE.Input.Touch.previousTouchState[button] == 1)) released = true; + } + + return released; +} + +// Check if mouse button is NOT being pressed +bool IsMouseButtonUp(int button) +{ + bool up = false; + + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) + { + if (CORE.Input.Mouse.currentButtonState[button] == 0) up = true; + + // NOTE: Touches are considered like mouse buttons + if (CORE.Input.Touch.currentTouchState[button] == 0) up = true; + } + + return up; +} + +// Get mouse position X +int GetMouseX(void) +{ + int mouseX = (int)((CORE.Input.Mouse.currentPosition.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x); + + return mouseX; +} + +// Get mouse position Y +int GetMouseY(void) +{ + int mouseY = (int)((CORE.Input.Mouse.currentPosition.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y); + + return mouseY; +} + +// Get mouse position XY +Vector2 GetMousePosition(void) +{ + Vector2 position = { 0 }; + + position.x = (CORE.Input.Mouse.currentPosition.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x; + position.y = (CORE.Input.Mouse.currentPosition.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y; + + return position; +} + +// Get mouse delta between frames +Vector2 GetMouseDelta(void) +{ + Vector2 delta = { 0 }; + + delta.x = (CORE.Input.Mouse.currentPosition.x - CORE.Input.Mouse.previousPosition.x)*CORE.Input.Mouse.scale.x; + delta.y = (CORE.Input.Mouse.currentPosition.y - CORE.Input.Mouse.previousPosition.y)*CORE.Input.Mouse.scale.y; + + return delta; +} + +// Set mouse offset +// NOTE: Useful when rendering to different size targets +void SetMouseOffset(int offsetX, int offsetY) +{ + CORE.Input.Mouse.offset = (Vector2){ (float)offsetX, (float)offsetY }; +} + +// Set mouse scaling +// NOTE: Useful when rendering to different size targets +void SetMouseScale(float scaleX, float scaleY) +{ + CORE.Input.Mouse.scale = (Vector2){ scaleX, scaleY }; +} + +// Get mouse wheel movement Y +float GetMouseWheelMove(void) +{ + float result = 0.0f; + + if (fabsf(CORE.Input.Mouse.currentWheelMove.x) > fabsf(CORE.Input.Mouse.currentWheelMove.y)) result = (float)CORE.Input.Mouse.currentWheelMove.x; + else result = (float)CORE.Input.Mouse.currentWheelMove.y; + + return result; +} + +// Get mouse wheel movement X/Y as a vector +Vector2 GetMouseWheelMoveV(void) +{ + Vector2 result = { 0 }; + + result = CORE.Input.Mouse.currentWheelMove; + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Input Handling: Touch +//---------------------------------------------------------------------------------- + +// Get touch position X for touch point 0 (relative to screen size) +int GetTouchX(void) +{ + int touchX = (int)CORE.Input.Touch.position[0].x; + return touchX; +} + +// Get touch position Y for touch point 0 (relative to screen size) +int GetTouchY(void) +{ + int touchY = (int)CORE.Input.Touch.position[0].y; + return touchY; +} + +// Get touch position XY for a touch point index (relative to screen size) +Vector2 GetTouchPosition(int index) +{ + Vector2 position = { -1.0f, -1.0f }; + + if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index]; + else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS); + + return position; +} + +// Get touch point identifier for provided index +int GetTouchPointId(int index) +{ + int id = -1; + + if (index < MAX_TOUCH_POINTS) id = CORE.Input.Touch.pointId[index]; + + return id; +} + +// Get number of touch points +int GetTouchPointCount(void) +{ + return CORE.Input.Touch.pointCount; +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// NOTE: Functions with a platform-specific implementation on rcore_.c +//int InitPlatform(void) +//void ClosePlatform(void) + +// Initialize hi-resolution timer +void InitTimer(void) +{ + // Setting a higher resolution can improve the accuracy of time-out intervals in wait functions + // However, it can also reduce overall system performance, because the thread scheduler switches tasks more often + // High resolutions can also prevent the CPU power management system from entering power-saving modes + // Setting a higher resolution does not improve the accuracy of the high-resolution performance counter +#if defined(_WIN32) && SUPPORT_WINMM_HIGHRES_TIMER && !SUPPORT_BUSY_WAIT_LOOP && !defined(PLATFORM_DESKTOP_SDL) + timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) +#endif + +#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) + struct timespec now = { 0 }; + + if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success + { + CORE.Time.base = (unsigned long long)now.tv_sec*1000000000LLU + (unsigned long long)now.tv_nsec; + } + else TRACELOG(LOG_WARNING, "TIMER: Hi-resolution timer not available"); +#endif + + CORE.Time.previous = GetTime(); // Get time as double +} + +// Set viewport for a provided width and height +void SetupViewport(int width, int height) +{ + CORE.Window.render.width = width; + CORE.Window.render.height = height; + + // Set viewport width and height + rlViewport(CORE.Window.renderOffset.x/2, CORE.Window.renderOffset.y/2, CORE.Window.render.width, CORE.Window.render.height); + + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlLoadIdentity(); // Reset current matrix (projection) + + // Set orthographic projection to current framebuffer size + // NOTE: Configured top-left corner as (0, 0) + rlOrtho(0, CORE.Window.render.width, CORE.Window.render.height, 0, 0.0f, 1.0f); + + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) +} + +// Scan all files and directories in a base path +// WARNING: files.paths[] must be previously allocated and +// contain enough space to store all required paths +static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const char *filter, unsigned int expectedFileCount, bool scanSubdirs) +{ + // WARNING: Path can not be static or it will be reused between recursive function calls! + char path[MAX_FILEPATH_LENGTH] = { 0 }; + memset(path, 0, MAX_FILEPATH_LENGTH); + + struct dirent *dp = NULL; + DIR *dir = opendir(basePath); + + if (dir != NULL) + { + while (((dp = readdir(dir)) != NULL) && (files->count < expectedFileCount)) + { + if ((strcmp(dp->d_name, ".") != 0) && (strcmp(dp->d_name, "..") != 0)) + { + // Construct new path from our base path + #if defined(_WIN32) + int pathLength = snprintf(path, MAX_FILEPATH_LENGTH, "%s\\%s", basePath, dp->d_name); + #else + int pathLength = snprintf(path, MAX_FILEPATH_LENGTH, "%s/%s", basePath, dp->d_name); + #endif + + if ((pathLength < 0) || (pathLength >= MAX_FILEPATH_LENGTH)) + { + TRACELOG(LOG_WARNING, "FILEIO: Path longer than %d characters (%s...)", MAX_FILEPATH_LENGTH, basePath); + } + else if (IsPathFile(path)) + { + if ((filter == NULL) || (strstr(filter, FILE_FILTER_TAG_ALL) != NULL) || + (strstr(filter, FILE_FILTER_TAG_FILE_ONLY) != NULL) || IsFileExtension(path, filter)) + { + memcpy(files->paths[files->count], path, pathLength); + files->count++; + } + } + else + { + if ((filter != NULL) && ((strstr(filter, FILE_FILTER_TAG_DIR_ONLY) != NULL) || (strstr(filter, FILE_FILTER_TAG_ALL) != NULL))) + { + memcpy(files->paths[files->count], path, pathLength); + files->count++; + } + + if (scanSubdirs) ScanDirectoryFiles(path, files, filter, expectedFileCount, scanSubdirs); + } + } + } + + closedir(dir); + } + else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); // Maybe it's a file... +} + +#if SUPPORT_AUTOMATION_EVENTS +// Automation event recording +// Checking events in current frame and save them into currentEventList +// NOTE: Recording is by default done at EndDrawing(), before PollInputEvents() +static void RecordAutomationEvent(void) +{ + if (currentEventList->count == currentEventList->capacity) return; + + // Keyboard input events recording + //------------------------------------------------------------------------------------- + for (int key = 0; key < MAX_KEYBOARD_KEYS; key++) + { + // Event type: INPUT_KEY_UP (only saved once) + if (CORE.Input.Keyboard.previousKeyState[key] && !CORE.Input.Keyboard.currentKeyState[key]) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_KEY_UP; + currentEventList->events[currentEventList->count].params[0] = key; + currentEventList->events[currentEventList->count].params[1] = 0; + currentEventList->events[currentEventList->count].params[2] = 0; + + 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]); + currentEventList->count++; + } + + if (currentEventList->count == currentEventList->capacity) return; // Security check + + // Event type: INPUT_KEY_DOWN + if (CORE.Input.Keyboard.currentKeyState[key]) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_KEY_DOWN; + currentEventList->events[currentEventList->count].params[0] = key; + currentEventList->events[currentEventList->count].params[1] = 0; + currentEventList->events[currentEventList->count].params[2] = 0; + + 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]); + currentEventList->count++; + } + + if (currentEventList->count == currentEventList->capacity) return; // Security check + } + //------------------------------------------------------------------------------------- + + // Mouse input currentEventList->events recording + //------------------------------------------------------------------------------------- + for (int button = 0; button < MAX_MOUSE_BUTTONS; button++) + { + // Event type: INPUT_MOUSE_BUTTON_UP + if (CORE.Input.Mouse.previousButtonState[button] && !CORE.Input.Mouse.currentButtonState[button]) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_MOUSE_BUTTON_UP; + currentEventList->events[currentEventList->count].params[0] = button; + currentEventList->events[currentEventList->count].params[1] = 0; + currentEventList->events[currentEventList->count].params[2] = 0; + + 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]); + currentEventList->count++; + } + + if (currentEventList->count == currentEventList->capacity) return; // Security check + + // Event type: INPUT_MOUSE_BUTTON_DOWN + if (CORE.Input.Mouse.currentButtonState[button]) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_MOUSE_BUTTON_DOWN; + currentEventList->events[currentEventList->count].params[0] = button; + currentEventList->events[currentEventList->count].params[1] = 0; + currentEventList->events[currentEventList->count].params[2] = 0; + + 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]); + currentEventList->count++; + } + + if (currentEventList->count == currentEventList->capacity) return; // Security check + } + + // Event type: INPUT_MOUSE_POSITION (only saved if changed) + if (((int)CORE.Input.Mouse.currentPosition.x != (int)CORE.Input.Mouse.previousPosition.x) || + ((int)CORE.Input.Mouse.currentPosition.y != (int)CORE.Input.Mouse.previousPosition.y)) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_MOUSE_POSITION; + currentEventList->events[currentEventList->count].params[0] = (int)CORE.Input.Mouse.currentPosition.x; + currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Mouse.currentPosition.y; + currentEventList->events[currentEventList->count].params[2] = 0; + + 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]); + currentEventList->count++; + + if (currentEventList->count == currentEventList->capacity) return; // Security check + } + + // Event type: INPUT_MOUSE_WHEEL_MOTION + if (((int)CORE.Input.Mouse.currentWheelMove.x != (int)CORE.Input.Mouse.previousWheelMove.x) || + ((int)CORE.Input.Mouse.currentWheelMove.y != (int)CORE.Input.Mouse.previousWheelMove.y)) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_MOUSE_WHEEL_MOTION; + currentEventList->events[currentEventList->count].params[0] = (int)CORE.Input.Mouse.currentWheelMove.x; + currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Mouse.currentWheelMove.y; + currentEventList->events[currentEventList->count].params[2] = 0; + + 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]); + currentEventList->count++; + + if (currentEventList->count == currentEventList->capacity) return; // Security check + } + //------------------------------------------------------------------------------------- + + // Touch input currentEventList->events recording + //------------------------------------------------------------------------------------- + for (int id = 0; id < MAX_TOUCH_POINTS; id++) + { + // Event type: INPUT_TOUCH_UP + if (CORE.Input.Touch.previousTouchState[id] && !CORE.Input.Touch.currentTouchState[id]) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_TOUCH_UP; + currentEventList->events[currentEventList->count].params[0] = id; + currentEventList->events[currentEventList->count].params[1] = 0; + currentEventList->events[currentEventList->count].params[2] = 0; + + 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]); + currentEventList->count++; + } + + if (currentEventList->count == currentEventList->capacity) return; // Security check + + // Event type: INPUT_TOUCH_DOWN + if (CORE.Input.Touch.currentTouchState[id]) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_TOUCH_DOWN; + currentEventList->events[currentEventList->count].params[0] = id; + currentEventList->events[currentEventList->count].params[1] = 0; + currentEventList->events[currentEventList->count].params[2] = 0; + + 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]); + currentEventList->count++; + } + + if (currentEventList->count == currentEventList->capacity) return; // Security check + + // Event type: INPUT_TOUCH_POSITION + if (((int)CORE.Input.Touch.position[id].x != (int)CORE.Input.Touch.previousPosition[id].x) || + ((int)CORE.Input.Touch.position[id].y != (int)CORE.Input.Touch.previousPosition[id].y)) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_TOUCH_POSITION; + currentEventList->events[currentEventList->count].params[0] = id; + currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Touch.position[id].x; + currentEventList->events[currentEventList->count].params[2] = (int)CORE.Input.Touch.position[id].y; + + 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]); + currentEventList->count++; + } + + + if (currentEventList->count == currentEventList->capacity) return; // Security check + } + //------------------------------------------------------------------------------------- + + // Gamepad input currentEventList->events recording + //------------------------------------------------------------------------------------- + for (int gamepad = 0; gamepad < MAX_GAMEPADS; gamepad++) + { + // Event type: INPUT_GAMEPAD_CONNECT + /* + if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) && + (CORE.Input.Gamepad.currentState[gamepad])) // Check if changed to ready + { + // TODO: Save gamepad connect event + } + */ + + // Event type: INPUT_GAMEPAD_DISCONNECT + /* + if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) && + (!CORE.Input.Gamepad.currentState[gamepad])) // Check if changed to not-ready + { + // TODO: Save gamepad disconnect event + } + */ + + for (int button = 0; button < MAX_GAMEPAD_BUTTONS; button++) + { + // Event type: INPUT_GAMEPAD_BUTTON_UP + if (CORE.Input.Gamepad.previousButtonState[gamepad][button] && !CORE.Input.Gamepad.currentButtonState[gamepad][button]) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_GAMEPAD_BUTTON_UP; + currentEventList->events[currentEventList->count].params[0] = gamepad; + currentEventList->events[currentEventList->count].params[1] = button; + currentEventList->events[currentEventList->count].params[2] = 0; + + 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]); + currentEventList->count++; + } + + if (currentEventList->count == currentEventList->capacity) return; // Security check + + // Event type: INPUT_GAMEPAD_BUTTON_DOWN + if (CORE.Input.Gamepad.currentButtonState[gamepad][button]) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_GAMEPAD_BUTTON_DOWN; + currentEventList->events[currentEventList->count].params[0] = gamepad; + currentEventList->events[currentEventList->count].params[1] = button; + currentEventList->events[currentEventList->count].params[2] = 0; + + 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]); + currentEventList->count++; + } + + if (currentEventList->count == currentEventList->capacity) return; // Security check + } + + for (int axis = 0; axis < MAX_GAMEPAD_AXES; axis++) + { + // Event type: INPUT_GAMEPAD_AXIS_MOTION + float defaultMovement = ((axis == GAMEPAD_AXIS_LEFT_TRIGGER) || (axis == GAMEPAD_AXIS_RIGHT_TRIGGER))? -1.0f : 0.0f; + if (GetGamepadAxisMovement(gamepad, axis) != defaultMovement) + { + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_GAMEPAD_AXIS_MOTION; + currentEventList->events[currentEventList->count].params[0] = gamepad; + currentEventList->events[currentEventList->count].params[1] = axis; + currentEventList->events[currentEventList->count].params[2] = (int)(CORE.Input.Gamepad.axisState[gamepad][axis]*32768.0f); + + 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]); + currentEventList->count++; + } + + if (currentEventList->count == currentEventList->capacity) return; // Security check + } + } + //------------------------------------------------------------------------------------- + +#if SUPPORT_GESTURES_SYSTEM + // Gestures input currentEventList->events recording + //------------------------------------------------------------------------------------- + if (GESTURES.current != GESTURE_NONE) + { + // Event type: INPUT_GESTURE + currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; + currentEventList->events[currentEventList->count].type = INPUT_GESTURE; + currentEventList->events[currentEventList->count].params[0] = GESTURES.current; + currentEventList->events[currentEventList->count].params[1] = 0; + currentEventList->events[currentEventList->count].params[2] = 0; + + 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]); + currentEventList->count++; + + if (currentEventList->count == currentEventList->capacity) return; // Security check + } + //------------------------------------------------------------------------------------- +#endif +} +#endif + +#if !SUPPORT_MODULE_RTEXT +// Formatting of text with variables to 'embed' +// WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times +const char *TextFormat(const char *text, ...) +{ +#ifndef MAX_TEXTFORMAT_BUFFERS + #define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting +#endif +#ifndef MAX_TEXT_BUFFER_LENGTH + #define MAX_TEXT_BUFFER_LENGTH 1024 // Maximum size of static text buffer +#endif + + // Define an array of buffers, so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations + static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 }; + static int index = 0; + + char *currentBuffer = buffers[index]; + memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using + + if (text != NULL) + { + va_list args; + va_start(args, text); + int requiredByteCount = vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); + va_end(args); + + // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occurred + if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) + { + // Inserting "..." at the end of the string to mark as truncated + char *truncBuffer = buffers[index] + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0" + snprintf(truncBuffer, 4, "..."); + } + + index += 1; // Move to next buffer for next function call + if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; + } + + return currentBuffer; +} +#endif diff --git a/src/libraries/raylib/raylib/src/rgestures.h b/src/libraries/raylib/raylib/src/rgestures.h new file mode 100644 index 0000000..e279581 --- /dev/null +++ b/src/libraries/raylib/raylib/src/rgestures.h @@ -0,0 +1,557 @@ +/********************************************************************************************** +* +* rgestures - Gestures system, gestures processing based on input events (touch/mouse) +* +* CONFIGURATION: +* #define RGESTURES_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RGESTURES_STANDALONE +* If defined, the library can be used as standalone to process gesture events with +* no external dependencies. +* +* CONTRIBUTORS: +* Marc Palau: Initial implementation (2014) +* Albert Martos: Complete redesign and testing (2015) +* Ian Eito: Complete redesign and testing (2015) +* Ramon Santamaria: Supervision, review, update and maintenance +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2026 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RGESTURES_H +#define RGESTURES_H + +#ifndef PI + #define PI 3.14159265358979323846 +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef MAX_TOUCH_POINTS + #define MAX_TOUCH_POINTS 8 // Maximum number of touch points supported +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Below types are required for standalone usage +//---------------------------------------------------------------------------------- +// Boolean type +#if !defined(__cplusplus) +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) + #include +#elif !defined(bool) + typedef enum bool { false = 0, true = !false } bool; + #define RL_BOOL_TYPE +#endif +#endif + +#if !defined(RL_VECTOR2_TYPE) +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; +#endif + +#if defined(RGESTURES_STANDALONE) +// Gestures type +// NOTE: It could be used as flags to enable only some gestures +typedef enum { + GESTURE_NONE = 0, + GESTURE_TAP = 1, + GESTURE_DOUBLETAP = 2, + GESTURE_HOLD = 4, + GESTURE_DRAG = 8, + GESTURE_SWIPE_RIGHT = 16, + GESTURE_SWIPE_LEFT = 32, + GESTURE_SWIPE_UP = 64, + GESTURE_SWIPE_DOWN = 128, + GESTURE_PINCH_IN = 256, + GESTURE_PINCH_OUT = 512 +} Gesture; +#endif + +typedef enum { + TOUCH_ACTION_UP = 0, + TOUCH_ACTION_DOWN, + TOUCH_ACTION_MOVE, + TOUCH_ACTION_CANCEL +} TouchAction; + +// Gesture event +typedef struct { + int touchAction; + int pointCount; + int pointId[MAX_TOUCH_POINTS]; + Vector2 position[MAX_TOUCH_POINTS]; +} GestureEvent; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +void ProcessGestureEvent(GestureEvent event); // Process gesture event and translate it into gestures +void UpdateGestures(void); // Update gestures detected (must be called every frame) + +#if defined(RGESTURES_STANDALONE) +void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +bool IsGestureDetected(int gesture); // Check if gesture has been detected +int GetGestureDetected(void); // Get latest detected gesture + +float GetGestureHoldDuration(void); // Get gesture hold time in seconds +Vector2 GetGestureDragVector(void); // Get gesture drag vector +float GetGestureDragAngle(void); // Get gesture drag angle +Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +float GetGesturePinchAngle(void); // Get gesture pinch angle +#endif + +#if defined(__cplusplus) +} +#endif + +#endif // RGESTURES_H + +/*********************************************************************************** +* +* RGESTURES IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RGESTURES_IMPLEMENTATION) + +#if defined(RGESTURES_STANDALONE) +#if defined(_WIN32) + #if defined(__cplusplus) + extern "C" { // Prevents name mangling of functions + #endif + // Functions required to query time on Windows + int __stdcall QueryPerformanceCounter(unsigned long long *lpPerformanceCount); + int __stdcall QueryPerformanceFrequency(unsigned long long *lpFrequency); + #if defined(__cplusplus) + } + #endif +#elif defined(__linux__) + #if _POSIX_C_SOURCE < 199309L + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for CLOCK_MONOTONIC if compiled with c99 without gnu ext. + #endif + #include // Required for: timespec + #include // Required for: clock_gettime() + + #include // Required for: sqrtf(), atan2f() +#endif +#if defined(__APPLE__) // macOS also defines __MACH__ + #include // Required for: clock_get_time() + #include // Required for: mach_timespec_t +#endif +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define FORCE_TO_SWIPE 0.2f // Swipe force, measured in normalized screen units/time +#define MINIMUM_DRAG 0.015f // Drag minimum force, measured in normalized screen units (0.0f to 1.0f) +#define DRAG_TIMEOUT 0.3f // Drag minimum time for web, measured in seconds +#define MINIMUM_PINCH 0.005f // Pinch minimum force, measured in normalized screen units (0.0f to 1.0f) +#define TAP_TIMEOUT 0.3f // Tap minimum time, measured in seconds +#define PINCH_TIMEOUT 0.3f // Pinch minimum time, measured in seconds +#define DOUBLETAP_RANGE 0.03f // DoubleTap range, measured in normalized screen units (0.0f to 1.0f) + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + +// Gestures module state context [136 bytes] +typedef struct { + unsigned int current; // Current detected gesture + unsigned int enabledFlags; // Enabled gestures flags + struct { + int firstId; // Touch id for first touch point + int pointCount; // Touch points counter + double eventTime; // Time stamp when an event happened + Vector2 upPosition; // Touch up position + Vector2 downPositionA; // First touch down position + Vector2 downPositionB; // Second touch down position + Vector2 downDragPosition; // Touch drag position + Vector2 moveDownPositionA; // First touch down position on move + Vector2 moveDownPositionB; // Second touch down position on move + Vector2 previousPositionA; // Previous position A to compare for pinch gestures + Vector2 previousPositionB; // Previous position B to compare for pinch gestures + int tapCounter; // TAP counter (one tap implies TOUCH_ACTION_DOWN and TOUCH_ACTION_UP actions) + } Touch; + struct { + bool resetRequired; // HOLD reset to get first touch point again + double timeDuration; // HOLD duration in seconds + } Hold; + struct { + Vector2 vector; // DRAG vector (between initial and current position) + float angle; // DRAG angle (relative to x-axis) + float distance; // DRAG distance (from initial touch point to final) (normalized [0..1]) + float intensity; // DRAG intensity, how far why did the DRAG (pixels per frame) + } Drag; + struct { + double startTime; // SWIPE start time to calculate drag intensity + } Swipe; + struct { + Vector2 vector; // PINCH vector (between first and second touch points) + float angle; // PINCH angle (relative to x-axis) + float distance; // PINCH displacement distance (normalized [0..1]) + } Pinch; +} GesturesData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static GesturesData GESTURES = { + .current = GESTURE_NONE, // No current gesture detected + .Touch.firstId = -1, + .enabledFlags = 0b0000001111111111 // All gestures supported by default +}; + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +static float rgVector2Angle(Vector2 initialPosition, Vector2 finalPosition); +static float rgVector2Distance(Vector2 v1, Vector2 v2); +static double rgGetCurrentTime(void); + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Enable only desired gestures to be detected +void SetGesturesEnabled(unsigned int flags) +{ + GESTURES.enabledFlags = flags; +} + +// Check if gesture have been detected +bool IsGestureDetected(unsigned int gesture) +{ + if ((GESTURES.enabledFlags & GESTURES.current) == gesture) return true; + else return false; +} + +// Process gesture event and translate it into gestures +void ProcessGestureEvent(GestureEvent event) +{ + // Reset required variables + GESTURES.Touch.pointCount = event.pointCount; // Required on UpdateGestures() + + if (GESTURES.Touch.pointCount == 1) // One touch point + { + if (event.touchAction == TOUCH_ACTION_DOWN) + { + GESTURES.Touch.tapCounter++; // Tap counter + + // Detect GESTURE_DOUBLE_TAP + if ((GESTURES.current == GESTURE_NONE) && (GESTURES.Touch.tapCounter >= 2) && ((rgGetCurrentTime() - GESTURES.Touch.eventTime) < TAP_TIMEOUT) && (rgVector2Distance(GESTURES.Touch.downPositionA, event.position[0]) < DOUBLETAP_RANGE)) + { + GESTURES.current = GESTURE_DOUBLETAP; + GESTURES.Touch.tapCounter = 0; + } + else // Detect GESTURE_TAP + { + GESTURES.Touch.tapCounter = 1; + GESTURES.current = GESTURE_TAP; + } + + GESTURES.Touch.downPositionA = event.position[0]; + GESTURES.Touch.downDragPosition = event.position[0]; + + GESTURES.Touch.upPosition = GESTURES.Touch.downPositionA; + GESTURES.Touch.eventTime = rgGetCurrentTime(); + + GESTURES.Swipe.startTime = rgGetCurrentTime(); + + GESTURES.Drag.vector = (Vector2){ 0.0f, 0.0f }; + } + else if (event.touchAction == TOUCH_ACTION_UP) + { + // A swipe can happen while the current gesture is drag, but (especially for web) also hold, so set upPosition for both cases + if (GESTURES.current == GESTURE_DRAG || GESTURES.current == GESTURE_HOLD) GESTURES.Touch.upPosition = event.position[0]; + + // NOTE: GESTURES.Drag.intensity is dependent on the resolution of the screen + GESTURES.Drag.distance = rgVector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.upPosition); + GESTURES.Drag.intensity = GESTURES.Drag.distance/(float)((rgGetCurrentTime() - GESTURES.Swipe.startTime)); + + // Detect GESTURE_SWIPE + if ((GESTURES.Drag.intensity > FORCE_TO_SWIPE) && (GESTURES.current != GESTURE_DRAG)) + { + // NOTE: Angle should be inverted in Y + GESTURES.Drag.angle = 360.0f - rgVector2Angle(GESTURES.Touch.downPositionA, GESTURES.Touch.upPosition); + + if ((GESTURES.Drag.angle < 30) || (GESTURES.Drag.angle > 330)) GESTURES.current = GESTURE_SWIPE_RIGHT; // Right + else if ((GESTURES.Drag.angle >= 30) && (GESTURES.Drag.angle <= 150)) GESTURES.current = GESTURE_SWIPE_UP; // Up + else if ((GESTURES.Drag.angle > 150) && (GESTURES.Drag.angle < 210)) GESTURES.current = GESTURE_SWIPE_LEFT; // Left + else if ((GESTURES.Drag.angle >= 210) && (GESTURES.Drag.angle <= 330)) GESTURES.current = GESTURE_SWIPE_DOWN; // Down + else GESTURES.current = GESTURE_NONE; + } + else + { + GESTURES.Drag.distance = 0.0f; + GESTURES.Drag.intensity = 0.0f; + GESTURES.Drag.angle = 0.0f; + + GESTURES.current = GESTURE_NONE; + } + + GESTURES.Touch.downDragPosition = (Vector2){ 0.0f, 0.0f }; + GESTURES.Touch.pointCount = 0; + } + else if (event.touchAction == TOUCH_ACTION_MOVE) + { + GESTURES.Touch.moveDownPositionA = event.position[0]; + + if (GESTURES.current == GESTURE_HOLD) + { + if (GESTURES.Hold.resetRequired) GESTURES.Touch.downPositionA = event.position[0]; + + GESTURES.Hold.resetRequired = false; + + // Detect GESTURE_DRAG + if ((rgGetCurrentTime() - GESTURES.Touch.eventTime) > DRAG_TIMEOUT) + { + GESTURES.Touch.eventTime = rgGetCurrentTime(); + GESTURES.current = GESTURE_DRAG; + } + } + + GESTURES.Drag.vector.x = GESTURES.Touch.moveDownPositionA.x - GESTURES.Touch.downDragPosition.x; + GESTURES.Drag.vector.y = GESTURES.Touch.moveDownPositionA.y - GESTURES.Touch.downDragPosition.y; + } + } + else if (GESTURES.Touch.pointCount == 2) + { + if (event.touchAction == TOUCH_ACTION_DOWN) + { + GESTURES.Touch.downPositionA = event.position[0]; + GESTURES.Touch.downPositionB = event.position[1]; + + GESTURES.Touch.previousPositionA = GESTURES.Touch.downPositionA; + GESTURES.Touch.previousPositionB = GESTURES.Touch.downPositionB; + + //GESTURES.Pinch.distance = rgVector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.downPositionB); + + GESTURES.Pinch.vector.x = GESTURES.Touch.downPositionB.x - GESTURES.Touch.downPositionA.x; + GESTURES.Pinch.vector.y = GESTURES.Touch.downPositionB.y - GESTURES.Touch.downPositionA.y; + + GESTURES.current = GESTURE_HOLD; + GESTURES.Hold.timeDuration = rgGetCurrentTime(); + } + else if (event.touchAction == TOUCH_ACTION_MOVE) + { + GESTURES.Pinch.distance = rgVector2Distance(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB); + + GESTURES.Touch.moveDownPositionA = event.position[0]; + GESTURES.Touch.moveDownPositionB = event.position[1]; + + GESTURES.Pinch.vector.x = GESTURES.Touch.moveDownPositionB.x - GESTURES.Touch.moveDownPositionA.x; + GESTURES.Pinch.vector.y = GESTURES.Touch.moveDownPositionB.y - GESTURES.Touch.moveDownPositionA.y; + + if ((rgVector2Distance(GESTURES.Touch.previousPositionA, GESTURES.Touch.moveDownPositionA) >= MINIMUM_PINCH) || (rgVector2Distance(GESTURES.Touch.previousPositionB, GESTURES.Touch.moveDownPositionB) >= MINIMUM_PINCH)) + { + if ( rgVector2Distance(GESTURES.Touch.previousPositionA, GESTURES.Touch.previousPositionB) > rgVector2Distance(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB) ) GESTURES.current = GESTURE_PINCH_IN; + else GESTURES.current = GESTURE_PINCH_OUT; + } + else + { + GESTURES.current = GESTURE_HOLD; + GESTURES.Hold.timeDuration = rgGetCurrentTime(); + } + + // NOTE: Angle should be inverted in Y + GESTURES.Pinch.angle = 360.0f - rgVector2Angle(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB); + } + else if (event.touchAction == TOUCH_ACTION_UP) + { + GESTURES.Pinch.distance = 0.0f; + GESTURES.Pinch.angle = 0.0f; + GESTURES.Pinch.vector = (Vector2){ 0.0f, 0.0f }; + GESTURES.Touch.pointCount = 0; + + GESTURES.current = GESTURE_NONE; + } + } + else if (GESTURES.Touch.pointCount > 2) // More than two touch points + { + // TODO: Process gesture events for more than two points + } +} + +// Update gestures detected (must be called every frame) +void UpdateGestures(void) +{ + // NOTE: Gestures are processed through system callbacks on touch events + + // Detect GESTURE_HOLD + if (((GESTURES.current == GESTURE_TAP) || (GESTURES.current == GESTURE_DOUBLETAP)) && (GESTURES.Touch.pointCount < 2)) + { + GESTURES.current = GESTURE_HOLD; + GESTURES.Hold.timeDuration = rgGetCurrentTime(); + } + + // Detect GESTURE_NONE + if ((GESTURES.current == GESTURE_SWIPE_RIGHT) || (GESTURES.current == GESTURE_SWIPE_UP) || (GESTURES.current == GESTURE_SWIPE_LEFT) || (GESTURES.current == GESTURE_SWIPE_DOWN)) + { + GESTURES.current = GESTURE_NONE; + } +} + +// Get latest detected gesture +int GetGestureDetected(void) +{ + // Get current gesture only if enabled + return (GESTURES.enabledFlags & GESTURES.current); +} + +// Hold time measured in seconds +float GetGestureHoldDuration(void) +{ + // NOTE: time is calculated on current gesture HOLD + + double time = 0.0; + + if (GESTURES.current == GESTURE_HOLD) time = rgGetCurrentTime() - GESTURES.Hold.timeDuration; + + return (float)time; +} + +// Get drag vector (between initial touch point to current) +Vector2 GetGestureDragVector(void) +{ + // NOTE: drag vector is calculated on one touch points TOUCH_ACTION_MOVE + + return GESTURES.Drag.vector; +} + +// Get drag angle +// NOTE: Angle in degrees, horizontal-right is 0, counterclockwise +float GetGestureDragAngle(void) +{ + // NOTE: drag angle is calculated on one touch points TOUCH_ACTION_UP + + return GESTURES.Drag.angle; +} + +// Get distance between two pinch points +Vector2 GetGesturePinchVector(void) +{ + // NOTE: Pinch distance is calculated on two touch points TOUCH_ACTION_MOVE + + return GESTURES.Pinch.vector; +} + +// Get angle between two pinch points +// NOTE: Angle in degrees, horizontal-right is 0, counterclockwise +float GetGesturePinchAngle(void) +{ + // NOTE: pinch angle is calculated on two touch points TOUCH_ACTION_MOVE + + return GESTURES.Pinch.angle; +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +// Get angle from two-points vector with X-axis +static float rgVector2Angle(Vector2 v1, Vector2 v2) +{ + float angle = atan2f(v2.y - v1.y, v2.x - v1.x)*(180.0f/PI); + + if (angle < 0) angle += 360.0f; + + return angle; +} + +// Calculate distance between two Vector2 +static float rgVector2Distance(Vector2 v1, Vector2 v2) +{ + float result; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + + result = (float)sqrt(dx*dx + dy*dy); + + return result; +} + +// Time measure returned are seconds +static double rgGetCurrentTime(void) +{ + double time = 0; + +#if !defined(RGESTURES_STANDALONE) + time = GetTime(); +#else +#if defined(_WIN32) + unsigned long long clockFrequency = 0; + unsigned long long currentClockTicks = 0; + + QueryPerformanceFrequency(&clockFrequency); // BE CAREFUL: Costly operation! + QueryPerformanceCounter(¤tClockTicks); + + time = (double)currentClockTicks/clockFrequency; // Time in seconds +#endif +#if defined(__linux__) + // NOTE: Only for Linux-based systems + struct timespec now = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &now); + unsigned long long nanoSeconds = (unsigned long long)now.tv_sec*1000000000LLU + (unsigned long long)now.tv_nsec; + + time = ((double)nanoSeconds*1e-9); // Time in seconds +#endif +#if defined(__APPLE__) + //#define CLOCK_REALTIME CALENDAR_CLOCK // returns UTC time since 1970-01-01 + //#define CLOCK_MONOTONIC SYSTEM_CLOCK // returns the time since boot time + + clock_serv_t cclock = { 0 }; + mach_timespec_t now = { 0 }; + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + + // NOTE: OS X does not have clock_gettime(), using clock_get_time() + clock_get_time(cclock, &now); + mach_port_deallocate(mach_task_self(), cclock); + unsigned long long nanoSeconds = (unsigned long long)now.tv_sec*1000000000LLU + (unsigned long long)now.tv_nsec; + + time = ((double)nanoSeconds*1e-9); // Time in seconds +#endif +#endif + + return time; +} + +#endif // RGESTURES_IMPLEMENTATION diff --git a/src/libraries/raylib/raylib/src/rglfw.c b/src/libraries/raylib/raylib/src/rglfw.c new file mode 100644 index 0000000..5cdb48d --- /dev/null +++ b/src/libraries/raylib/raylib/src/rglfw.c @@ -0,0 +1,171 @@ +/********************************************************************************************** +* +* rglfw - raylib GLFW single file compilation +* +* This file includes latest GLFW sources (https://github.com/glfw/glfw) to be compiled together +* with raylib for all supported platforms, this way, no external dependencies are required. +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2017-2026 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +//#define _GLFW_BUILD_DLL // To build shared version +// Ref: http://www.glfw.org/docs/latest/compile.html#compile_manual + +// Platform options: +// _GLFW_WIN32 to use the Win32 API +// _GLFW_X11 to use the X Window System +// _GLFW_WAYLAND to use the Wayland API (experimental and incomplete) +// _GLFW_COCOA to use the Cocoa frameworks +// +// On Linux, _GLFW_X11 and _GLFW_WAYLAND can be combined + +//---------------------------------------------------------------------------------- +// Feature Test Macros required for this module +//---------------------------------------------------------------------------------- +#if (defined(__linux__) || defined(PLATFORM_WEB)) && (_POSIX_C_SOURCE < 199309L) + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext. +#endif +#if (defined(__linux__) || defined(PLATFORM_WEB)) && !defined(_GNU_SOURCE) + #undef _GNU_SOURCE + #define _GNU_SOURCE // Required for: ppoll if compiled with c99 without gnu ext. +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) + #define _GLFW_WIN32 +#endif +#if defined(__linux__) + #if !defined(_GLFW_WAYLAND) && !defined(_GLFW_X11) + #error "Cannot disable Wayland and X11 at the same time" + #endif +#endif +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) + #define _GLFW_X11 +#endif +#if defined(__APPLE__) + #define _GLFW_COCOA + #define _GLFW_USE_MENUBAR // To create and populate the menu bar when the first window is created + #define _GLFW_USE_RETINA // To have windows use the full resolution of Retina displays +#endif +#if defined(__TINYC__) + #define _WIN32_WINNT_WINXP 0x0501 +#endif + +// Common modules to all platforms +#include "external/glfw/src/init.c" +#include "external/glfw/src/platform.c" +#include "external/glfw/src/context.c" +#include "external/glfw/src/monitor.c" +#include "external/glfw/src/window.c" +#include "external/glfw/src/input.c" +#include "external/glfw/src/vulkan.c" +#include "external/glfw/src/egl_context.c" +#include "external/glfw/src/osmesa_context.c" + +//We need to define this function because GLFW calls it even though raylib does +//not use GLFW's Null platform +GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform) +{ + return GLFW_FALSE; +} + +#if defined(_WIN32) || defined(__CYGWIN__) + #include "external/glfw/src/win32_init.c" + #include "external/glfw/src/win32_module.c" + #include "external/glfw/src/win32_monitor.c" + #include "external/glfw/src/win32_window.c" + #include "external/glfw/src/win32_joystick.c" + #include "external/glfw/src/win32_time.c" + #include "external/glfw/src/win32_thread.c" + #include "external/glfw/src/wgl_context.c" +#endif + +#if defined(__linux__) + #include "external/glfw/src/posix_module.c" + #include "external/glfw/src/posix_thread.c" + #include "external/glfw/src/posix_time.c" + #include "external/glfw/src/posix_poll.c" + #include "external/glfw/src/linux_joystick.c" + #include "external/glfw/src/xkb_unicode.c" + + #if defined(_GLFW_WAYLAND) + //These functions need to be temporarily renamed because including + //source files for both Wayland and X11 results in pairs of functions + //with the same name and thus a build error + #define createKeyTables createKeyTablesWayland + #define translateKey translateKeyWayland + #define acquireMonitor acquireMonitorWayland + #define releaseMonitor releaseMonitorWayland + + #include "external/glfw/src/wl_init.c" + #include "external/glfw/src/wl_monitor.c" + #include "external/glfw/src/wl_window.c" + + #undef createKeyTables + #undef translateKey + #undef acquireMonitor + #undef releaseMonitor + #endif + #if defined(_GLFW_X11) + //These functions need to be temporarily renamed because including + //source files for both Wayland and X11 results in pairs of functions + //with the same name and thus a build error + #define createKeyTables createKeyTablesX11 + #define translateKey translateKeyX11 + #define acquireMonitor acquireMonitorX11 + #define releaseMonitor releaseMonitorX11 + + #include "external/glfw/src/x11_init.c" + #include "external/glfw/src/x11_monitor.c" + #include "external/glfw/src/x11_window.c" + #include "external/glfw/src/glx_context.c" + + #undef createKeyTables + #undef translateKey + #undef acquireMonitor + #undef releaseMonitor + #endif +#endif + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined( __NetBSD__) || defined(__DragonFly__) + #include "external/glfw/src/posix_module.c" + #include "external/glfw/src/posix_thread.c" + #include "external/glfw/src/posix_time.c" + #include "external/glfw/src/posix_poll.c" + #include "external/glfw/src/null_joystick.c" + #include "external/glfw/src/xkb_unicode.c" + + #include "external/glfw/src/x11_init.c" + #include "external/glfw/src/x11_monitor.c" + #include "external/glfw/src/x11_window.c" + #include "external/glfw/src/glx_context.c" +#endif + +#if defined(__APPLE__) + #include "external/glfw/src/posix_module.c" + #include "external/glfw/src/posix_thread.c" + #include "external/glfw/src/cocoa_init.m" + #include "external/glfw/src/cocoa_joystick.m" + #include "external/glfw/src/cocoa_monitor.m" + #include "external/glfw/src/cocoa_window.m" + #include "external/glfw/src/macos_time.c" + #include "external/glfw/src/nsgl_context.m" +#endif diff --git a/src/libraries/raylib/raylib/src/rlgl.h b/src/libraries/raylib/raylib/src/rlgl.h new file mode 100644 index 0000000..cc054e6 --- /dev/null +++ b/src/libraries/raylib/raylib/src/rlgl.h @@ -0,0 +1,5423 @@ +/********************************************************************************************** +* +* rlgl v6.0 - A multi-OpenGL abstraction layer with an immediate-mode style API +* +* DESCRIPTION: +* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0, ES 3.0) +* that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) +* +* ADDITIONAL NOTES: +* When choosing an OpenGL backend different than OpenGL 1.1, some internal buffers are +* initialized on rlglInit() to accumulate vertex data +* +* When an internal state change is required all the stored vertex data is rendered in a batch, +* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch +* +* Some resources are also loaded for convenience, here the complete list: +* - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data +* - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 +* - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) +* +* Internal buffer (and resources) must be manually unloaded calling rlglClose() +* +* CONFIGURATION: +* #define GRAPHICS_API_OPENGL_SOFTWARE +* #define GRAPHICS_API_OPENGL_11 +* #define GRAPHICS_API_OPENGL_21 +* #define GRAPHICS_API_OPENGL_33 +* #define GRAPHICS_API_OPENGL_43 +* #define GRAPHICS_API_OPENGL_ES2 +* #define GRAPHICS_API_OPENGL_ES3 +* Use selected OpenGL graphics backend, should be supported by platform +* Those preprocessor defines are only used on the rlgl module, if OpenGL version is +* required by any other module, use rlGetVersion() to check it +* +* #define RLGL_IMPLEMENTATION +* Generates the implementation of the library into the included file +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation +* +* #if RLGL_SHOW_GL_DETAILS_INFO +* Show OpenGL extensions and capabilities detailed logs on init +* +* #if RLGL_ENABLE_OPENGL_DEBUG_CONTEXT +* Enable debug context (only available on OpenGL 4.3) +* +* rlgl capabilities could be customized defining some internal +* values before library inclusion (default values listed): +* +* #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch elements limits +* #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +* #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +* #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of texture units that can be activated on batch drawing (SetShaderValueTexture()) +* +* #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack +* #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +* #define RL_CULL_DISTANCE_NEAR 0.05 // Default projection matrix near cull distance +* #define RL_CULL_DISTANCE_FAR 4000.0 // Default projection matrix far cull distance +* +* When loading a shader, the following vertex attributes and uniform location names are tried to be set automatically: +* WARNING: Pre-defined names can not be changed, they are used by default shaders and all raylib examples shaders, they are just listed here for reference +* +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEINDICES "vertexBoneIndices" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_INSTANCETRANSFORM "instanceTransform" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_INSTANCETRANSFORM +* +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView))) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONEMATRICES "boneMatrices" // bone matrices +* +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) +* +* DEPENDENCIES: +* - OpenGL libraries (depending on platform and OpenGL version selected) +* - GLAD OpenGL extensions loading library (only for OpenGL 3.3 Core, 4.3 Core) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2026 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RLGL_H +#define RLGL_H + +#define RLGL_VERSION "6.0" + +// Function specifiers in case library is build/used as a shared library +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +// NOTE: visibility(default) attribute makes symbols "visible" when compiled with -fvisibility=hidden +#if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // Building the library as a Win32 shared library (.dll) +#elif defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __attribute__((visibility("default"))) // Building the library as a Unix shared library (.so/.dylib) +#elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // Using the library as a Win32 shared library (.dll) +#endif + +// Function specifiers definition +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +// Support TRACELOG macros +#ifndef TRACELOG + #define TRACELOG(level, ...) (void)0 +#endif + +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(n,sz) realloc(n,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(p) free(p) +#endif + +// Security check in case no GRAPHICS_API_OPENGL_* defined +#if !defined(GRAPHICS_API_OPENGL_SOFTWARE) && \ + !defined(GRAPHICS_API_OPENGL_11) && \ + !defined(GRAPHICS_API_OPENGL_21) && \ + !defined(GRAPHICS_API_OPENGL_33) && \ + !defined(GRAPHICS_API_OPENGL_43) && \ + !defined(GRAPHICS_API_OPENGL_ES2) && \ + !defined(GRAPHICS_API_OPENGL_ES3) + #define GRAPHICS_API_OPENGL_33 +#endif + +// Security check in case multiple GRAPHICS_API_OPENGL_* defined +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_SOFTWARE) + #if defined(GRAPHICS_API_OPENGL_21) + #undef GRAPHICS_API_OPENGL_21 + #endif + #if defined(GRAPHICS_API_OPENGL_33) + #undef GRAPHICS_API_OPENGL_33 + #endif + #if defined(GRAPHICS_API_OPENGL_43) + #undef GRAPHICS_API_OPENGL_43 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + #undef GRAPHICS_API_OPENGL_ES2 + #endif +#endif + +// Software implementation uses OpenGL 1.1 functionality +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + #define GRAPHICS_API_OPENGL_11 +#endif + +// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality +// WARNING: Specific parts are checked with #if defines +#if defined(GRAPHICS_API_OPENGL_21) + #define GRAPHICS_API_OPENGL_33 +#endif + +// OpenGL 4.3 uses OpenGL 3.3 Core functionality +#if defined(GRAPHICS_API_OPENGL_43) + #define GRAPHICS_API_OPENGL_33 +#endif + +// OpenGL ES 3.0 uses OpenGL ES 2.0 functionality (and more) +#if defined(GRAPHICS_API_OPENGL_ES3) + #define GRAPHICS_API_OPENGL_ES2 +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Default internal render batch elements limits +#ifndef RL_DEFAULT_BATCH_BUFFER_ELEMENTS + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // This is the maximum amount of elements (quads) per batch + // NOTE: Be careful with text, every letter maps to a quad + #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + // Reducing memory sizes for embedded systems (RPI and HTML5) + // NOTE: On HTML5 (emscripten) this is allocated on heap, + // by default heap is only 16MB!...just take care... + #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 2048 + #endif +#endif +#ifndef RL_DEFAULT_BATCH_BUFFERS + #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +#endif +#ifndef RL_DEFAULT_BATCH_DRAWCALLS + #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +#endif +#ifndef RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS + #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) +#endif + +// Internal Matrix stack +#ifndef RL_MAX_MATRIX_STACK_SIZE + #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of Matrix stack +#endif + +// Shader limits +#ifndef RL_MAX_SHADER_LOCATIONS + #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +#endif + +// Projection matrix culling +#ifndef RL_CULL_DISTANCE_NEAR + #define RL_CULL_DISTANCE_NEAR 0.05 // Default near cull distance +#endif +#ifndef RL_CULL_DISTANCE_FAR + #define RL_CULL_DISTANCE_FAR 4000.0 // Default far cull distance +#endif + +// Texture parameters (equivalent to OpenGL defines) +#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S +#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T +#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER +#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER + +#define RL_TEXTURE_FILTER_NEAREST 0x2600 // GL_NEAREST +#define RL_TEXTURE_FILTER_LINEAR 0x2601 // GL_LINEAR +#define RL_TEXTURE_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_ANISOTROPIC 0x3000 // Anisotropic filter (custom identifier) +#define RL_TEXTURE_MIPMAP_BIAS_RATIO 0x4000 // Texture mipmap bias, percentage ratio (custom identifier) + +#define RL_TEXTURE_WRAP_REPEAT 0x2901 // GL_REPEAT +#define RL_TEXTURE_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE +#define RL_TEXTURE_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT +#define RL_TEXTURE_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT + +// Matrix modes (equivalent to OpenGL) +#define RL_MODELVIEW 0x1700 // GL_MODELVIEW +#define RL_PROJECTION 0x1701 // GL_PROJECTION +#define RL_TEXTURE 0x1702 // GL_TEXTURE + +// Primitive assembly draw modes +#define RL_LINES 0x0001 // GL_LINES +#define RL_TRIANGLES 0x0004 // GL_TRIANGLES +#define RL_QUADS 0x0007 // GL_QUADS + +// GL equivalent data types +#define RL_UNSIGNED_BYTE 0x1401 // GL_UNSIGNED_BYTE +#define RL_FLOAT 0x1406 // GL_FLOAT + +// GL buffer usage hint +#define RL_STREAM_DRAW 0x88E0 // GL_STREAM_DRAW +#define RL_STREAM_READ 0x88E1 // GL_STREAM_READ +#define RL_STREAM_COPY 0x88E2 // GL_STREAM_COPY +#define RL_STATIC_DRAW 0x88E4 // GL_STATIC_DRAW +#define RL_STATIC_READ 0x88E5 // GL_STATIC_READ +#define RL_STATIC_COPY 0x88E6 // GL_STATIC_COPY +#define RL_DYNAMIC_DRAW 0x88E8 // GL_DYNAMIC_DRAW +#define RL_DYNAMIC_READ 0x88E9 // GL_DYNAMIC_READ +#define RL_DYNAMIC_COPY 0x88EA // GL_DYNAMIC_COPY + +// GL Shader type +#define RL_FRAGMENT_SHADER 0x8B30 // GL_FRAGMENT_SHADER +#define RL_VERTEX_SHADER 0x8B31 // GL_VERTEX_SHADER +#define RL_COMPUTE_SHADER 0x91B9 // GL_COMPUTE_SHADER + +// GL blending factors +#define RL_ZERO 0 // GL_ZERO +#define RL_ONE 1 // GL_ONE +#define RL_SRC_COLOR 0x0300 // GL_SRC_COLOR +#define RL_ONE_MINUS_SRC_COLOR 0x0301 // GL_ONE_MINUS_SRC_COLOR +#define RL_SRC_ALPHA 0x0302 // GL_SRC_ALPHA +#define RL_ONE_MINUS_SRC_ALPHA 0x0303 // GL_ONE_MINUS_SRC_ALPHA +#define RL_DST_ALPHA 0x0304 // GL_DST_ALPHA +#define RL_ONE_MINUS_DST_ALPHA 0x0305 // GL_ONE_MINUS_DST_ALPHA +#define RL_DST_COLOR 0x0306 // GL_DST_COLOR +#define RL_ONE_MINUS_DST_COLOR 0x0307 // GL_ONE_MINUS_DST_COLOR +#define RL_SRC_ALPHA_SATURATE 0x0308 // GL_SRC_ALPHA_SATURATE +#define RL_CONSTANT_COLOR 0x8001 // GL_CONSTANT_COLOR +#define RL_ONE_MINUS_CONSTANT_COLOR 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR +#define RL_CONSTANT_ALPHA 0x8003 // GL_CONSTANT_ALPHA +#define RL_ONE_MINUS_CONSTANT_ALPHA 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA + +// GL blending functions/equations +#define RL_FUNC_ADD 0x8006 // GL_FUNC_ADD +#define RL_MIN 0x8007 // GL_MIN +#define RL_MAX 0x8008 // GL_MAX +#define RL_FUNC_SUBTRACT 0x800A // GL_FUNC_SUBTRACT +#define RL_FUNC_REVERSE_SUBTRACT 0x800B // GL_FUNC_REVERSE_SUBTRACT +#define RL_BLEND_EQUATION 0x8009 // GL_BLEND_EQUATION +#define RL_BLEND_EQUATION_RGB 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) +#define RL_BLEND_EQUATION_ALPHA 0x883D // GL_BLEND_EQUATION_ALPHA +#define RL_BLEND_DST_RGB 0x80C8 // GL_BLEND_DST_RGB +#define RL_BLEND_SRC_RGB 0x80C9 // GL_BLEND_SRC_RGB +#define RL_BLEND_DST_ALPHA 0x80CA // GL_BLEND_DST_ALPHA +#define RL_BLEND_SRC_ALPHA 0x80CB // GL_BLEND_SRC_ALPHA +#define RL_BLEND_COLOR 0x8005 // GL_BLEND_COLOR + +#define RL_READ_FRAMEBUFFER 0x8CA8 // GL_READ_FRAMEBUFFER +#define RL_DRAW_FRAMEBUFFER 0x8CA9 // GL_DRAW_FRAMEBUFFER + +// Default shader vertex attribute locations +// NOTE: Locations can be redefined by user if required +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 +#endif + #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES 7 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INSTANCETRANSFORM + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INSTANCETRANSFORM 9 +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if !defined(__cplusplus) +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) + #include +#elif !defined(bool) + typedef enum bool { false = 0, true = !false } bool; + #define RL_BOOL_TYPE +#endif +#endif + +#if !defined(RL_MATRIX_TYPE) +// Matrix, 4x4 components, column major, OpenGL style, right handed +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; +#define RL_MATRIX_TYPE +#endif + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +typedef struct rlVertexBuffer { + int elementCount; // Number of elements in the buffer (QUADS) + + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *normals; // Vertex normal (XYZ - 3 components per vertex) (shader-location = 2) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int vboId[5]; // OpenGL Vertex Buffer Objects id (5 types of vertex data) +} rlVertexBuffer; + +// Draw call type +// NOTE: Only texture changes register a new draw, other state-change-related elements are not +// used at this moment (vaoId, shaderId, matrices), raylib forces a batch draw call if any +// of those state-change happens (this is done in core module) +typedef struct rlDrawCall { + int mode; // Drawing mode: LINES, TRIANGLES, QUADS + int vertexCount; // Number of vertex of the draw + int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) + //unsigned int vaoId; // Vertex array id to be used on the draw -> Using RLGL.currentBatch->vertexBuffer.vaoId + //unsigned int shaderId; // Shader id to be used on the draw -> Using RLGL.currentShaderId + unsigned int textureId; // Texture id to be used on the draw -> Use to create new draw call if changes + + //Matrix projection; // Projection matrix for this draw -> Using RLGL.projection by default + //Matrix modelview; // Modelview matrix for this draw -> Using RLGL.modelview by default +} rlDrawCall; + +// rlRenderBatch type +typedef struct rlRenderBatch { + int bufferCount; // Number of vertex buffers (multi-buffering support) + int currentBuffer; // Current buffer tracking in case of multi-buffering + rlVertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data + + rlDrawCall *draws; // Draw calls array, depends on textureId + int drawCounter; // Draw calls counter + float currentDepth; // Current depth value for next draw +} rlRenderBatch; + +// OpenGL version +typedef enum { + RL_OPENGL_SOFTWARE = 0, // Software rendering + RL_OPENGL_11, // OpenGL 1.1 + RL_OPENGL_21, // OpenGL 2.1 (GLSL 120) + RL_OPENGL_33, // OpenGL 3.3 (GLSL 330) + RL_OPENGL_43, // OpenGL 4.3 (using GLSL 330) + RL_OPENGL_ES_20, // OpenGL ES 2.0 (GLSL 100) + RL_OPENGL_ES_30 // OpenGL ES 3.0 (GLSL 300 es) +} rlGlVersion; + +// Trace log level +// NOTE: Organized by priority level +typedef enum { + RL_LOG_ALL = 0, // Display all logs + RL_LOG_TRACE, // Trace logging, intended for internal use only + RL_LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + RL_LOG_INFO, // Info logging, used for program execution info + RL_LOG_WARNING, // Warning logging, used on recoverable failures + RL_LOG_ERROR, // Error logging, used on unrecoverable failures + RL_LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + RL_LOG_NONE // Disable logging +} rlTraceLogLevel; + +// Texture pixel formats +// NOTE: Support depends on OpenGL version +typedef enum { + RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + RL_PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) + RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) + RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) + RL_PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} rlPixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + RL_TEXTURE_FILTER_POINT = 0, // No filter, pixel approximation + RL_TEXTURE_FILTER_BILINEAR, // Linear filtering + RL_TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + RL_TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + RL_TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + RL_TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} rlTextureFilter; + +// Color blending modes (pre-defined) +typedef enum { + RL_BLEND_ALPHA = 0, // Blend textures considering alpha (default) + RL_BLEND_ADDITIVE, // Blend textures adding colors + RL_BLEND_MULTIPLIED, // Blend textures multiplying colors + RL_BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + RL_BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + RL_BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha + RL_BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) + RL_BLEND_CUSTOM_SEPARATE // Blend textures using custom src/dst factors (use rlSetBlendFactorsSeparate()) +} rlBlendMode; + +// Shader location point type +typedef enum { + RL_SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position + RL_SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 + RL_SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 + RL_SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal + RL_SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent + RL_SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color + RL_SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection + RL_SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) + RL_SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection + RL_SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) + RL_SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal + RL_SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view + RL_SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color + RL_SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color + RL_SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color + RL_SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: RL_SHADER_LOC_MAP_DIFFUSE) + RL_SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: RL_SHADER_LOC_MAP_SPECULAR) + RL_SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal + RL_SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness + RL_SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion + RL_SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission + RL_SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height + RL_SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap + RL_SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance + RL_SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter + RL_SHADER_LOC_MAP_BRDF // Shader location: sampler2d texture: brdf +} rlShaderLocationIndex; + +#define RL_SHADER_LOC_MAP_DIFFUSE RL_SHADER_LOC_MAP_ALBEDO +#define RL_SHADER_LOC_MAP_SPECULAR RL_SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + RL_SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float + RL_SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) + RL_SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) + RL_SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) + RL_SHADER_UNIFORM_INT, // Shader uniform type: int + RL_SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) + RL_SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) + RL_SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + RL_SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int + RL_SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) + RL_SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) + RL_SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) + RL_SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d +} rlShaderUniformDataType; + +// Shader attribute data types +typedef enum { + RL_SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float + RL_SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) + RL_SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) + RL_SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) +} rlShaderAttributeDataType; + +// Framebuffer attachment type +// NOTE: By default up to 8 color channels defined, but it can be more +typedef enum { + RL_ATTACHMENT_COLOR_CHANNEL0 = 0, // Framebuffer attachment type: color 0 + RL_ATTACHMENT_COLOR_CHANNEL1 = 1, // Framebuffer attachment type: color 1 + RL_ATTACHMENT_COLOR_CHANNEL2 = 2, // Framebuffer attachment type: color 2 + RL_ATTACHMENT_COLOR_CHANNEL3 = 3, // Framebuffer attachment type: color 3 + RL_ATTACHMENT_COLOR_CHANNEL4 = 4, // Framebuffer attachment type: color 4 + RL_ATTACHMENT_COLOR_CHANNEL5 = 5, // Framebuffer attachment type: color 5 + RL_ATTACHMENT_COLOR_CHANNEL6 = 6, // Framebuffer attachment type: color 6 + RL_ATTACHMENT_COLOR_CHANNEL7 = 7, // Framebuffer attachment type: color 7 + RL_ATTACHMENT_DEPTH = 100, // Framebuffer attachment type: depth + RL_ATTACHMENT_STENCIL = 200, // Framebuffer attachment type: stencil +} rlFramebufferAttachType; + +// Framebuffer texture attachment type +typedef enum { + RL_ATTACHMENT_CUBEMAP_POSITIVE_X = 0, // Framebuffer texture attachment type: cubemap, +X side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_X = 1, // Framebuffer texture attachment type: cubemap, -X side + RL_ATTACHMENT_CUBEMAP_POSITIVE_Y = 2, // Framebuffer texture attachment type: cubemap, +Y side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Y = 3, // Framebuffer texture attachment type: cubemap, -Y side + RL_ATTACHMENT_CUBEMAP_POSITIVE_Z = 4, // Framebuffer texture attachment type: cubemap, +Z side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Z = 5, // Framebuffer texture attachment type: cubemap, -Z side + RL_ATTACHMENT_TEXTURE2D = 100, // Framebuffer texture attachment type: texture2d + RL_ATTACHMENT_RENDERBUFFER = 200, // Framebuffer texture attachment type: renderbuffer +} rlFramebufferAttachTextureType; + +// Face culling mode +typedef enum { + RL_CULL_FACE_FRONT = 0, + RL_CULL_FACE_BACK +} rlCullMode; + +//------------------------------------------------------------------------------------ +// Functions Declaration - Matrix operations +//------------------------------------------------------------------------------------ + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed +RLAPI void rlPushMatrix(void); // Push the current matrix to stack +RLAPI void rlPopMatrix(void); // Pop latest inserted matrix from stack +RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix +RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix +RLAPI void rlRotatef(float angle, float x, float y, float z); // Multiply the current matrix by a rotation matrix +RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix +RLAPI void rlMultMatrixf(const float *matf); // Multiply the current matrix by another matrix +RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area +RLAPI void rlSetClipPlanes(double nearPlane, double farPlane); // Set clip planes distances +RLAPI double rlGetCullDistanceNear(void); // Get cull plane distance near +RLAPI double rlGetCullDistanceFar(void); // Get cull plane distance far + +//------------------------------------------------------------------------------------ +// Functions Declaration - Vertex level operations +//------------------------------------------------------------------------------------ +RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) +RLAPI void rlEnd(void); // Finish vertex providing +RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int +RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float +RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float +RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float +RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float +RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte +RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float +RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float + +//------------------------------------------------------------------------------------ +// Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) +// NOTE: This functions are used to completely abstract raylib code from OpenGL layer, +// some of them are direct wrappers over OpenGL calls, some others are custom +//------------------------------------------------------------------------------------ + +// Vertex buffers state +RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array (VAO, if supported) +RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) +RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) +RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) +RLAPI void rlEnableVertexBufferElement(unsigned int id); // Enable vertex buffer element (VBO element) +RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) +RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index +RLAPI void rlDisableVertexAttribute(unsigned int index); // Disable vertex attribute index +RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); // Enable attribute state pointer +RLAPI void rlDisableStatePointer(int vertexAttribType); // Disable attribute state pointer + +// Textures state +RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot +RLAPI void rlEnableTexture(unsigned int id); // Enable texture +RLAPI void rlDisableTexture(void); // Disable texture +RLAPI void rlEnableTextureCubemap(unsigned int id); // Enable texture cubemap +RLAPI void rlDisableTextureCubemap(void); // Disable texture cubemap +RLAPI void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) +RLAPI void rlCubemapParameters(unsigned int id, int param, int value); // Set cubemap parameters (filter, wrap) + +// Shader state +RLAPI void rlEnableShader(unsigned int id); // Enable shader program +RLAPI void rlDisableShader(void); // Disable shader program + +// Framebuffer state +RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) +RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer +RLAPI unsigned int rlGetActiveFramebuffer(void); // Get the currently active render texture (fbo), 0 for default framebuffer +RLAPI void rlActiveDrawBuffers(int count); // Activate multiple draw color buffers +RLAPI void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask); // Blit active framebuffer to main framebuffer +RLAPI void rlBindFramebuffer(unsigned int target, unsigned int framebuffer); // Bind framebuffer (FBO) + +// General render state +RLAPI void rlEnableColorBlend(void); // Enable color blending +RLAPI void rlDisableColorBlend(void); // Disable color blending +RLAPI void rlEnableDepthTest(void); // Enable depth test +RLAPI void rlDisableDepthTest(void); // Disable depth test +RLAPI void rlEnableDepthMask(void); // Enable depth write +RLAPI void rlDisableDepthMask(void); // Disable depth write +RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling +RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling +RLAPI void rlColorMask(bool r, bool g, bool b, bool a); // Color mask control +RLAPI void rlSetCullFace(int mode); // Set face culling mode +RLAPI void rlEnableScissorTest(void); // Enable scissor test +RLAPI void rlDisableScissorTest(void); // Disable scissor test +RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test +RLAPI void rlEnablePointMode(void); // Enable point mode +RLAPI void rlDisablePointMode(void); // Disable point mode +RLAPI void rlSetPointSize(float size); // Set the point drawing size +RLAPI float rlGetPointSize(void); // Get the point drawing size +RLAPI void rlEnableWireMode(void); // Enable wire mode +RLAPI void rlDisableWireMode(void); // Disable wire mode +RLAPI void rlSetLineWidth(float width); // Set the line drawing width +RLAPI float rlGetLineWidth(void); // Get the line drawing width +RLAPI void rlEnableSmoothLines(void); // Enable line aliasing +RLAPI void rlDisableSmoothLines(void); // Disable line aliasing +RLAPI void rlEnableStereoRender(void); // Enable stereo rendering +RLAPI void rlDisableStereoRender(void); // Disable stereo rendering +RLAPI bool rlIsStereoRenderEnabled(void); // Check if stereo render is enabled + +RLAPI void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color +RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) +RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes +RLAPI void rlSetBlendMode(int mode); // Set blending mode +RLAPI void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation); // Set blending mode factor and equation (using OpenGL factors) +RLAPI void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha); // Set blending mode factors and equations separately (using OpenGL factors) + +//------------------------------------------------------------------------------------ +// Functions Declaration - rlgl functionality +//------------------------------------------------------------------------------------ +// rlgl initialization functions +RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) +RLAPI void rlglClose(void); // De-initialize rlgl (buffers, shaders, textures) +RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions (loader function required) +RLAPI void *rlGetProcAddress(const char *procName); // Get OpenGL procedure address +RLAPI int rlGetVersion(void); // Get current OpenGL version +RLAPI void rlSetFramebufferWidth(int width); // Set current framebuffer width +RLAPI int rlGetFramebufferWidth(void); // Get default framebuffer width +RLAPI void rlSetFramebufferHeight(int height); // Set current framebuffer height +RLAPI int rlGetFramebufferHeight(void); // Get default framebuffer height + +RLAPI unsigned int rlGetTextureIdDefault(void); // Get default texture id +RLAPI unsigned int rlGetShaderIdDefault(void); // Get default shader id +RLAPI int *rlGetShaderLocsDefault(void); // Get default shader locations + +// Render batch management +// NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode +// but this render batch API is exposed in case of custom batches are required +RLAPI rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system +RLAPI void rlUnloadRenderBatch(rlRenderBatch batch); // Unload render batch system +RLAPI void rlDrawRenderBatch(rlRenderBatch *batch); // Draw render batch data (Update->Draw->Reset) +RLAPI void rlSetRenderBatchActive(rlRenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) +RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch +RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex + +RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits + +//------------------------------------------------------------------------------------------------------------------------ + +// Vertex buffers management +RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported +RLAPI unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic); // Load a vertex buffer object +RLAPI unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic); // Load vertex buffer elements object +RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, const void *data, int dataSize, int offset); // Update vertex buffer object data on GPU buffer +RLAPI void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset); // Update vertex buffer elements data on GPU buffer +RLAPI void rlUnloadVertexArray(unsigned int vaoId); // Unload vertex array (vao) +RLAPI void rlUnloadVertexBuffer(unsigned int vboId); // Unload vertex buffer object +RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset); // Set vertex attribute data configuration +RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); // Set vertex attribute data divisor +RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value, when attribute to provided +RLAPI void rlDrawVertexArray(int offset, int count); // Draw vertex array (currently active vao) +RLAPI void rlDrawVertexArrayElements(int offset, int count, const void *buffer); // Draw vertex array elements +RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); // Draw vertex array (currently active vao) with instancing +RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances); // Draw vertex array elements with instancing + +// Textures management +RLAPI unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount); // Load texture data +RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) +RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount); // Load texture cubemap data +RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update texture with new data on GPU +RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats +RLAPI const char *rlGetPixelFormatName(unsigned int format); // Get name string for pixel format +RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory +RLAPI void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps); // Generate mipmap data for selected texture +RLAPI void *rlReadTexturePixels(unsigned int id, int width, int height, int format); // Read texture pixel data +RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) + +// Framebuffer management (fbo) +RLAPI unsigned int rlLoadFramebuffer(void); // Load an empty framebuffer +RLAPI void rlFramebufferAttach(unsigned int id, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer +RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete +RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU +// WARNING: Copy and resize framebuffer functionality only defined for software backend +RLAPI void rlCopyFramebuffer(int x, int y, int width, int height, int format, void *pixels); // Copy framebuffer pixel data to internal buffer +RLAPI void rlResizeFramebuffer(int width, int height); // Resize internal framebuffer + +// Shaders management +RLAPI unsigned int rlLoadShader(const char *code, int type); // Load (compile) shader and return shader id (type: RL_VERTEX_SHADER, RL_FRAGMENT_SHADER, RL_COMPUTE_SHADER) +RLAPI unsigned int rlLoadShaderProgram(const char *vsCode, const char *fsCode); // Load shader from code strings +RLAPI unsigned int rlLoadShaderProgramEx(unsigned int vsId, unsigned int fsId); // Load shader program, using already loaded shader ids +RLAPI unsigned int rlLoadShaderProgramCompute(unsigned int csId); // Load compute shader program +RLAPI void rlUnloadShader(unsigned int id); // Unload shader, loaded with rlLoadShader() +RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program +RLAPI int rlGetLocationUniform(unsigned int id, const char *uniformName); // Get shader location uniform, requires shader program id +RLAPI int rlGetLocationAttrib(unsigned int id, const char *attribName); // Get shader location attribute, requires shader program id +RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform +RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix +RLAPI void rlSetUniformMatrices(int locIndex, const Matrix *mat, int count); // Set shader value matrices +RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler +RLAPI void rlSetShader(unsigned int id, int *locs); // Set shader currently active (id and locations) + +// Compute shader management +RLAPI void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ); // Dispatch compute shader (equivalent to *draw* for graphics pipeline) + +// Shader buffer storage object management (ssbo) +RLAPI unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint); // Load shader storage buffer object (SSBO) +RLAPI void rlUnloadShaderBuffer(unsigned int ssboId); // Unload shader storage buffer object (SSBO) +RLAPI void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset); // Update SSBO buffer data +RLAPI void rlBindShaderBuffer(unsigned int id, unsigned int index); // Bind SSBO buffer +RLAPI void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset); // Read SSBO buffer data (GPU->CPU) +RLAPI void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count); // Copy SSBO data between buffers +RLAPI unsigned int rlGetShaderBufferSize(unsigned int id); // Get SSBO buffer size + +// Buffer management +RLAPI void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly); // Bind image texture + +// Matrix state management +RLAPI Matrix rlGetMatrixModelview(void); // Get internal modelview matrix +RLAPI Matrix rlGetMatrixProjection(void); // Get internal projection matrix +RLAPI Matrix rlGetMatrixTransform(void); // Get internal accumulated transform matrix +RLAPI Matrix rlGetMatrixProjectionStereo(int eye); // Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye); // Get internal view offset matrix for stereo render (selected eye) +RLAPI void rlSetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) +RLAPI void rlSetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) +RLAPI void rlSetMatrixProjectionStereo(Matrix right, Matrix left); // Set eyes projection matrices for stereo rendering +RLAPI void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left); // Set eyes view offsets matrices for stereo rendering + +// Quick and dirty cube/quad buffers load->draw->unload +RLAPI void rlLoadDrawCube(void); // Load and draw a cube +RLAPI void rlLoadDrawQuad(void); // Load and draw a quad + +#if defined(__cplusplus) +} +#endif + +#endif // RLGL_H + +/*********************************************************************************** +* +* RLGL IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RLGL_IMPLEMENTATION) + +// Expose OpenGL functions from glad in raylib +#if defined(BUILD_LIBTYPE_SHARED) + #define GLAD_API_CALL_EXPORT + #define GLAD_API_CALL_EXPORT_BUILD +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(GRAPHICS_API_OPENGL_SOFTWARE) + #define RLSW_IMPLEMENTATION + #define SW_MALLOC(sz) RL_MALLOC(sz) + #define SW_CALLOC(n,sz) RL_CALLOC(n, sz) + #define SW_REALLOC(ptr, newSz) RL_REALLOC(ptr, newSz) + #define SW_FREE(ptr) RL_FREE(ptr) + #include "external/rlsw.h" // OpenGL 1.1 software implementation + #else + #if defined(__APPLE__) + #include // OpenGL 1.1 library for OSX + #include // OpenGL extensions library + #else + // APIENTRY for OpenGL function pointer declarations is required + #if !defined(APIENTRY) + #if defined(_WIN32) + #define APIENTRY __stdcall + #else + #define APIENTRY + #endif + #endif + // WINGDIAPI definition. Some Windows OpenGL headers need it + #if !defined(WINGDIAPI) && defined(_WIN32) + #define WINGDIAPI __declspec(dllimport) + #endif + + #include // OpenGL 1.1 library + #endif + #endif +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + #define GLAD_MALLOC RL_MALLOC + #define GLAD_FREE RL_FREE + + #define GLAD_GL_IMPLEMENTATION + #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + #include // OpenGL ES 3.0 library + #define GL_GLEXT_PROTOTYPES + #include // OpenGL ES 2.0 extensions library +#elif defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 can be enabled on Desktop platforms, + // in that case, functions are loaded from a custom glad for OpenGL ES 2.0 + // TODO: OpenGL ES 2.0 support shouldn't be platform-dependent, neither require GLAD + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) + #define GLAD_GLES2_IMPLEMENTATION + #include "external/glad_gles2.h" + #else + #define GL_GLEXT_PROTOTYPES + //#include // EGL library -> not required, platform layer + #include // OpenGL ES 2.0 library + #include // OpenGL ES 2.0 extensions library + #endif + + // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi + // provided headers (despite being defined in official Khronos GLES2 headers) + // TODO: Avoid raylib platform-dependent code on rlgl, it should be a completely portable library + #if defined(PLATFORM_DRM) // Use CONFIG_DRM? + typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); + #endif +#endif + +#include // Required for: calloc(), free() +#include // Required for: strcmp(), strlen() [Used in rlglInit(), on extensions loading] +#include // Required for: sqrtf(), sinf(), cosf(), floor(), log() +#include // Required for: INT_MAX + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +#ifndef GL_SHADING_LANGUAGE_VERSION + #define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#endif + +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif +#ifndef GL_ETC1_RGB8_OES + #define GL_ETC1_RGB8_OES 0x8D64 +#endif +#ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 +#endif +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#endif +#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#endif +#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR + #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR + #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 +#endif + +#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#endif + +#ifndef GL_PROGRAM_POINT_SIZE + #define GL_PROGRAM_POINT_SIZE 0x8642 +#endif + +#ifndef GL_LINE_WIDTH + #define GL_LINE_WIDTH 0x0B21 +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + #define GL_LUMINANCE 0x1909 + #define GL_LUMINANCE_ALPHA 0x190A +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + #define glClearDepth glClearDepthf + #if !defined(GRAPHICS_API_OPENGL_ES3) + #define GL_READ_FRAMEBUFFER GL_FRAMEBUFFER + #define GL_DRAW_FRAMEBUFFER GL_FRAMEBUFFER + #define GL_DRAW_FRAMEBUFFER_BINDING GL_FRAMEBUFFER_BINDING + #endif +#endif + +// Default shader vertex attribute names to set location points +// WARNING: Pre-defined names can not be changed, they are used by default shaders and all raylib examples shaders, they are just listed here for reference +#define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION +#define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD +#define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL +#define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR +#define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +#define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 +#define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEINDICES "vertexBoneIndices" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES +#define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS +#define RL_DEFAULT_SHADER_ATTRIB_NAME_INSTANCETRANSFORM "instanceTransform" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_INSTANCETRANSFORM + +#define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +#define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +#define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +#define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +#define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) +#define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +#define RL_DEFAULT_SHADER_UNIFORM_NAME_BONEMATRICES "boneMatrices" // bone matrices (required for GPU skinning) + +#define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +#define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +#define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) + +//---------------------------------------------------------------------------------- +// Module Types and Structures Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + +typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions loader signature (same as GLADloadproc) + +typedef struct rlglData { + rlRenderBatch *currentBatch; // Current render batch + rlRenderBatch defaultBatch; // Default internal render batch + + rlglLoadProc loader; // OpenGL function loader + + struct { + int vertexCounter; // Current active render batch vertex counter (generic, used for all batches) + float texcoordx, texcoordy; // Current active texture coordinate (added on glVertex*()) + float normalx, normaly, normalz; // Current active normal (added on glVertex*()) + unsigned char colorr, colorg, colorb, colora; // Current active color (added on glVertex*()) + + int currentMatrixMode; // Current matrix mode + Matrix *currentMatrix; // Current matrix pointer + Matrix modelview; // Default modelview matrix + Matrix projection; // Default projection matrix + Matrix transform; // Transform matrix to be used with rlTranslate, rlRotate, rlScale + bool transformRequired; // Require transform matrix application to current draw-call vertex (if required) + Matrix stack[RL_MAX_MATRIX_STACK_SIZE];// Matrix stack for push/pop + int stackCounter; // Matrix stack counter + + unsigned int currentTextureId; // Current texture id to be used on glBegin + unsigned int defaultTextureId; // Default texture used on shapes/poly drawing (required by shader) + unsigned int activeTextureId[RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS]; // Active texture ids to be enabled on batch drawing (0 active by default) + unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) + unsigned int defaultFShaderId; // Default fragment shader id (used by default shader program) + unsigned int defaultShaderId; // Default shader program id, supports vertex color and diffuse texture + int *defaultShaderLocs; // Default shader locations pointer to be used on rendering + unsigned int currentShaderId; // Current shader id to be used on rendering (by default, defaultShaderId) + int *currentShaderLocs; // Current shader locations pointer to be used on rendering (by default, defaultShaderLocs) + + bool stereoRender; // Stereo rendering flag + Matrix projectionStereo[2]; // VR stereo rendering eyes projection matrices + Matrix viewOffsetStereo[2]; // VR stereo rendering eyes view offset matrices + + // Blending variables + int currentBlendMode; // Blending mode active + int glBlendSrcFactor; // Blending source factor + int glBlendDstFactor; // Blending destination factor + int glBlendEquation; // Blending equation + int glBlendSrcFactorRGB; // Blending source RGB factor + int glBlendDestFactorRGB; // Blending destination RGB factor + int glBlendSrcFactorAlpha; // Blending source alpha factor + int glBlendDestFactorAlpha; // Blending destination alpha factor + int glBlendEquationRGB; // Blending equation for RGB + int glBlendEquationAlpha; // Blending equation for alpha + bool glCustomBlendModeModified; // Custom blending factor and equation modification status + + int framebufferWidth; // Current framebuffer width + int framebufferHeight; // Current framebuffer height + + } State; // Renderer state + struct { + bool vao; // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object) + bool instancing; // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays) + bool texNPOT; // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot) + bool texDepth; // Depth textures supported (GL_ARB_depth_texture, GL_OES_depth_texture) + bool texDepthWebGL; // Depth textures supported WebGL specific (GL_WEBGL_depth_texture) + bool texFloat32; // float textures support (32 bit per channel) (GL_OES_texture_float) + bool texFloat16; // half float textures support (16 bit per channel) (GL_OES_texture_half_float) + bool texCompDXT; // DDS texture compression support (GL_EXT_texture_compression_s3tc, GL_WEBGL_compressed_texture_s3tc, GL_WEBKIT_WEBGL_compressed_texture_s3tc) + bool texCompETC1; // ETC1 texture compression support (GL_OES_compressed_ETC1_RGB8_texture, GL_WEBGL_compressed_texture_etc1) + bool texCompETC2; // ETC2/EAC texture compression support (GL_ARB_ES3_compatibility) + bool texCompPVRT; // PVR texture compression support (GL_IMG_texture_compression_pvrtc) + bool texCompASTC; // ASTC texture compression support (GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr) + bool texMirrorClamp; // Clamp mirror wrap mode supported (GL_EXT_texture_mirror_clamp) + bool texAnisoFilter; // Anisotropic texture filtering support (GL_EXT_texture_filter_anisotropic) + bool computeShader; // Compute shaders support (GL_ARB_compute_shader) + bool ssbo; // Shader storage buffer object support (GL_ARB_shader_storage_buffer_object) + + float maxAnisotropyLevel; // Maximum anisotropy level supported (minimum is 2.0f) + int maxDepthBits; // Maximum bits for depth component + + } ExtSupported; // Extensions supported flags +} rlglData; + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static bool isGpuReady = false; +static double rlCullDistanceNear = RL_CULL_DISTANCE_NEAR; +static double rlCullDistanceFar = RL_CULL_DISTANCE_FAR; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static rlglData RLGL = { 0 }; +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) && !defined(GRAPHICS_API_OPENGL_ES3) +// VAO functions entry points +// NOTE: VAO functionality is exposed through extensions (OES) +static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays = NULL; +static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL; +static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL; + +// Instancing functionality entry points +// NOTE: Instancing functionality could be available through extensions +static PFNGLDRAWARRAYSINSTANCEDEXTPROC glDrawArraysInstanced = NULL; +static PFNGLDRAWELEMENTSINSTANCEDEXTPROC glDrawElementsInstanced = NULL; +static PFNGLVERTEXATTRIBDIVISOREXTPROC glVertexAttribDivisor = NULL; +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static void rlLoadShaderDefault(void); // Load default shader +static void rlUnloadShaderDefault(void); // Unload default shader +#if RLGL_SHOW_GL_DETAILS_INFO +static const char *rlGetCompressedFormatName(int format); // Get compressed format official GL identifier name +#endif +#endif + +static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) + +static Matrix rlMatrixIdentity(void); // Get identity matrix +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Auxiliar matrix math functions +typedef struct rl_float16 { float v[16]; } rl_float16; +static rl_float16 rlMatrixToFloatV(Matrix mat); // Get float array of matrix data +#define rlMatrixToFloat(mat) (rlMatrixToFloatV(mat).v) // Get float vector for Matrix +static Matrix rlMatrixMultiply(Matrix left, Matrix right); // Multiply two matrices +static Matrix rlMatrixTranspose(Matrix mat); // Transposes provided matrix +static Matrix rlMatrixInvert(Matrix mat); // Invert provided matrix +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix operations +//---------------------------------------------------------------------------------- + +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlMatrixMode(int mode) +{ + switch (mode) + { + case RL_PROJECTION: glMatrixMode(GL_PROJECTION); break; + case RL_MODELVIEW: glMatrixMode(GL_MODELVIEW); break; + case RL_TEXTURE: glMatrixMode(GL_TEXTURE); break; + default: break; + } +} + +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + glFrustum(left, right, bottom, top, znear, zfar); +} + +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + glOrtho(left, right, bottom, top, znear, zfar); +} + +void rlPushMatrix(void) { glPushMatrix(); } +void rlPopMatrix(void) { glPopMatrix(); } +void rlLoadIdentity(void) { glLoadIdentity(); } +void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); } +void rlRotatef(float angle, float x, float y, float z) { glRotatef(angle, x, y, z); } +void rlScalef(float x, float y, float z) { glScalef(x, y, z); } +void rlMultMatrixf(const float *matf) { glMultMatrixf(matf); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Choose the current matrix to be transformed +void rlMatrixMode(int mode) +{ + if (mode == RL_PROJECTION) RLGL.State.currentMatrix = &RLGL.State.projection; + else if (mode == RL_MODELVIEW) RLGL.State.currentMatrix = &RLGL.State.modelview; + //else if (mode == RL_TEXTURE) // Not supported + + RLGL.State.currentMatrixMode = mode; +} + +// Push the current matrix into RLGL.State.stack +void rlPushMatrix(void) +{ + if (RLGL.State.stackCounter >= RL_MAX_MATRIX_STACK_SIZE) + { + TRACELOG(RL_LOG_ERROR, "RLGL: Matrix stack overflow (RL_MAX_MATRIX_STACK_SIZE)"); + return; + } + + if (RLGL.State.currentMatrixMode == RL_MODELVIEW) + { + RLGL.State.transformRequired = true; + RLGL.State.currentMatrix = &RLGL.State.transform; + } + + RLGL.State.stack[RLGL.State.stackCounter] = *RLGL.State.currentMatrix; + RLGL.State.stackCounter++; +} + +// Pop latest inserted matrix from RLGL.State.stack +void rlPopMatrix(void) +{ + if (RLGL.State.stackCounter > 0) + { + Matrix mat = RLGL.State.stack[RLGL.State.stackCounter - 1]; + *RLGL.State.currentMatrix = mat; + RLGL.State.stackCounter--; + } + + if ((RLGL.State.stackCounter == 0) && (RLGL.State.currentMatrixMode == RL_MODELVIEW)) + { + RLGL.State.currentMatrix = &RLGL.State.modelview; + RLGL.State.transformRequired = false; + } +} + +// Reset current matrix to identity matrix +void rlLoadIdentity(void) +{ + *RLGL.State.currentMatrix = rlMatrixIdentity(); +} + +// Multiply the current matrix by a translation matrix +void rlTranslatef(float x, float y, float z) +{ + Matrix matTranslation = rlMatrixIdentity(); + + // Set translation component of matrix + matTranslation.m12 = x; + matTranslation.m13 = y; + matTranslation.m14 = z; + + // NOTE: Transposing matrix by multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matTranslation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a rotation matrix +// NOTE: The provided angle must be in degrees +void rlRotatef(float angle, float x, float y, float z) +{ + Matrix matRotation = rlMatrixIdentity(); + + // Axis vector (x, y, z) normalization + float lengthSquared = x*x + y*y + z*z; + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float inverseLength = 1.0f/sqrtf(lengthSquared); + x *= inverseLength; + y *= inverseLength; + z *= inverseLength; + } + + // Rotation matrix generation + float sinres = sinf(DEG2RAD*angle); + float cosres = cosf(DEG2RAD*angle); + float t = 1.0f - cosres; + + matRotation.m0 = x*x*t + cosres; + matRotation.m1 = y*x*t + z*sinres; + matRotation.m2 = z*x*t - y*sinres; + matRotation.m3 = 0.0f; + + matRotation.m4 = x*y*t - z*sinres; + matRotation.m5 = y*y*t + cosres; + matRotation.m6 = z*y*t + x*sinres; + matRotation.m7 = 0.0f; + + matRotation.m8 = x*z*t + y*sinres; + matRotation.m9 = y*z*t - x*sinres; + matRotation.m10 = z*z*t + cosres; + matRotation.m11 = 0.0f; + + matRotation.m12 = 0.0f; + matRotation.m13 = 0.0f; + matRotation.m14 = 0.0f; + matRotation.m15 = 1.0f; + + // NOTE: Transposing matrix by multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matRotation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a scaling matrix +void rlScalef(float x, float y, float z) +{ + Matrix matScale = rlMatrixIdentity(); + + // Set scale component of matrix + matScale.m0 = x; + matScale.m5 = y; + matScale.m10 = z; + + // NOTE: Transposing matrix by multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matScale, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by another matrix +void rlMultMatrixf(const float *matf) +{ + // Matrix creation from array + // Conversion from column-major to row-major memory order + Matrix mat = { matf[0], matf[4], matf[8], matf[12], + matf[1], matf[5], matf[9], matf[13], + matf[2], matf[6], matf[10], matf[14], + matf[3], matf[7], matf[11], matf[15] }; + + *RLGL.State.currentMatrix = rlMatrixMultiply(mat, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a perspective matrix generated by parameters +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + Matrix matFrustum = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(zfar - znear); + + matFrustum.m0 = ((float) znear*2.0f)/rl; + matFrustum.m1 = 0.0f; + matFrustum.m2 = 0.0f; + matFrustum.m3 = 0.0f; + + matFrustum.m4 = 0.0f; + matFrustum.m5 = ((float) znear*2.0f)/tb; + matFrustum.m6 = 0.0f; + matFrustum.m7 = 0.0f; + + matFrustum.m8 = ((float)right + (float)left)/rl; + matFrustum.m9 = ((float)top + (float)bottom)/tb; + matFrustum.m10 = -((float)zfar + (float)znear)/fn; + matFrustum.m11 = -1.0f; + + matFrustum.m12 = 0.0f; + matFrustum.m13 = 0.0f; + matFrustum.m14 = -((float)zfar*(float)znear*2.0f)/fn; + matFrustum.m15 = 0.0f; + + *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matFrustum); +} + +// Multiply the current matrix by an orthographic matrix generated by parameters +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + // NOTE: If left-right and top-botton values are equal it could create a division by zero, + // response to it is platform/compiler dependent + Matrix matOrtho = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(zfar - znear); + + matOrtho.m0 = 2.0f/rl; + matOrtho.m1 = 0.0f; + matOrtho.m2 = 0.0f; + matOrtho.m3 = 0.0f; + matOrtho.m4 = 0.0f; + matOrtho.m5 = 2.0f/tb; + matOrtho.m6 = 0.0f; + matOrtho.m7 = 0.0f; + matOrtho.m8 = 0.0f; + matOrtho.m9 = 0.0f; + matOrtho.m10 = -2.0f/fn; + matOrtho.m11 = 0.0f; + matOrtho.m12 = -((float)left + (float)right)/rl; + matOrtho.m13 = -((float)top + (float)bottom)/tb; + matOrtho.m14 = -((float)zfar + (float)znear)/fn; + matOrtho.m15 = 1.0f; + + *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matOrtho); +} +#endif + +// Set the viewport area (transformation from normalized device coordinates to window coordinates) +void rlViewport(int x, int y, int width, int height) +{ + glViewport(x, y, width, height); +} + +// Set clip planes distances +void rlSetClipPlanes(double nearPlane, double farPlane) +{ + rlCullDistanceNear = nearPlane; + rlCullDistanceFar = farPlane; +} + +// Get cull plane distance near +double rlGetCullDistanceNear(void) +{ + return rlCullDistanceNear; +} + +// Get cull plane distance far +double rlGetCullDistanceFar(void) +{ + return rlCullDistanceFar; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vertex level operations +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlBegin(int mode) +{ + switch (mode) + { + case RL_LINES: glBegin(GL_LINES); break; + case RL_TRIANGLES: glBegin(GL_TRIANGLES); break; + case RL_QUADS: glBegin(GL_QUADS); break; + default: break; + } +} + +void rlEnd(void) { glEnd(); } +void rlVertex2i(int x, int y) { glVertex2i(x, y); } +void rlVertex2f(float x, float y) { glVertex2f(x, y); } +void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } +void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); } +void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); } +void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { glColor4ub(r, g, b, a); } +void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); } +void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Initialize drawing mode (how to organize vertex) +void rlBegin(int mode) +{ + // Draw mode can be RL_LINES, RL_TRIANGLES and RL_QUADS + // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode != mode) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) + { + RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; + RLGL.currentBatch->drawCounter++; + } + } + + if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = mode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = RLGL.State.currentTextureId; + RLGL.State.currentTextureId = RLGL.State.defaultTextureId; + } +} + +// Finish vertex providing +void rlEnd(void) +{ + // NOTE: Depth increment is dependent on rlOrtho(): z-near and z-far values, + // as well as depth buffer bit-depth (16bit or 24bit or 32bit) + // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) + RLGL.currentBatch->currentDepth += (1.0f/20000.0f); +} + +// Define one vertex (position) +// NOTE: Vertex position data is the basic information required for drawing +void rlVertex3f(float x, float y, float z) +{ + float tx = x; + float ty = y; + float tz = z; + + // Transform provided vector if required + if (RLGL.State.transformRequired) + { + tx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z + RLGL.State.transform.m12; + ty = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z + RLGL.State.transform.m13; + tz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z + RLGL.State.transform.m14; + } + + // WARNING: Be careful with primitives breaking when launching a new batch! + // RL_LINES comes in pairs, RL_TRIANGLES come in groups of 3 vertices and RL_QUADS come in groups of 4 vertices + // Checking current draw.mode when a new vertex is required and finish the batch only if the draw.mode draw.vertexCount is %2, %3 or %4 + if (RLGL.State.vertexCounter > (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4 - 4)) + { + if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%2 == 0)) + { + // Reached the maximum number of vertices for RL_LINES drawing + // Launch a draw call but keep current state for next vertices comming + // NOTE: Adding +1 vertex to the check for some safety + rlCheckRenderBatchLimit(2 + 1); + } + else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%3 == 0)) + { + rlCheckRenderBatchLimit(3 + 1); + } + else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_QUADS) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4 == 0)) + { + rlCheckRenderBatchLimit(4 + 1); + } + } + + // Add vertices + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter] = tx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 1] = ty; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 2] = tz; + + // Add current texcoord + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter] = RLGL.State.texcoordx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter + 1] = RLGL.State.texcoordy; + + // Add current normal + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter] = RLGL.State.normalx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 1] = RLGL.State.normaly; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 2] = RLGL.State.normalz; + + // Add current color + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter] = RLGL.State.colorr; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 1] = RLGL.State.colorg; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 2] = RLGL.State.colorb; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 3] = RLGL.State.colora; + + RLGL.State.vertexCounter++; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount++; +} + +// Define one vertex (position) +void rlVertex2f(float x, float y) +{ + rlVertex3f(x, y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (position) +void rlVertex2i(int x, int y) +{ + rlVertex3f((float)x, (float)y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (texture coordinate) +// NOTE: Texture coordinates are limited to QUADS only +void rlTexCoord2f(float x, float y) +{ + RLGL.State.texcoordx = x; + RLGL.State.texcoordy = y; +} + +// Define one vertex (normal) +// NOTE: Normals limited to TRIANGLES only? +void rlNormal3f(float x, float y, float z) +{ + float normalx = x; + float normaly = y; + float normalz = z; + if (RLGL.State.transformRequired) + { + normalx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z; + normaly = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z; + normalz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z; + } + + // NOTE: Default behavior assumes the normal vector is in the correct space for what the shader expects, + // it could be not normalized to 0.0f..1.0f, magnitud can be useed for some effects + /* + // WARNING: Vector normalization if required + float length = sqrtf(normalx*normalx + normaly*normaly + normalz*normalz); + if (length != 0.0f) + { + float ilength = 1.0f/length; + normalx *= ilength; + normaly *= ilength; + normalz *= ilength; + } + */ + RLGL.State.normalx = normalx; + RLGL.State.normaly = normaly; + RLGL.State.normalz = normalz; +} + +// Define one vertex (color) +void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) +{ + RLGL.State.colorr = x; + RLGL.State.colorg = y; + RLGL.State.colorb = z; + RLGL.State.colora = w; +} + +// Define one vertex (color) +void rlColor4f(float r, float g, float b, float a) +{ + rlColor4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); +} + +// Define one vertex (color) +void rlColor3f(float x, float y, float z) +{ + rlColor4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); +} + +#endif + +//-------------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL style functions (common to 1.1, 3.3+, ES2) +//-------------------------------------------------------------------------------------- + +// Set current texture to use +void rlSetTexture(unsigned int id) +{ + if (id == 0) + { +#if defined(GRAPHICS_API_OPENGL_11) + rlDisableTexture(); +#else + // NOTE: If quads batch limit is reached, force a draw call and next batch starts + if (RLGL.State.vertexCounter >= + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4) + { + rlDrawRenderBatch(RLGL.currentBatch); + } + RLGL.State.currentTextureId = RLGL.State.defaultTextureId; +#endif + } + else + { +#if defined(GRAPHICS_API_OPENGL_11) + rlEnableTexture(id); +#else + RLGL.State.currentTextureId = id; + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId != id) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) + { + RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; + + RLGL.currentBatch->drawCounter++; + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 2].mode; + + } + } + + if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = id; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; + } +#endif + } +} + +// Select and active a texture slot +void rlActiveTextureSlot(int slot) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glActiveTexture(GL_TEXTURE0 + slot); +#endif +} + +// Enable texture +void rlEnableTexture(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, id); +} + +// Disable texture +void rlDisableTexture(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Enable texture cubemap +void rlEnableTextureCubemap(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_CUBE_MAP, id); +#endif +} + +// Disable texture cubemap +void rlDisableTextureCubemap(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif +} + +// Set texture parameters (wrap mode/filter mode) +void rlTextureParameters(unsigned int id, int param, int value) +{ + glBindTexture(GL_TEXTURE_2D, id); + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_2D, param, value); + else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); +#endif + } + else glTexParameteri(GL_TEXTURE_2D, param, value); + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; + case RL_TEXTURE_FILTER_ANISOTROPIC: + { +#if !defined(GRAPHICS_API_OPENGL_11) + // Reset anisotropy filter, in case it was set + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) + { + TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); +#endif + } break; +#if defined(GRAPHICS_API_OPENGL_33) + case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, value/100.0f); +#endif + default: break; + } + + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Set cubemap parameters (wrap mode/filter mode) +void rlCubemapParameters(unsigned int id, int param, int value) +{ +#if !defined(GRAPHICS_API_OPENGL_11) + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + + // Reset anisotropy filter, in case it was set + glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) + { + if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); + else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); + } + else glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); break; + case RL_TEXTURE_FILTER_ANISOTROPIC: + { + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) + { + TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); + glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); + } break; +#if defined(GRAPHICS_API_OPENGL_33) + case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_LOD_BIAS, value/100.0f); +#endif + default: break; + } + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif +} + +// Enable shader program +void rlEnableShader(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(id); +#endif +} + +// Disable shader program +void rlDisableShader(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(0); +#endif +} + +// Enable rendering to texture (fbo) +void rlEnableFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_SOFTWARE)) + glBindFramebuffer(GL_FRAMEBUFFER, id); +#endif +} + +// return the active render texture (fbo) +unsigned int rlGetActiveFramebuffer(void) +{ + GLint fboId = 0; +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_SOFTWARE)) + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboId); +#endif + return fboId; +} + +// Disable rendering to texture +void rlDisableFramebuffer(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_SOFTWARE)) + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Blit active framebuffer to main framebuffer +void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) + glBlitFramebuffer(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask, GL_NEAREST); +#endif +} + +// Bind framebuffer object (fbo) +void rlBindFramebuffer(unsigned int target, unsigned int framebuffer) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_SOFTWARE)) + glBindFramebuffer(target, framebuffer); +#endif +} + +// Activate multiple draw color buffers +// NOTE: One color buffer is always active by default +void rlActiveDrawBuffers(int count) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) + // NOTE: Maximum number of draw buffers supported is implementation dependent, + // it can be queried with glGet*() but it must be at least 8 + //GLint maxDrawBuffers = 0; + //glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); + + if (count > 0) + { + if (count > 8) TRACELOG(RL_LOG_WARNING, "GL: Max color buffers limited to 8"); + else + { + unsigned int buffers[8] = { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3, + GL_COLOR_ATTACHMENT4, + GL_COLOR_ATTACHMENT5, + GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT7, + }; + + glDrawBuffers(count, buffers); + } + } + else TRACELOG(RL_LOG_WARNING, "GL: One color buffer active by default"); +#endif +} + +//---------------------------------------------------------------------------------- +// General render state configuration +//---------------------------------------------------------------------------------- + +// Enable color blending +void rlEnableColorBlend(void) { glEnable(GL_BLEND); } + +// Disable color blending +void rlDisableColorBlend(void) { glDisable(GL_BLEND); } + +// Enable depth test +void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } + +// Disable depth test +void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } + +// Enable depth write +void rlEnableDepthMask(void) { glDepthMask(GL_TRUE); } + +// Disable depth write +void rlDisableDepthMask(void) { glDepthMask(GL_FALSE); } + +// Enable backface culling +void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } + +// Disable backface culling +void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } + +// Set color mask active for screen read/draw +void rlColorMask(bool r, bool g, bool b, bool a) { glColorMask(r, g, b, a); } + +// Set face culling mode +void rlSetCullFace(int mode) +{ + switch (mode) + { + case RL_CULL_FACE_BACK: glCullFace(GL_BACK); break; + case RL_CULL_FACE_FRONT: glCullFace(GL_FRONT); break; + default: break; + } +} + +// Enable scissor test +void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } + +// Disable scissor test +void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } + +// Scissor test +void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } + +// Enable wire mode +void rlEnableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); +#endif +} + +// Disable wire mode +void rlDisableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +} + +// Enable point mode +void rlEnablePointMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); + glEnable(GL_PROGRAM_POINT_SIZE); +#endif +} + +// Disable point mode +void rlDisablePointMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +} + +// Set the line drawing width +void rlSetLineWidth(float width) { glLineWidth(width); } + +// Get the line drawing width +float rlGetLineWidth(void) +{ + float width = 0; + glGetFloatv(GL_LINE_WIDTH, &width); + return width; +} + +// Set the point drawing size +void rlSetPointSize(float size) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glPointSize(size); +#endif +} + +// Get the point drawing size +float rlGetPointSize(void) +{ + float size = 1; +#if defined(GRAPHICS_API_OPENGL_11) + glGetFloatv(GL_POINT_SIZE, &size); +#endif + return size; + +} + +// Enable line aliasing +void rlEnableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_LINE_SMOOTH); +#endif +} + +// Disable line aliasing +void rlDisableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_LINE_SMOOTH); +#endif +} + +// Enable stereo rendering +void rlEnableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = true; +#endif +} + +// Disable stereo rendering +void rlDisableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = false; +#endif +} + +// Check if stereo render is enabled +bool rlIsStereoRenderEnabled(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + return RLGL.State.stereoRender; +#else + return false; +#endif +} + +// Clear color buffer with color +void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + // Color values clamp to 0.0f(0) and 1.0f(255) + float cr = (float)r/255; + float cg = (float)g/255; + float cb = (float)b/255; + float ca = (float)a/255; + + glClearColor(cr, cg, cb, ca); +} + +// Clear used screen buffers (color and depth) +void rlClearScreenBuffers(void) +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: Color and Depth (Depth is used for 3D) + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... +} + +// Check and log OpenGL error codes +void rlCheckErrors(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int check = 1; + while (check) + { + const GLenum err = glGetError(); + switch (err) + { + case GL_NO_ERROR: check = 0; break; + case 0x0500: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); break; + case 0x0501: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); break; + case 0x0502: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); break; + case 0x0503: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); break; + case 0x0504: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); break; + case 0x0505: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); break; + case 0x0506: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); break; + default: TRACELOG(RL_LOG_WARNING, "GL: Error detected: Unknown error code: %x", err); break; + } + } +#endif +} + +// Set blend mode +void rlSetBlendMode(int mode) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.currentBlendMode != mode) || ((mode == RL_BLEND_CUSTOM || mode == RL_BLEND_CUSTOM_SEPARATE) && RLGL.State.glCustomBlendModeModified)) + { + rlDrawRenderBatch(RLGL.currentBatch); + + switch (mode) + { + case RL_BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; + case RL_BLEND_ALPHA_PREMULTIPLY: glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_CUSTOM: + { + // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactors() + glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); + } break; + case RL_BLEND_CUSTOM_SEPARATE: + { + // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactorsSeparate() + glBlendFuncSeparate(RLGL.State.glBlendSrcFactorRGB, RLGL.State.glBlendDestFactorRGB, RLGL.State.glBlendSrcFactorAlpha, RLGL.State.glBlendDestFactorAlpha); + glBlendEquationSeparate(RLGL.State.glBlendEquationRGB, RLGL.State.glBlendEquationAlpha); + } break; + default: break; + } + + RLGL.State.currentBlendMode = mode; + RLGL.State.glCustomBlendModeModified = false; + } +#endif +} + +// Set blending mode factor and equation +void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.glBlendSrcFactor != glSrcFactor) || + (RLGL.State.glBlendDstFactor != glDstFactor) || + (RLGL.State.glBlendEquation != glEquation)) + { + RLGL.State.glBlendSrcFactor = glSrcFactor; + RLGL.State.glBlendDstFactor = glDstFactor; + RLGL.State.glBlendEquation = glEquation; + + RLGL.State.glCustomBlendModeModified = true; + } +#endif +} + +// Set blending mode factor and equation separately for RGB and alpha +void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.glBlendSrcFactorRGB != glSrcRGB) || + (RLGL.State.glBlendDestFactorRGB != glDstRGB) || + (RLGL.State.glBlendSrcFactorAlpha != glSrcAlpha) || + (RLGL.State.glBlendDestFactorAlpha != glDstAlpha) || + (RLGL.State.glBlendEquationRGB != glEqRGB) || + (RLGL.State.glBlendEquationAlpha != glEqAlpha)) + { + RLGL.State.glBlendSrcFactorRGB = glSrcRGB; + RLGL.State.glBlendDestFactorRGB = glDstRGB; + RLGL.State.glBlendSrcFactorAlpha = glSrcAlpha; + RLGL.State.glBlendDestFactorAlpha = glDstAlpha; + RLGL.State.glBlendEquationRGB = glEqRGB; + RLGL.State.glBlendEquationAlpha = glEqAlpha; + + RLGL.State.glCustomBlendModeModified = true; + } +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL Debug +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_43) && RLGL_ENABLE_OPENGL_DEBUG_CONTEXT +static void GLAPIENTRY rlDebugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) +{ + // Ignore non-significant error/warning codes (NVidia drivers) + // NOTE: Here there are the details with a sample output: + // - #131169 - Framebuffer detailed info: The driver allocated storage for renderbuffer 2. (severity: low) + // - #131185 - Buffer detailed info: Buffer object 1 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_ENUM_88e4) + // will use VIDEO memory as the source for buffer object operations. (severity: low) + // - #131218 - Program/shader state performance warning: Vertex shader in program 7 is being recompiled based on GL state. (severity: medium) + // - #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0 does not have + // a defined base level and cannot be used for texture mapping. (severity: low) + if ((id == 131169) || (id == 131185) || (id == 131218) || (id == 131204)) return; + + const char *msgSource = NULL; + switch (source) + { + case GL_DEBUG_SOURCE_API: msgSource = "API"; break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: msgSource = "WINDOW_SYSTEM"; break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: msgSource = "SHADER_COMPILER"; break; + case GL_DEBUG_SOURCE_THIRD_PARTY: msgSource = "THIRD_PARTY"; break; + case GL_DEBUG_SOURCE_APPLICATION: msgSource = "APPLICATION"; break; + case GL_DEBUG_SOURCE_OTHER: msgSource = "OTHER"; break; + default: break; + } + + const char *msgType = NULL; + switch (type) + { + case GL_DEBUG_TYPE_ERROR: msgType = "ERROR"; break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: msgType = "DEPRECATED_BEHAVIOR"; break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: msgType = "UNDEFINED_BEHAVIOR"; break; + case GL_DEBUG_TYPE_PORTABILITY: msgType = "PORTABILITY"; break; + case GL_DEBUG_TYPE_PERFORMANCE: msgType = "PERFORMANCE"; break; + case GL_DEBUG_TYPE_MARKER: msgType = "MARKER"; break; + case GL_DEBUG_TYPE_PUSH_GROUP: msgType = "PUSH_GROUP"; break; + case GL_DEBUG_TYPE_POP_GROUP: msgType = "POP_GROUP"; break; + case GL_DEBUG_TYPE_OTHER: msgType = "OTHER"; break; + default: break; + } + + const char *msgSeverity = "DEFAULT"; + switch (severity) + { + case GL_DEBUG_SEVERITY_LOW: msgSeverity = "LOW"; break; + case GL_DEBUG_SEVERITY_MEDIUM: msgSeverity = "MEDIUM"; break; + case GL_DEBUG_SEVERITY_HIGH: msgSeverity = "HIGH"; break; + case GL_DEBUG_SEVERITY_NOTIFICATION: msgSeverity = "NOTIFICATION"; break; + default: break; + } + + TRACELOG(RL_LOG_WARNING, "GL: OpenGL debug message: %s", message); + TRACELOG(RL_LOG_WARNING, " > Type: %s", msgType); + TRACELOG(RL_LOG_WARNING, " > Source = %s", msgSource); + TRACELOG(RL_LOG_WARNING, " > Severity = %s", msgSeverity); +} +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition - rlgl functionality +//---------------------------------------------------------------------------------- + +// Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states +void rlglInit(int width, int height) +{ + isGpuReady = true; + + // Enable OpenGL debug context if requested (and supported) +#if defined(GRAPHICS_API_OPENGL_43) && RLGL_ENABLE_OPENGL_DEBUG_CONTEXT + if ((glDebugMessageCallback != NULL) && (glDebugMessageControl != NULL)) + { + glDebugMessageCallback(rlDebugMessageCallback, 0); + // glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_HIGH, 0, 0, GL_TRUE); + + // Debug context options: + // - GL_DEBUG_OUTPUT - Faster version but not useful for breakpoints + // - GL_DEBUG_OUTPUT_SYNCHRONUS - Callback is in sync with errors, so a breakpoint can be placed on the callback in order to get a stacktrace for the GL error + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + } +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Init default white texture + unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) + RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + RLGL.State.currentTextureId = RLGL.State.defaultTextureId; + + if (RLGL.State.defaultTextureId != 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully", RLGL.State.defaultTextureId); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load default texture"); + + // Init default Shader (customized for GL 3.3 and ES2) + // Loaded: RLGL.State.defaultShaderId + RLGL.State.defaultShaderLocs + rlLoadShaderDefault(); + RLGL.State.currentShaderId = RLGL.State.defaultShaderId; + RLGL.State.currentShaderLocs = RLGL.State.defaultShaderLocs; + + // Init default vertex arrays buffers + // Simulate that the default shader has the location RL_SHADER_LOC_VERTEX_NORMAL to bind the normal buffer for the default render batch + RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL; + RLGL.defaultBatch = rlLoadRenderBatch(RL_DEFAULT_BATCH_BUFFERS, RL_DEFAULT_BATCH_BUFFER_ELEMENTS); + RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = -1; + RLGL.currentBatch = &RLGL.defaultBatch; + + // Init stack matrices (emulating OpenGL 1.1) + for (int i = 0; i < RL_MAX_MATRIX_STACK_SIZE; i++) RLGL.State.stack[i] = rlMatrixIdentity(); + + // Init internal matrices + RLGL.State.transform = rlMatrixIdentity(); + RLGL.State.projection = rlMatrixIdentity(); + RLGL.State.modelview = rlMatrixIdentity(); + RLGL.State.currentMatrix = &RLGL.State.modelview; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + // Initialize software renderer backend + int result = swInit(width, height); + if (result == 0) + { + TRACELOG(RL_LOG_ERROR, "RLSW: Software renderer initialization failed!"); + exit(-1); + } +#endif + + // Initialize OpenGL default states + //---------------------------------------------------------- + // Init state: Depth test + glDepthFunc(GL_LEQUAL); // Type of depth testing to apply + glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) + + // Init state: Blending mode + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Color blending function (how colors are mixed) + glEnable(GL_BLEND); // Enable color blending (required to work with transparencies) + + // Init state: Culling + // NOTE: All shapes/models triangles are drawn CCW + glCullFace(GL_BACK); // Cull the back face (default) + glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) + glEnable(GL_CULL_FACE); // Enable backface culling + +#if defined(GRAPHICS_API_OPENGL_11) + // Init state: Color hints (deprecated in OpenGL 3.0+) + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation + glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) +#endif +#if defined(GRAPHICS_API_OPENGL_33) + // Init state: Cubemap seamless + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Store screen size into global variables + RLGL.State.framebufferWidth = width; + RLGL.State.framebufferHeight = height; +#endif + + // Init state: Color/Depth buffers clear + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) + glClearDepth(1.0f); // Set clear depth value (default) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) + + TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); + //---------------------------------------------------------- +} + +// Vertex Buffer Object deinitialization (memory free) +void rlglClose(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlUnloadRenderBatch(RLGL.defaultBatch); + + rlUnloadShaderDefault(); // Unload default shader + + glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId); +#endif + +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + swClose(); // Unload sofware renderer resources +#endif + isGpuReady = false; +} + +// Load OpenGL extensions +// NOTE: External loader function must be provided +void rlLoadExtensions(void *loader) +{ +#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 + // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) + if (gladLoadGL((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); + else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); + + // Get number of supported extensions + GLint numExt = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if RLGL_SHOW_GL_DETAILS_INFO + // Get supported extensions list + // WARNING: glGetStringi() not available on OpenGL 2.1 + TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", glGetStringi(GL_EXTENSIONS, i)); +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + // Register supported extensions flags + // Optional OpenGL 2.1 extensions + RLGL.ExtSupported.vao = GLAD_GL_ARB_vertex_array_object; + RLGL.ExtSupported.instancing = (GLAD_GL_EXT_draw_instanced && GLAD_GL_ARB_instanced_arrays); + RLGL.ExtSupported.texNPOT = GLAD_GL_ARB_texture_non_power_of_two; + RLGL.ExtSupported.texFloat32 = GLAD_GL_ARB_texture_float; + RLGL.ExtSupported.texFloat16 = GLAD_GL_ARB_texture_float; + RLGL.ExtSupported.texDepth = GLAD_GL_ARB_depth_texture; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = GLAD_GL_EXT_texture_filter_anisotropic; + RLGL.ExtSupported.texMirrorClamp = GLAD_GL_EXT_texture_mirror_clamp; +#else + // Register supported extensions flags + // OpenGL 3.3 extensions supported by default (core) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texFloat16 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; +#endif + + // Optional OpenGL 3.3 extensions + RLGL.ExtSupported.texCompASTC = GLAD_GL_KHR_texture_compression_astc_hdr && GLAD_GL_KHR_texture_compression_astc_ldr; + RLGL.ExtSupported.texCompDXT = GLAD_GL_EXT_texture_compression_s3tc; // Texture compression: DXT + RLGL.ExtSupported.texCompETC2 = GLAD_GL_ARB_ES3_compatibility; // Texture compression: ETC2/EAC + #if defined(GRAPHICS_API_OPENGL_43) + RLGL.ExtSupported.computeShader = GLAD_GL_ARB_compute_shader; + RLGL.ExtSupported.ssbo = GLAD_GL_ARB_shader_storage_buffer_object; + #endif + +#endif // GRAPHICS_API_OPENGL_33 + +#if defined(GRAPHICS_API_OPENGL_ES3) + // Register supported extensions flags + // OpenGL ES 3.0 extensions supported by default (or it should be) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texFloat16 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.texDepthWebGL = true; + RLGL.ExtSupported.maxDepthBits = 24; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; + // TODO: Check for additional OpenGL ES 3.0 supported extensions: + //RLGL.ExtSupported.texCompDXT = true; + //RLGL.ExtSupported.texCompETC1 = true; + //RLGL.ExtSupported.texCompETC2 = true; + //RLGL.ExtSupported.texCompPVRT = true; + //RLGL.ExtSupported.texCompASTC = true; + //RLGL.ExtSupported.maxAnisotropyLevel = true; + //RLGL.ExtSupported.computeShader = true; + //RLGL.ExtSupported.ssbo = true; + +#elif defined(GRAPHICS_API_OPENGL_ES2) + + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) + // TODO: Support GLAD loader for OpenGL ES 3.0 + if (gladLoadGLES2((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL ES2.0 functions"); + else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL ES 2.0 loaded successfully"); + #endif + + // Get supported extensions list + GLint numExt = 0; + const char **extList = (const char **)RL_CALLOC(512, sizeof(const char *)); // Allocate 512 strings pointers (2 KB) + const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string + + // NOTE: String duplication rquired because glGetString() returns a const string + int extensionsLength = (int)strlen(extensions); // Get extensions string size in bytes + char *extensionsDup = (char *)RL_CALLOC(extensionsLength + 1, sizeof(char)); // Allocate space for copy with additional EOL byte + snprintf(extensionsDup, extensionsLength, "%s", extensions); + extList[numExt] = extensionsDup; + + for (int i = 0; i < extensionsLength; i++) + { + if (extensionsDup[i] == ' ') + { + extensionsDup[i] = '\0'; + numExt++; + extList[numExt] = &extensionsDup[i + 1]; + } + } + + TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if RLGL_SHOW_GL_DETAILS_INFO + TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", extList[i]); +#endif + + // Check required extensions + for (int i = 0; i < numExt; i++) + { + // Check VAO support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) + { + // The extension is supported by our hardware and driver, try to get related functions pointers + // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... + glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glGenVertexArraysOES"); + glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)((rlglLoadProc)loader)("glBindVertexArrayOES"); + glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glDeleteVertexArraysOES"); + //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)loader("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted + + if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; + } + + // Check instanced rendering support + if (strstr(extList[i], (const char *)"instanced_arrays") != NULL) // Broad check for instanced_arrays + { + // Specific check + if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // ANGLE + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); + } + else if (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0) // EXT + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); + } + else if (strcmp(extList[i], (const char *)"GL_NV_instanced_arrays") == 0) // NVIDIA GLES + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorNV"); + } + + // The feature will only be marked as supported if the elements from GL_XXX_instanced_arrays are present + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + else if (strstr(extList[i], (const char *)"draw_instanced") != NULL) + { + // GL_ANGLE_draw_instanced doesn't exist + if (strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + } + else if (strcmp(extList[i], (const char *)"GL_NV_draw_instanced") == 0) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); + } + + // But the functions will at least be loaded if only GL_XX_EXT_draw_instanced exist + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + + // Check NPOT textures support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; + + // Check texture float support + if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; + if (strcmp(extList[i], (const char *)"GL_OES_texture_half_float") == 0) RLGL.ExtSupported.texFloat16 = true; + + // Check depth texture support + if (strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) RLGL.ExtSupported.texDepth = true; + if (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0) RLGL.ExtSupported.texDepthWebGL = true; // WebGL requires unsized internal format + if (RLGL.ExtSupported.texDepthWebGL) RLGL.ExtSupported.texDepth = true; + + if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; // Not available on WebGL + if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; // Not available on WebGL + + // Check texture compression support: DXT + if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; + + // Check texture compression support: ETC1 + if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; + + // Check texture compression support: ETC2/EAC + if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; + + // Check texture compression support: PVR + if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; + + // Check texture compression support: ASTC + if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; + + // Check anisotropic texture filter support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) RLGL.ExtSupported.texAnisoFilter = true; + + // Check clamp mirror wrap mode support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; + } + + // Free extensions pointers + RL_FREE(extList); + RL_FREE(extensionsDup); // Duplicated string must be deallocated +#endif // GRAPHICS_API_OPENGL_ES2 + + // Check OpenGL information and capabilities + //------------------------------------------------------------------------------ + // Show current OpenGL and GLSL version + TRACELOG(RL_LOG_INFO, "GL: OpenGL device information:"); + TRACELOG(RL_LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); + TRACELOG(RL_LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); + TRACELOG(RL_LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); + TRACELOG(RL_LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.loader = (rlglLoadProc)loader; + + // NOTE: Anisotropy levels capability is an extension + #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + #endif + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &RLGL.ExtSupported.maxAnisotropyLevel); + +#if RLGL_SHOW_GL_DETAILS_INFO + // Show some OpenGL GPU capabilities + TRACELOG(RL_LOG_INFO, "GL: OpenGL capabilities:"); + GLint capability = 0; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_CUBE_MAP_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_IMAGE_UNITS: %i", capability); + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIBS: %i", capability); + #if !defined(GRAPHICS_API_OPENGL_ES2) + glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_BLOCK_SIZE: %i", capability); + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_DRAW_BUFFERS: %i", capability); + if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_MAX_ANISOTROPY: %.0f", RLGL.ExtSupported.maxAnisotropyLevel); + #endif + glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &capability); + TRACELOG(RL_LOG_INFO, " GL_NUM_COMPRESSED_TEXTURE_FORMATS: %i", capability); + GLint *compFormats = (GLint *)RL_CALLOC(capability, sizeof(GLint)); + glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, compFormats); + for (int i = 0; i < capability; i++) TRACELOG(RL_LOG_INFO, " %s", rlGetCompressedFormatName(compFormats[i])); + RL_FREE(compFormats); + +#if defined(GRAPHICS_API_OPENGL_43) + glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIB_BINDINGS: %i", capability); + glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_LOCATIONS: %i", capability); +#endif + +#else // !RLGL_SHOW_GL_DETAILS_INFO + + // Show some basic info about GL supported features + if (RLGL.ExtSupported.vao) TRACELOG(RL_LOG_INFO, "GL: VAO extension detected, VAO functions loaded successfully"); + else TRACELOG(RL_LOG_WARNING, "GL: VAO extension not found, VAO not supported"); + if (RLGL.ExtSupported.texNPOT) TRACELOG(RL_LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); + else TRACELOG(RL_LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); + if (RLGL.ExtSupported.texCompDXT) TRACELOG(RL_LOG_INFO, "GL: DXT compressed textures supported"); + if (RLGL.ExtSupported.texCompETC1) TRACELOG(RL_LOG_INFO, "GL: ETC1 compressed textures supported"); + if (RLGL.ExtSupported.texCompETC2) TRACELOG(RL_LOG_INFO, "GL: ETC2/EAC compressed textures supported"); + if (RLGL.ExtSupported.texCompPVRT) TRACELOG(RL_LOG_INFO, "GL: PVRT compressed textures supported"); + if (RLGL.ExtSupported.texCompASTC) TRACELOG(RL_LOG_INFO, "GL: ASTC compressed textures supported"); + if (RLGL.ExtSupported.computeShader) TRACELOG(RL_LOG_INFO, "GL: Compute shaders supported"); + if (RLGL.ExtSupported.ssbo) TRACELOG(RL_LOG_INFO, "GL: Shader storage buffer objects supported"); +#endif + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +} + +// Get OpenGL procedure address +void *rlGetProcAddress(const char *procName) +{ + void *func = NULL; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + func = RLGL.loader(procName); +#endif + return func; +} + +// Get current OpenGL version +int rlGetVersion(void) +{ + int glVersion = 0; + +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + glVersion = RL_OPENGL_SOFTWARE; +#elif defined(GRAPHICS_API_OPENGL_11) + glVersion = RL_OPENGL_11; +#endif +#if defined(GRAPHICS_API_OPENGL_21) + glVersion = RL_OPENGL_21; +#elif defined(GRAPHICS_API_OPENGL_43) + glVersion = RL_OPENGL_43; +#elif defined(GRAPHICS_API_OPENGL_33) + glVersion = RL_OPENGL_33; +#endif +#if defined(GRAPHICS_API_OPENGL_ES3) + glVersion = RL_OPENGL_ES_30; +#elif defined(GRAPHICS_API_OPENGL_ES2) + glVersion = RL_OPENGL_ES_20; +#endif + + return glVersion; +} + +// Set current framebuffer width +void rlSetFramebufferWidth(int width) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.framebufferWidth = width; +#endif +} + +// Set current framebuffer height +void rlSetFramebufferHeight(int height) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.framebufferHeight = height; +#endif +} + +// Get default framebuffer width +int rlGetFramebufferWidth(void) +{ + int width = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + width = RLGL.State.framebufferWidth; +#endif + return width; +} + +// Get default framebuffer height +int rlGetFramebufferHeight(void) +{ + int height = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + height = RLGL.State.framebufferHeight; +#endif + return height; +} + +// Get default internal texture (white texture) +// NOTE: Default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 +unsigned int rlGetTextureIdDefault(void) +{ + unsigned int id = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + id = RLGL.State.defaultTextureId; +#endif + return id; +} + +// Get default shader id +unsigned int rlGetShaderIdDefault(void) +{ + unsigned int id = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + id = RLGL.State.defaultShaderId; +#endif + return id; +} + +// Get default shader locs +int *rlGetShaderLocsDefault(void) +{ + int *locs = NULL; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + locs = RLGL.State.defaultShaderLocs; +#endif + return locs; +} + +// Render batch management +//------------------------------------------------------------------------------------------------ +// Load render batch +rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) +{ + rlRenderBatch batch = { 0 }; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return batch; } + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) + //-------------------------------------------------------------------------------------------- + batch.vertexBuffer = (rlVertexBuffer *)RL_CALLOC(numBuffers, sizeof(rlVertexBuffer)); + + for (int i = 0; i < numBuffers; i++) + { + batch.vertexBuffer[i].elementCount = bufferElements; + + batch.vertexBuffer[i].vertices = (float *)RL_CALLOC(bufferElements*3*4, sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].texcoords = (float *)RL_CALLOC(bufferElements*2*4, sizeof(float)); // 2 float by texcoord, 4 texcoord by quad + batch.vertexBuffer[i].normals = (float *)RL_CALLOC(bufferElements*3*4, sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].colors = (unsigned char *)RL_CALLOC(bufferElements*4*4, sizeof(unsigned char)); // 4 float by color, 4 colors by quad +#if defined(GRAPHICS_API_OPENGL_33) + batch.vertexBuffer[i].indices = (unsigned int *)RL_CALLOC(bufferElements*6, sizeof(unsigned int)); // 6 int by quad (indices) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + batch.vertexBuffer[i].indices = (unsigned short *)RL_CALLOC(bufferElements*6, sizeof(unsigned short)); // 6 int by quad (indices) +#endif + + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; + for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].normals[j] = 0.0f; + for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; + + int k = 0; + + // Indices can be initialized right now + for (int j = 0; j < (6*bufferElements); j += 6) + { + batch.vertexBuffer[i].indices[j] = 4*k; + batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; + batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 3] = 4*k; + batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; + + k++; + } + + RLGL.State.vertexCounter = 0; + } + + TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)"); + //-------------------------------------------------------------------------------------------- + + // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs + //-------------------------------------------------------------------------------------------- + for (int i = 0; i < numBuffers; i++) + { + if (RLGL.ExtSupported.vao) + { + // Initialize Quads VAO + glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); + glBindVertexArray(batch.vertexBuffer[i].vaoId); + } + + // Quads - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + + // Vertex texcoord buffer (shader-location = 1) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + + // Vertex normal buffer (shader-location = 2) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].normals, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); + + // Vertex color buffer (shader-location = 3) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + // Fill index buffer + glGenBuffers(1, &batch.vertexBuffer[i].vboId[4]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[4]); +#if defined(GRAPHICS_API_OPENGL_33) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif + } + + TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)"); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + //-------------------------------------------------------------------------------------------- + + // Init draw calls tracking system + //-------------------------------------------------------------------------------------------- + batch.draws = (rlDrawCall *)RL_CALLOC(RL_DEFAULT_BATCH_DRAWCALLS, sizeof(rlDrawCall)); + + for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) + { + batch.draws[i].mode = RL_QUADS; + batch.draws[i].vertexCount = 0; + batch.draws[i].vertexAlignment = 0; + //batch.draws[i].vaoId = 0; + //batch.draws[i].shaderId = 0; + batch.draws[i].textureId = RLGL.State.defaultTextureId; + //batch.draws[i].RLGL.State.projection = rlMatrixIdentity(); + //batch.draws[i].RLGL.State.modelview = rlMatrixIdentity(); + } + + batch.bufferCount = numBuffers; // Record buffer count + batch.drawCounter = 1; // Reset draws counter + batch.currentDepth = -1.0f; // Reset depth value + //-------------------------------------------------------------------------------------------- +#endif + + return batch; +} + +// Unload default internal buffers vertex data from CPU and GPU +void rlUnloadRenderBatch(rlRenderBatch batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Unbind everything + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // Unload all vertex buffers data + for (int i = 0; i < batch.bufferCount; i++) + { + // Unbind VAO attribs data + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(batch.vertexBuffer[i].vaoId); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR); + glBindVertexArray(0); + } + + // Delete VBOs from GPU (VRAM) + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[4]); + + // Delete VAOs from GPU (VRAM) + if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); + + // Free vertex arrays memory from CPU (RAM) + RL_FREE(batch.vertexBuffer[i].vertices); + RL_FREE(batch.vertexBuffer[i].texcoords); + RL_FREE(batch.vertexBuffer[i].normals); + RL_FREE(batch.vertexBuffer[i].colors); + RL_FREE(batch.vertexBuffer[i].indices); + } + + // Unload arrays + RL_FREE(batch.vertexBuffer); + RL_FREE(batch.draws); +#endif +} + +// Draw render batch +// NOTE: Batch is reseted and current buffer is updated (for multi-buffer config) +void rlDrawRenderBatch(rlRenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Update batch vertex buffers + //------------------------------------------------------------------------------------------------------------ + // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) + if (RLGL.State.vertexCounter > 0) + { + // Activate elements VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + + // TODO: If no data changed on the CPU arrays there is no need to re-upload data to GPU, + // a flag can be used to detect changes but it would imply keeping a copy buffer and memcmp() both, does it worth it? + + // Vertex positions buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer + + // Texture coordinates buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer + + // Normals buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].normals); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].normals, GL_DYNAMIC_DRAW); // Update all buffer + + // Colors buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer + + // NOTE: glMapBuffer() causes sync issue + // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job + // To avoid waiting (idle), glBufferData() can be called first with NULL pointer before glMapBuffer() + // Doing that, the previous data in PBO will be discarded and glMapBuffer() returns a new + // allocated pointer immediately even if GPU is still working with the previous data + + // Another option: map the buffer object into client's memory + //batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + //if (batch->vertexBuffer[batch->currentBuffer].vertices) + //{ + // Update vertex data + //} + //glUnmapBuffer(GL_ARRAY_BUFFER); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + } + //------------------------------------------------------------------------------------------------------------ + + // Draw batch vertex buffers (considering VR stereo if required) + //------------------------------------------------------------------------------------------------------------ + Matrix matProjection = RLGL.State.projection; + Matrix matModelView = RLGL.State.modelview; + + int eyeCount = 1; + if (RLGL.State.stereoRender) eyeCount = 2; + + for (int eye = 0; eye < eyeCount; eye++) + { + if (eyeCount == 2) + { + // Setup current eye viewport (half screen width) + rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); + + // Set current eye view offset to modelview matrix + rlSetMatrixModelview(rlMatrixMultiply(matModelView, RLGL.State.viewOffsetStereo[eye])); + // Set current eye projection matrix + rlSetMatrixProjection(RLGL.State.projectionStereo[eye]); + } + + // Draw buffers + if (RLGL.State.vertexCounter > 0) + { + // Set current shader and upload current MVP matrix + glUseProgram(RLGL.State.currentShaderId); + + // Create modelview-projection matrix and upload to shader + Matrix matMVP = rlMatrixMultiply(RLGL.State.modelview, RLGL.State.projection); + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MVP], 1, false, rlMatrixToFloat(matMVP)); + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION], 1, false, rlMatrixToFloat(RLGL.State.projection)); + } + + // WARNING: For the following setup of the view, model, and normal matrices, it is expected that + // transformations and rendering occur between rlPushMatrix() and rlPopMatrix() + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW], 1, false, rlMatrixToFloat(RLGL.State.modelview)); + } + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL], 1, false, rlMatrixToFloat(RLGL.State.transform)); + } + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL], 1, false, rlMatrixToFloat(rlMatrixTranspose(rlMatrixInvert(RLGL.State.transform)))); + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + else + { + // Bind vertex attrib: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); + + // Bind vertex attrib: texcoord (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); + + // Bind vertex attrib: normal (shader-location = 2) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[4]); + } + + // Setup some default shader values + glUniform4f(RLGL.State.currentShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1i(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE], 0); // Active default sampler2D: texture0 + + // Activate additional sampler textures + // Those additional textures will be common for all draw calls of the batch + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] > 0) + { + glActiveTexture(GL_TEXTURE0 + 1 + i); + glBindTexture(GL_TEXTURE_2D, RLGL.State.activeTextureId[i]); + } + } + + // Activate default sampler2D texture0 (one texture is always active for default batch shader) + // NOTE: Batch system accumulates calls by texture0 changes, additional textures are enabled for all the draw calls + glActiveTexture(GL_TEXTURE0); + + for (int i = 0, vertexOffset = 0; i < batch->drawCounter; i++) + { + // Bind current draw call texture, activated as GL_TEXTURE0 and bound to sampler2D texture0 by default + glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); + + if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); + else + { + #if defined(GRAPHICS_API_OPENGL_33) + // The number of indices to be processed needs to be defined: elementCount*6 + // NOTE: The final parameter tells the GPU the offset in bytes from the + // start of the index buffer to the location of the first index to process + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); + #endif + } + + vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); + } + + if (!RLGL.ExtSupported.vao) + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO + + glUseProgram(0); // Unbind shader program + } + + // Restore viewport to default measures + if (eyeCount == 2) rlViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); + //------------------------------------------------------------------------------------------------------------ + + // Reset batch buffers + //------------------------------------------------------------------------------------------------------------ + // Reset vertex counter for next frame + RLGL.State.vertexCounter = 0; + + // Reset depth for next draw + batch->currentDepth = -1.0f; + + // Restore projection/modelview matrices + RLGL.State.projection = matProjection; + RLGL.State.modelview = matModelView; + + // Reset RLGL.currentBatch->draws array + for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) + { + batch->draws[i].mode = RL_QUADS; + batch->draws[i].vertexCount = 0; + batch->draws[i].textureId = RLGL.State.defaultTextureId; + } + + // Reset active texture units for next batch + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) RLGL.State.activeTextureId[i] = 0; + + // Reset draws counter to one draw for the batch + batch->drawCounter = 1; + //------------------------------------------------------------------------------------------------------------ + + // Change to next buffer in the list (in case of multi-buffering) + batch->currentBuffer++; + if (batch->currentBuffer >= batch->bufferCount) batch->currentBuffer = 0; +#endif +} + +// Set the active render batch for rlgl +void rlSetRenderBatchActive(rlRenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); + + if (batch != NULL) RLGL.currentBatch = batch; + else RLGL.currentBatch = &RLGL.defaultBatch; +#endif +} + +// Update and draw internal render batch +void rlDrawRenderBatchActive(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside +#endif +} + +// Check internal buffer overflow for a given number of vertex +// and force a rlRenderBatch draw call if required +bool rlCheckRenderBatchLimit(int vCount) +{ + bool overflow = false; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.vertexCounter + vCount) >= + (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4)) + { + overflow = true; + + // Store current primitive drawing mode and texture id + int currentMode = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode; + int currentTexture = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId; + + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside + + // Restore state of last batch so new vertices can be added + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = currentMode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = currentTexture; + } +#endif + + return overflow; +} + +// Textures data management +//----------------------------------------------------------------------------------------- +// Convert image data to OpenGL texture (returns OpenGL valid Id) +unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount) +{ + unsigned int id = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } + + glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding + + // Check texture format support by OpenGL 1.1 (compressed textures not supported) +#if defined(GRAPHICS_API_OPENGL_11) + if (format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + // TODO: Support texture data decompression + TRACELOG(RL_LOG_WARNING, "GL: OpenGL 1.1 does not support GPU compressed texture formats"); + return id; + } +#else + if ((!RLGL.ExtSupported.texCompDXT) && ((format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA) || + (format == RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: DXT compressed texture format not supported"); + return id; + } +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((!RLGL.ExtSupported.texCompETC1) && (format == RL_PIXELFORMAT_COMPRESSED_ETC1_RGB)) + { + TRACELOG(RL_LOG_WARNING, "GL: ETC1 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompETC2) && ((format == RL_PIXELFORMAT_COMPRESSED_ETC2_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: ETC2 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompPVRT) && ((format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: PVRT compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompASTC) && ((format == RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: ASTC compressed texture format not supported"); + return id; + } +#endif +#endif // GRAPHICS_API_OPENGL_11 + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glGenTextures(1, &id); // Generate texture id + + glBindTexture(GL_TEXTURE_2D, id); + + int mipWidth = width; + int mipHeight = height; + int mipOffset = 0; // Mipmap data offset, only used for tracelog + (void)mipOffset; // Used to avoid gcc warnings about unused variable + + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned char *dataPtr = NULL; + if (data != NULL) dataPtr = (unsigned char *)data; + + // Load the different mipmap levels + for (int i = 0; i < mipmapCount; i++) + { + unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + TRACELOG(RL_LOG_DEBUG, "TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); + + if (glInternalFormat != 0) + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, dataPtr); +#if !defined(GRAPHICS_API_OPENGL_11) + else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, dataPtr); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + } + + mipWidth /= 2; + mipHeight /= 2; + mipOffset += mipSize; // Increment offset position to next mipmap + if (data != NULL) dataPtr += mipSize; // Increment data pointer to next mipmap + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + } + + // Texture parameters configuration + // NOTE: glTexParameteri does NOT affect texture uploading +#if defined(GRAPHICS_API_OPENGL_ES2) + + // Check if texture is power-of-two (POT) + bool texIsPOT = false; + + if (((width > 0) && ((width & (width - 1)) == 0)) && + ((height > 0) && ((height & (height - 1)) == 0))) texIsPOT = true; + + // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used + if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis + } + else + { + // NOTE: If using negative texture coordinates (LoadOBJ()), it does not work! + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis + } +#else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis +#endif + + // Magnification and minification filters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + +#if defined(GRAPHICS_API_OPENGL_33) + if (mipmapCount > 1) + { + // Activate trilinear filtering if mipmaps are available + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + + // Define the maximum number of mipmap levels to be used, 0 is base texture size + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipmapCount - 1); + + // Check if the loaded texture with mipmaps is complete, + // uncomplete textures will draw in black if mipmap filtering is required + //GLint complete = 0; + //glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_IMMUTABLE_FORMAT, &complete); + } +#endif + + // At this point texture is loaded in GPU and texture parameters configured + + // NOTE: If mipmaps were not in data, they are not generated automatically + + // Unbind current texture + glBindTexture(GL_TEXTURE_2D, 0); + + if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Texture loaded successfully (%ix%i | %s | %i mipmaps)", id, width, height, rlGetPixelFormatName(format), mipmapCount); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load texture"); + + return id; +} + +// Load depth texture/renderbuffer (to be attached to fbo) +// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture and WebGL requires WEBGL_depth_texture extensions +unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) +{ + unsigned int id = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // In case depth textures were not supported, force renderbuffer usage + if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true; + + // NOTE: Letting the implementation to choose the best bit-depth + // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F + unsigned int glInternalFormat = GL_DEPTH_COMPONENT; + +#if defined(GRAPHICS_API_OPENGL_ES2) + // WARNING: WebGL platform requires unsized internal format definition (GL_DEPTH_COMPONENT) + // while other platforms using OpenGL ES 2.0 require/support sized internal formats depending on the GPU capabilities + if (!RLGL.ExtSupported.texDepthWebGL || useRenderBuffer) + { + if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; + else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; + else glInternalFormat = GL_DEPTH_COMPONENT16; + } +#endif +#if defined(GRAPHICS_API_OPENGL_ES3) + // NOTE: This sized internal format should also work for WebGL 2.0 + // WARNING: Specification only allows GL_DEPTH_COMPONENT32F for GL_FLOAT type + // REF: https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml + if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24; + else glInternalFormat = GL_DEPTH_COMPONENT16; +#endif + + if (!useRenderBuffer && RLGL.ExtSupported.texDepth) + { + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + + TRACELOG(RL_LOG_INFO, "TEXTURE: Depth texture loaded successfully"); + } + else + { + // Create the renderbuffer that will serve as the depth attachment for the framebuffer + // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices + glGenRenderbuffers(1, &id); + glBindRenderbuffer(GL_RENDERBUFFER, id); + glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Depth renderbuffer loaded successfully (%i bits)", id, (RLGL.ExtSupported.maxDepthBits >= 24)? RLGL.ExtSupported.maxDepthBits : 16); + } +#elif defined(GRAPHICS_API_OPENGL_SOFTWARE) + // NOTE: Renderbuffers are the same type of object as textures in rlsw + // WARNING: Ensure that the depth format is the one specified at rlsw compilation + glGenRenderbuffers(1, &id); + glBindRenderbuffer(GL_RENDERBUFFER, id); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, width, height); + glBindRenderbuffer(GL_RENDERBUFFER, 0); +#endif + + return id; +} + +// Load texture cubemap +// NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), +// expected the following convention: +X, -X, +Y, -Y, +Z, -Z +unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount) +{ + unsigned int id = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int mipSize = size; + + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned char *dataPtr = NULL; + if (data != NULL) dataPtr = (unsigned char *)data; + + unsigned int dataSize = rlGetPixelDataSize(size, size, format); + + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if (glInternalFormat != 0) + { + // Load cubemap faces/mipmaps + for (int i = 0; i < 6*mipmapCount; i++) + { + int mipmapLevel = i/6; + int face = i%6; + + if (data == NULL) + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + if ((format == RL_PIXELFORMAT_UNCOMPRESSED_R32) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R16) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, NULL); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); + } + else + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, (unsigned char *)dataPtr + face*dataSize); + else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, dataSize, (unsigned char *)dataPtr + face*dataSize); + } + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + if (face == 5) + { + mipSize /= 2; + if (data != NULL) dataPtr += dataSize*6; // Increment data pointer to next mipmap + + // Security check for NPOT textures + if (mipSize < 1) mipSize = 1; + + dataSize = rlGetPixelDataSize(mipSize, mipSize, format); + } + } + } + + // Set cubemap texture sampling parameters + if (mipmapCount > 1) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + else glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#if defined(GRAPHICS_API_OPENGL_33) + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 +#endif + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif + + if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Cubemap texture loaded successfully (%ix%i)", id, size, size); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load cubemap texture"); + + return id; +} + +// Update already loaded texture in GPU with new data +// WARNING: Not possible to know safely if internal texture format is the expected one... +void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) +{ + glBindTexture(GL_TEXTURE_2D, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, data); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to update for current texture format (%i)", id, format); +} + +// Get OpenGL internal formats and data type from raylib PixelFormat +void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType) +{ + *glInternalFormat = 0; + *glFormat = 0; + *glType = 0; + + switch (format) + { + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + #if !defined(GRAPHICS_API_OPENGL_11) + #if defined(GRAPHICS_API_OPENGL_ES3) + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F_EXT; *glFormat = GL_RED_EXT; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F_EXT; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F_EXT; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F_EXT; *glFormat = GL_RED_EXT; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F_EXT; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F_EXT; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; + #else + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + #if defined(GRAPHICS_API_OPENGL_21) + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_ARB; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_ARB; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_ARB; break; + #else // GRAPHICS_API_OPENGL_ES2 + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + #endif + #endif + #endif + #elif defined(GRAPHICS_API_OPENGL_33) + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F; *glFormat = GL_RED; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; + #endif + #if !defined(GRAPHICS_API_OPENGL_11) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + #endif + default: TRACELOG(RL_LOG_WARNING, "TEXTURE: Current format not supported (%i)", format); break; + } +} + +// Unload texture from GPU memory +void rlUnloadTexture(unsigned int id) +{ + glDeleteTextures(1, &id); +} + +// Generate mipmap data for selected texture +// NOTE: Only supports GPU mipmap generation +void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps) +{ + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return; } + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_2D, id); + + // Check if texture is power-of-two (POT) + bool texIsPOT = false; + + if (((width > 0) && ((width & (width - 1)) == 0)) && + ((height > 0) && ((height & (height - 1)) == 0))) texIsPOT = true; + + if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) + { + //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorithm: GL_FASTEST, GL_NICEST, GL_DONT_CARE + glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically + + #define MIN(a,b) (((a)<(b))? (a):(b)) + #define MAX(a,b) (((a)>(b))? (a):(b)) + + *mipmaps = 1 + (int)floor(log(MAX(width, height))/log(2)); + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Mipmaps generated automatically, total: %i", id, *mipmaps); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps", id); + + glBindTexture(GL_TEXTURE_2D, 0); +#else + TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] GPU mipmap generation not supported", id); +#endif +} + +// Read texture pixel data +void *rlReadTexturePixels(unsigned int id, int width, int height, int format) +{ + void *pixels = NULL; + +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + glBindTexture(GL_TEXTURE_2D, id); + + // NOTE: Using texture id, some texture info can be retrieved (but not on OpenGL ES 2.0) + // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE + //int width, height, format; + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); + + // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding + // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting + // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) + // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + unsigned int size = rlGetPixelDataSize(width, height, format); + + if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + pixels = RL_CALLOC(size, 1); + glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", id, format); + + glBindTexture(GL_TEXTURE_2D, 0); +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + // glGetTexImage() is not available on OpenGL ES 2.0 + // Texture width and height are required on OpenGL ES 2.0, there is no way to get it from texture id + // Two possible Options: + // 1 - Bind texture to color fbo attachment and glReadPixels() + // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() + // Using Option 1, care for texture format on retrieval + // NOTE: This behaviour could be conditioned by graphic driver... + unsigned int fboId = rlLoadFramebuffer(); + + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + glBindTexture(GL_TEXTURE_2D, 0); + + // Attach our texture to FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0); + +#if defined(FBO_READ_TEXTURE_AS_RGBA) + // Reading data as RGBA because FBO texture is configured as RGBA, despite binding another texture format + pixels = RL_CALLOC(rlGetPixelDataSize(width, height, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8), 1); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); +#else + // Reading data as original texture format, in some platforms (RPI, Wasm) it works + pixels = (unsigned char *)RL_MALLOC(rlGetPixelDataSize(width, height, format)); + unsigned int glInternalFormat = 0, glFormat = 0, glType = 0; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + glReadPixels(0, 0, width, height, glFormat, glType, pixels); +#endif + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Clean up temporal fbo + rlUnloadFramebuffer(fboId); +#endif + + return pixels; +} + +// Copy framebuffer pixel data to internal buffer +void rlCopyFramebuffer(int x, int y, int width, int height, int format, void *pixels) +{ +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); // Get OpenGL texture format + swReadPixels(x, y, width, height, glFormat, glType, pixels); +#endif +} + +// Resize internal framebuffer +void rlResizeFramebuffer(int width, int height) +{ +#if defined(GRAPHICS_API_OPENGL_SOFTWARE) + swResize(width, height); +#endif +} + +// Read screen pixel data (color buffer) +unsigned char *rlReadScreenPixels(int width, int height) +{ + unsigned char *imgData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); + + // NOTE: Buffer retrieved is GL_FRONT in single-buffered configurations + // and GL_BACK in double-buffered configurations, make sure to call it at the end of frame + //glReadBuffer(GL_BACK); + + // NOTE: glReadPixels() returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer + // WARNING: Getting alpha channel! Be careful, it can be transparent if not cleared properly! + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, imgData); + + // Flip image vertically + // NOTE: Alpha value has already been applied to RGB in framebuffer, not needed anymore + for (int y = height - 1; y >= height/2; y--) + { + for (int x = 0; x < (width*4); x += 4) + { + unsigned int s = ((height - 1) - y)*width*4 + x; + unsigned int e = y*width*4 + x; + + unsigned char r = imgData[s]; + unsigned char g = imgData[s+1]; + unsigned char b = imgData[s+2]; + + imgData[s] = imgData[e]; + imgData[s+1] = imgData[e+1]; + imgData[s+2] = imgData[e+2]; + imgData[s+3] = 255; // Set alpha component value to 255 (no trasparent image retrieval) + + imgData[e] = r; + imgData[e+1] = g; + imgData[e+2] = b; + imgData[e+3] = 255; // Ditto + } + } + + return imgData; // NOTE: image data should be freed +} + +// Framebuffer management (fbo) +//----------------------------------------------------------------------------------------- +// Load a framebuffer to be used for rendering +// NOTE: No textures attached +unsigned int rlLoadFramebuffer(void) +{ + unsigned int fboId = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return fboId; } + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_SOFTWARE)) + glGenFramebuffers(1, &fboId); // Create the framebuffer object + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer +#endif + + return fboId; +} + +// Attach color buffer texture to a framebuffer object (unloads previous attachment) +// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture +void rlFramebufferAttach(unsigned int id, unsigned int texId, int attachType, int texType, int mipLevel) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_SOFTWARE)) + glBindFramebuffer(GL_FRAMEBUFFER, id); + + switch (attachType) + { + case RL_ATTACHMENT_COLOR_CHANNEL0: + case RL_ATTACHMENT_COLOR_CHANNEL1: + case RL_ATTACHMENT_COLOR_CHANNEL2: + case RL_ATTACHMENT_COLOR_CHANNEL3: + case RL_ATTACHMENT_COLOR_CHANNEL4: + case RL_ATTACHMENT_COLOR_CHANNEL5: + case RL_ATTACHMENT_COLOR_CHANNEL6: + case RL_ATTACHMENT_COLOR_CHANNEL7: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); + else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); + } break; + case RL_ATTACHMENT_DEPTH: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); + } break; + case RL_ATTACHMENT_STENCIL: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); + } break; + default: break; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Verify render texture is complete +bool rlFramebufferComplete(unsigned int id) +{ + bool result = false; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_SOFTWARE)) + glBindFramebuffer(GL_FRAMEBUFFER, id); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + switch (status) + { + case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", id); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", id); break; +#if defined(GRAPHICS_API_OPENGL_ES2) + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", id); break; +#endif + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", id); break; + default: break; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + result = (status == GL_FRAMEBUFFER_COMPLETE); +#endif + + return result; +} + +// Unload framebuffer from GPU memory +// NOTE: All attached textures/cubemaps/renderbuffers are also deleted +void rlUnloadFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_SOFTWARE)) + // Query depth attachment to automatically delete texture/renderbuffer + int depthType = 0; + glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); + + // WARNING: WebGL: INVALID_ENUM: getFramebufferAttachmentParameter: invalid parameter name + // REF: https://registry.khronos.org/webgl/specs/latest/1.0/ + int depthId = 0; + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); + + unsigned int depthIdU = (unsigned int)depthId; + if (depthType == GL_RENDERBUFFER) glDeleteRenderbuffers(1, &depthIdU); + else if (depthType == GL_TEXTURE) glDeleteTextures(1, &depthIdU); + + // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, + // the texture image is automatically detached from the currently bound framebuffer + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &id); + + TRACELOG(RL_LOG_INFO, "FBO: [ID %i] Unloaded framebuffer from VRAM (GPU)", id); +#endif +} + +// Vertex data management +//----------------------------------------------------------------------------------------- +// Load a new attributes buffer +unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferData(GL_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Load a new attributes element buffer +unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Enable vertex buffer (VBO) +void rlEnableVertexBuffer(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); +#endif +} + +// Disable vertex buffer (VBO) +void rlDisableVertexBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, 0); +#endif +} + +// Enable vertex buffer element (VBO element) +void rlEnableVertexBufferElement(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); +#endif +} + +// Disable vertex buffer element (VBO element) +void rlDisableVertexBufferElement(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +#endif +} + +// Update vertex buffer with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBuffer(unsigned int id, const void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferSubData(GL_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +// Update vertex buffer elements with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +// Enable vertex array object (VAO) +bool rlEnableVertexArray(unsigned int vaoId) +{ + bool result = false; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(vaoId); + result = true; + } +#endif + return result; +} + +// Disable vertex array object (VAO) +void rlDisableVertexArray(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) glBindVertexArray(0); +#endif +} + +// Enable vertex attribute index +void rlEnableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glEnableVertexAttribArray(index); +#endif +} + +// Disable vertex attribute index +void rlDisableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDisableVertexAttribArray(index); +#endif +} + +// Draw vertex array +void rlDrawVertexArray(int offset, int count) +{ + glDrawArrays(GL_TRIANGLES, offset, count); +} + +// Draw vertex array elements +void rlDrawVertexArrayElements(int offset, int count, const void *buffer) +{ + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned short *bufferPtr = (unsigned short *)buffer; + if (offset > 0) bufferPtr += offset; + + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr); +} + +// Draw vertex array instanced +void rlDrawVertexArrayInstanced(int offset, int count, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawArraysInstanced(GL_TRIANGLES, offset, count, instances); +#endif +} + +// Draw vertex array elements instanced +void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned short *bufferPtr = (unsigned short *)buffer; + if (offset > 0) bufferPtr += offset; + + glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr, instances); +#endif +} + +// Enable vertex state pointer +void rlEnableStatePointer(int vertexAttribType, void *buffer) +{ +#if defined(GRAPHICS_API_OPENGL_11) + if (buffer != NULL) glEnableClientState(vertexAttribType); + switch (vertexAttribType) + { + case GL_VERTEX_ARRAY: glVertexPointer(3, GL_FLOAT, 0, buffer); break; + case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(2, GL_FLOAT, 0, buffer); break; + case GL_NORMAL_ARRAY: if (buffer != NULL) glNormalPointer(GL_FLOAT, 0, buffer); break; + case GL_COLOR_ARRAY: if (buffer != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, buffer); break; + //case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors + default: break; + } +#endif +} + +// Disable vertex state pointer +void rlDisableStatePointer(int vertexAttribType) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glDisableClientState(vertexAttribType); +#endif +} + +// Load vertex array object (VAO) +unsigned int rlLoadVertexArray(void) +{ + unsigned int vaoId = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return vaoId; } + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) glGenVertexArrays(1, &vaoId); +#endif + + return vaoId; +} + +// Set vertex attribute +void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Data type could be: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT + // Additional types (depends on OpenGL version or extensions): + // - GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE, GL_FIXED, + // - GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10F_11F_11F_REV + + size_t offsetNative = offset; + glVertexAttribPointer(index, compSize, type, normalized, stride, (void *)offsetNative); +#endif +} + +// Set vertex attribute divisor +void rlSetVertexAttributeDivisor(unsigned int index, int divisor) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glVertexAttribDivisor(index, divisor); +#endif +} + +// Unload vertex array object (VAO) +void rlUnloadVertexArray(unsigned int vaoId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(0); + glDeleteVertexArrays(1, &vaoId); + TRACELOG(RL_LOG_INFO, "VAO: [ID %i] Unloaded vertex array data from VRAM (GPU)", vaoId); + } +#endif +} + +// Unload vertex buffer (VBO) +void rlUnloadVertexBuffer(unsigned int vboId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteBuffers(1, &vboId); + //TRACELOG(RL_LOG_INFO, "VBO: Unloaded vertex data from VRAM (GPU)"); +#endif +} + +// Shaders management +//----------------------------------------------------------------------------------------------- +// Load (compile) shader and return shader id +unsigned int rlLoadShader(const char *code, int type) +{ + unsigned int shaderId = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + shaderId = glCreateShader(type); + glShaderSource(shaderId, 1, &code, NULL); + + GLint success = 0; + glCompileShader(shaderId); + glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success); + + if (success == GL_FALSE) + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile vertex shader code", shaderId); break; + case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile fragment shader code", shaderId); break; + //case GL_GEOMETRY_SHADER: + #if defined(GRAPHICS_API_OPENGL_43) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shaderId); break; + #elif defined(GRAPHICS_API_OPENGL_33) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shaderId); break; + #endif + default: break; + } + + int maxLength = 0; + glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetShaderInfoLog(shaderId, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shaderId, log); + RL_FREE(log); + } + + // Unload object allocated by glCreateShader(), + // despite failing in the compilation process + glDeleteShader(shaderId); + shaderId = 0; + } + else + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Vertex shader compiled successfully", shaderId); break; + case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Fragment shader compiled successfully", shaderId); break; + //case GL_GEOMETRY_SHADER: + #if defined(GRAPHICS_API_OPENGL_43) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shaderId); break; + #elif defined(GRAPHICS_API_OPENGL_33) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shaderId); break; + #endif + default: break; + } + } +#endif + + return shaderId; +} + +// Load shader program from code strings +// NOTE: If shader string is NULL, using default vertex/fragment shaders +unsigned int rlLoadShaderProgram(const char *vsCode, const char *fsCode) +{ + unsigned int id = 0; // Shader program id + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int vertexShaderId = 0; + unsigned int fragmentShaderId = 0; + + // Compile vertex shader (if provided) + // NOTE: If not vertex shader is provided, use default one + if (vsCode != NULL) vertexShaderId = rlLoadShader(vsCode, GL_VERTEX_SHADER); + else vertexShaderId = RLGL.State.defaultVShaderId; + + // Compile fragment shader (if provided) + // NOTE: If not vertex shader is provided, use default one + if (fsCode != NULL) fragmentShaderId = rlLoadShader(fsCode, GL_FRAGMENT_SHADER); + else fragmentShaderId = RLGL.State.defaultFShaderId; + + // In case vertex and fragment shader are the default ones, no need to recompile, assign the default shader program id + if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; + else if ((vertexShaderId > 0) && (fragmentShaderId > 0)) + { + // One of or both shader are new, a new shader program needs to be compiled + id = rlLoadShaderProgramEx(vertexShaderId, fragmentShaderId); + + // Detaching and deleting vertex/fragment shaders (if not default ones) + // WARNING: Detach shader before deletion to make sure memory is freed + if (vertexShaderId != RLGL.State.defaultVShaderId) + { + // WARNING: Shader program linkage could fail and returned id is 0 + if (id > 0) glDetachShader(id, vertexShaderId); + glDeleteShader(vertexShaderId); + } + if (fragmentShaderId != RLGL.State.defaultFShaderId) + { + // WARNING: Shader program linkage could fail and returned id is 0 + if (id > 0) glDetachShader(id, fragmentShaderId); + glDeleteShader(fragmentShaderId); + } + + // In case shader program loading failed, assign default shader + if (id == 0) + { + // In case shader loading fails, reassigning default shader + TRACELOG(RL_LOG_WARNING, "SHADER: Failed to load custom shader code, using default shader"); + id = RLGL.State.defaultShaderId; + } + /* + else + { + // Get available shader uniforms + // NOTE: This information is useful for debug... + int uniformCount = -1; + glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); + + for (int i = 0; i < uniformCount; i++) + { + int namelen = -1; + int num = -1; + char name[256] = { 0 }; // Assume no variable names longer than 256 + GLenum type = GL_ZERO; + + // Get the name of the uniforms + glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); + + name[namelen] = 0; + TRACELOG(RL_LOG_DEBUG, "SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); + } + } + */ + } +#endif + + return id; +} + +// Load shader program from already loaded shader ids +unsigned int rlLoadShaderProgramEx(unsigned int vsId, unsigned int fsId) +{ + unsigned int programId = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return programId; } + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + GLint success = 0; + programId = glCreateProgram(); + + glAttachShader(programId, vsId); + glAttachShader(programId, fsId); + + // Default attribute shader locations must be bound before linking + // NOTE: There is no problem with binding a generic attribute index to an attribute variable name + // that is never used; if some attrib name is no found on the shader, it locations becomes -1 + glBindAttribLocation(programId, RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + glBindAttribLocation(programId, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + glBindAttribLocation(programId, RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + glBindAttribLocation(programId, RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + glBindAttribLocation(programId, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + glBindAttribLocation(programId, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + glBindAttribLocation(programId, RL_DEFAULT_SHADER_ATTRIB_LOCATION_INSTANCETRANSFORM, RL_DEFAULT_SHADER_ATTRIB_NAME_INSTANCETRANSFORM); + glBindAttribLocation(programId, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEINDICES); + glBindAttribLocation(programId, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); + + glLinkProgram(programId); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(programId, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link shader program", programId); + + int maxLength = 0; + glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(programId, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", programId, log); + RL_FREE(log); + } + + glDeleteProgram(programId); + + programId = 0; + } + else + { + // Get the size of compiled shader program (not available on OpenGL ES 2.0) + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero + //GLint binarySize = 0; + //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Program shader loaded successfully", programId); + } +#endif + return programId; +} + +// Load compute shader program +unsigned int rlLoadShaderProgramCompute(unsigned int csId) +{ + unsigned int programId = 0; + +#if defined(GRAPHICS_API_OPENGL_43) + GLint success = 0; + programId = glCreateProgram(); + + glAttachShader(programId, csId); + + glLinkProgram(programId); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(programId, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link compute shader program", programId); + + int maxLength = 0; + glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(programId, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", programId, log); + RL_FREE(log); + } + + glDeleteProgram(programId); + + programId = 0; + } + else + { + // Get the size of compiled shader program (not available on OpenGL ES 2.0) + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero + //GLint binarySize = 0; + //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader program loaded successfully", programId); + } +#else + TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not supported, enable GRAPHICS_API_OPENGL_43"); +#endif + + return programId; +} + +// Delete shader +void rlUnloadShader(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteShader(id); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Unloaded shader data from VRAM (GPU)", id); +#endif +} + +// Unload shader program +void rlUnloadShaderProgram(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteProgram(id); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", id); +#endif +} + +// Get shader location uniform +// NOTE: First parameter refers to shader program id +int rlGetLocationUniform(unsigned int id, const char *uniformName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetUniformLocation(id, uniformName); + + //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shaderId, uniformName); + //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shaderId, uniformName, location); +#endif + return location; +} + +// Get shader location attribute +// NOTE: First parameter refers to shader program id +int rlGetLocationAttrib(unsigned int id, const char *attribName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetAttribLocation(id, attribName); + + //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader attribute: %s", shaderId, attribName); + //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader attribute (%s) set at location: %i", shaderId, attribName, location); +#endif + return location; +} + +// Set shader value uniform +void rlSetUniform(int locIndex, const void *value, int uniformType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (uniformType) + { + case RL_SHADER_UNIFORM_FLOAT: glUniform1fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC2: glUniform2fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC3: glUniform3fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC4: glUniform4fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_INT: glUniform1iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + #if !defined(GRAPHICS_API_OPENGL_ES2) + case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; + #endif + case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; + default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); + } +#endif +} + +// Set shader value attribute +void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (attribType) + { + case RL_SHADER_ATTRIB_FLOAT: if (count == 1) glVertexAttrib1fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC2: if (count == 2) glVertexAttrib2fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC3: if (count == 3) glVertexAttrib3fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC4: if (count == 4) glVertexAttrib4fv(locIndex, (float *)value); break; + default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set attrib default value, data type not recognized"); + } +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrix(int locIndex, Matrix mat) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glUniformMatrix4fv(locIndex, 1, false, rlMatrixToFloat(mat)); +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrices(int locIndex, const Matrix *matrices, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) + glUniformMatrix4fv(locIndex, count, true, (const float *)matrices); +#elif defined(GRAPHICS_API_OPENGL_ES2) + // WARNING: WebGL does not support Matrix transpose ("true" parameter) + // REF: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix + glUniformMatrix4fv(locIndex, count, false, (const float *)matrices); +#endif +} + +// Set shader value uniform sampler +void rlSetUniformSampler(int locIndex, unsigned int textureId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Check if texture is already active + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] == textureId) + { + glUniform1i(locIndex, 1 + i); + return; + } + } + + // Register a new active texture for the internal batch system + // NOTE: Default texture is always activated as GL_TEXTURE0 + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] == 0) + { + glUniform1i(locIndex, 1 + i); // Activate new texture unit + RLGL.State.activeTextureId[i] = textureId; // Save texture id for binding on drawing + break; + } + } +#endif +} + +// Set shader currently active (id and locations) +void rlSetShader(unsigned int id, int *locs) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentShaderId != id) + { + rlDrawRenderBatch(RLGL.currentBatch); + RLGL.State.currentShaderId = id; + RLGL.State.currentShaderLocs = locs; + } +#endif +} + +// Dispatch compute shader (equivalent to *draw* for graphics pilepine) +void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glDispatchCompute(groupX, groupY, groupZ); +#endif +} + +// Load shader storage buffer object (SSBO) +unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint) +{ + unsigned int ssbo = 0; + +#if defined(GRAPHICS_API_OPENGL_43) + glGenBuffers(1, &ssbo); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); + glBufferData(GL_SHADER_STORAGE_BUFFER, size, data, usageHint? usageHint : RL_STREAM_COPY); + if (data == NULL) glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL); // Clear buffer data to 0 + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif + + return ssbo; +} + +// Unload shader storage buffer object (SSBO) +void rlUnloadShaderBuffer(unsigned int ssboId) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glDeleteBuffers(1, &ssboId); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif +} + +// Update SSBO buffer data +void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, dataSize, data); +#endif +} + +// Get SSBO buffer size +unsigned int rlGetShaderBufferSize(unsigned int id) +{ + unsigned int result = 0; +#if defined(GRAPHICS_API_OPENGL_43) + GLint64 size = 0; + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glGetBufferParameteri64v(GL_SHADER_STORAGE_BUFFER, GL_BUFFER_SIZE, &size); + if (size > 0) result = (unsigned int)size; +#endif + return result; +} + +// Read SSBO buffer data (GPU->CPU) +void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, count, dest); +#endif +} + +// Bind SSBO buffer +void rlBindShaderBuffer(unsigned int id, unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index, id); +#endif +} + +// Copy SSBO buffer data +void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_COPY_READ_BUFFER, srcId); + glBindBuffer(GL_COPY_WRITE_BUFFER, destId); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, srcOffset, destOffset, count); +#endif +} + +// Bind image texture +void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly) +{ +#if defined(GRAPHICS_API_OPENGL_43) + unsigned int glInternalFormat = 0, glFormat = 0, glType = 0; + + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + glBindImageTexture(index, id, 0, 0, 0, readonly? GL_READ_ONLY : GL_READ_WRITE, glInternalFormat); +#else + TRACELOG(RL_LOG_WARNING, "TEXTURE: Image texture binding not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif +} + +// Matrix state management +//----------------------------------------------------------------------------------------- +// Get internal modelview matrix +Matrix rlGetMatrixModelview(void) +{ + Matrix matrix = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mat); + matrix.m0 = mat[0]; + matrix.m1 = mat[1]; + matrix.m2 = mat[2]; + matrix.m3 = mat[3]; + matrix.m4 = mat[4]; + matrix.m5 = mat[5]; + matrix.m6 = mat[6]; + matrix.m7 = mat[7]; + matrix.m8 = mat[8]; + matrix.m9 = mat[9]; + matrix.m10 = mat[10]; + matrix.m11 = mat[11]; + matrix.m12 = mat[12]; + matrix.m13 = mat[13]; + matrix.m14 = mat[14]; + matrix.m15 = mat[15]; +#else + matrix = RLGL.State.modelview; +#endif + return matrix; +} + +// Get internal projection matrix +Matrix rlGetMatrixProjection(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_PROJECTION_MATRIX,mat); + Matrix m; + m.m0 = mat[0]; + m.m1 = mat[1]; + m.m2 = mat[2]; + m.m3 = mat[3]; + m.m4 = mat[4]; + m.m5 = mat[5]; + m.m6 = mat[6]; + m.m7 = mat[7]; + m.m8 = mat[8]; + m.m9 = mat[9]; + m.m10 = mat[10]; + m.m11 = mat[11]; + m.m12 = mat[12]; + m.m13 = mat[13]; + m.m14 = mat[14]; + m.m15 = mat[15]; + return m; +#else + return RLGL.State.projection; +#endif +} + +// Get internal accumulated transform matrix +Matrix rlGetMatrixTransform(void) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // TODO: Consider possible transform matrices in the RLGL.State.stack + //Matrix matStackTransform = rlMatrixIdentity(); + //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = rlMatrixMultiply(RLGL.State.stack[i], matStackTransform); + + mat = RLGL.State.transform; +#endif + return mat; +} + +// Get internal projection matrix for stereo render (selected eye) +Matrix rlGetMatrixProjectionStereo(int eye) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.projectionStereo[eye]; +#endif + return mat; +} + +// Get internal view offset matrix for stereo render (selected eye) +Matrix rlGetMatrixViewOffsetStereo(int eye) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.viewOffsetStereo[eye]; +#endif + return mat; +} + +// Set a custom modelview matrix (replaces internal modelview matrix) +void rlSetMatrixModelview(Matrix view) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.modelview = view; +#endif +} + +// Set a custom projection matrix (replaces internal projection matrix) +void rlSetMatrixProjection(Matrix projection) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projection = projection; +#endif +} + +// Set eyes projection matrices for stereo rendering +void rlSetMatrixProjectionStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projectionStereo[0] = right; + RLGL.State.projectionStereo[1] = left; +#endif +} + +// Set eyes view offsets matrices for stereo rendering +void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.viewOffsetStereo[0] = right; + RLGL.State.viewOffsetStereo[1] = left; +#endif +} + +// Load and draw a quad in NDC +void rlLoadDrawQuad(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int quadVAO = 0; + unsigned int quadVBO = 0; + + float vertices[] = { + // Positions Texcoords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &quadVBO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, texcoords) + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords + + // Draw quad + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); + + // Delete buffers (VBO and VAO) + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); +#endif +} + +// Load and draw a cube in NDC +void rlLoadDrawCube(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int cubeVAO = 0; + unsigned int cubeVBO = 0; + + float vertices[] = { + // Positions Normals Texcoords + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &cubeVAO); + glBindVertexArray(cubeVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &cubeVBO); + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, normals, texcoords) + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + // Draw cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + + // Delete VBO and VAO + glDeleteBuffers(1, &cubeVBO); + glDeleteVertexArrays(1, &cubeVAO); +#endif +} + +// Get name string for pixel format +const char *rlGetPixelFormatName(unsigned int format) +{ + switch (format) + { + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: return "GRAYSCALE"; break; // 8 bit per pixel (no alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: return "GRAY_ALPHA"; break; // 8*2 bpp (2 channels) + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: return "R5G6B5"; break; // 16 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: return "R8G8B8"; break; // 24 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: return "R5G5B5A1"; break; // 16 bpp (1 bit alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: return "R4G4B4A4"; break; // 16 bpp (4 bit alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: return "R8G8B8A8"; break; // 32 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R32: return "R32"; break; // 32 bpp (1 channel - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: return "R32G32B32"; break; // 32*3 bpp (3 channels - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: return "R32G32B32A32"; break; // 32*4 bpp (4 channels - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16: return "R16"; break; // 16 bpp (1 channel - half float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: return "R16G16B16"; break; // 16*3 bpp (3 channels - half float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: return "R16G16B16A16"; break; // 16*4 bpp (4 channels - half float) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: return "DXT1_RGB"; break; // 4 bpp (no alpha) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: return "DXT1_RGBA"; break; // 4 bpp (1 bit alpha) + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: return "DXT3_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: return "DXT5_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: return "ETC1_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: return "ETC2_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: return "ETC2_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: return "PVRT_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: return "PVRT_RGBA"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: return "ASTC_4x4_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: return "ASTC_8x8_RGBA"; break; // 2 bpp + default: return "UNKNOWN"; break; + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Load default shader (just vertex positioning and texture coloring) +// NOTE: This shader program is used for internal buffers +// NOTE: Loaded: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs +static void rlLoadShaderDefault(void) +{ + RLGL.State.defaultShaderLocs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) RLGL.State.defaultShaderLocs[i] = -1; + + // Vertex shader directly defined, no external file required + const char *defaultVShaderCode = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) (on some browsers) + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) (on some browsers) + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#endif + + "uniform mat4 mvp; \n" + "void main() \n" + "{ \n" + " fragTexCoord = vertexTexCoord; \n" + " fragColor = vertexColor; \n" + " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" + "} \n"; + + // Fragment shader directly defined, no external file required + const char *defaultFShaderCode = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif + + // NOTE: Compiled vertex/fragment shaders are not deleted, + // they are kept for re-use as default shaders in case some shader loading fails + RLGL.State.defaultVShaderId = rlLoadShader(defaultVShaderCode, GL_VERTEX_SHADER); // Compile default vertex shader + RLGL.State.defaultFShaderId = rlLoadShader(defaultFShaderCode, GL_FRAGMENT_SHADER); // Compile default fragment shader + + RLGL.State.defaultShaderId = rlLoadShaderProgramEx(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); + + if (RLGL.State.defaultShaderId > 0) + { + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShaderId); + + // Set default shader locations: attributes locations + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + + // Set default shader locations: uniform locations + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); + } + else TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShaderId); +} + +// Unload default shader +// NOTE: Unloads: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs +static void rlUnloadShaderDefault(void) +{ + glUseProgram(0); + + glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultVShaderId); + glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultFShaderId); + glDeleteShader(RLGL.State.defaultVShaderId); + glDeleteShader(RLGL.State.defaultFShaderId); + + glDeleteProgram(RLGL.State.defaultShaderId); + + RL_FREE(RLGL.State.defaultShaderLocs); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader unloaded successfully", RLGL.State.defaultShaderId); +} + +#if RLGL_SHOW_GL_DETAILS_INFO +// Get compressed format official GL identifier name +static const char *rlGetCompressedFormatName(int format) +{ + switch (format) + { + // GL_EXT_texture_compression_s3tc + case 0x83F0: return "GL_COMPRESSED_RGB_S3TC_DXT1_EXT"; break; + case 0x83F1: return "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT"; break; + case 0x83F2: return "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT"; break; + case 0x83F3: return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT"; break; + // GL_3DFX_texture_compression_FXT1 + case 0x86B0: return "GL_COMPRESSED_RGB_FXT1_3DFX"; break; + case 0x86B1: return "GL_COMPRESSED_RGBA_FXT1_3DFX"; break; + // GL_IMG_texture_compression_pvrtc + case 0x8C00: return "GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG"; break; + case 0x8C01: return "GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG"; break; + case 0x8C02: return "GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"; break; + case 0x8C03: return "GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"; break; + // GL_OES_compressed_ETC1_RGB8_texture + case 0x8D64: return "GL_ETC1_RGB8_OES"; break; + // GL_ARB_texture_compression_rgtc + case 0x8DBB: return "GL_COMPRESSED_RED_RGTC1"; break; + case 0x8DBC: return "GL_COMPRESSED_SIGNED_RED_RGTC1"; break; + case 0x8DBD: return "GL_COMPRESSED_RG_RGTC2"; break; + case 0x8DBE: return "GL_COMPRESSED_SIGNED_RG_RGTC2"; break; + // GL_ARB_texture_compression_bptc + case 0x8E8C: return "GL_COMPRESSED_RGBA_BPTC_UNORM_ARB"; break; + case 0x8E8D: return "GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB"; break; + case 0x8E8E: return "GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB"; break; + case 0x8E8F: return "GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB"; break; + // GL_ARB_ES3_compatibility + case 0x9274: return "GL_COMPRESSED_RGB8_ETC2"; break; + case 0x9275: return "GL_COMPRESSED_SRGB8_ETC2"; break; + case 0x9276: return "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; + case 0x9277: return "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; + case 0x9278: return "GL_COMPRESSED_RGBA8_ETC2_EAC"; break; + case 0x9279: return "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"; break; + case 0x9270: return "GL_COMPRESSED_R11_EAC"; break; + case 0x9271: return "GL_COMPRESSED_SIGNED_R11_EAC"; break; + case 0x9272: return "GL_COMPRESSED_RG11_EAC"; break; + case 0x9273: return "GL_COMPRESSED_SIGNED_RG11_EAC"; break; + // GL_KHR_texture_compression_astc_hdr + case 0x93B0: return "GL_COMPRESSED_RGBA_ASTC_4x4_KHR"; break; + case 0x93B1: return "GL_COMPRESSED_RGBA_ASTC_5x4_KHR"; break; + case 0x93B2: return "GL_COMPRESSED_RGBA_ASTC_5x5_KHR"; break; + case 0x93B3: return "GL_COMPRESSED_RGBA_ASTC_6x5_KHR"; break; + case 0x93B4: return "GL_COMPRESSED_RGBA_ASTC_6x6_KHR"; break; + case 0x93B5: return "GL_COMPRESSED_RGBA_ASTC_8x5_KHR"; break; + case 0x93B6: return "GL_COMPRESSED_RGBA_ASTC_8x6_KHR"; break; + case 0x93B7: return "GL_COMPRESSED_RGBA_ASTC_8x8_KHR"; break; + case 0x93B8: return "GL_COMPRESSED_RGBA_ASTC_10x5_KHR"; break; + case 0x93B9: return "GL_COMPRESSED_RGBA_ASTC_10x6_KHR"; break; + case 0x93BA: return "GL_COMPRESSED_RGBA_ASTC_10x8_KHR"; break; + case 0x93BB: return "GL_COMPRESSED_RGBA_ASTC_10x10_KHR"; break; + case 0x93BC: return "GL_COMPRESSED_RGBA_ASTC_12x10_KHR"; break; + case 0x93BD: return "GL_COMPRESSED_RGBA_ASTC_12x12_KHR"; break; + case 0x93D0: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"; break; + case 0x93D1: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"; break; + case 0x93D2: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"; break; + case 0x93D3: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"; break; + case 0x93D4: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"; break; + case 0x93D5: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"; break; + case 0x93D6: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"; break; + case 0x93D7: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"; break; + case 0x93D8: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"; break; + case 0x93D9: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"; break; + case 0x93DA: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"; break; + case 0x93DB: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"; break; + case 0x93DC: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"; break; + case 0x93DD: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"; break; + default: return "GL_COMPRESSED_UNKNOWN"; break; + } +} +#endif + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +// Get pixel data size in bytes (image or texture) +// NOTE: Size depends on pixel format +static int rlGetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: bpp = 16; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 16*3; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 16*4; break; + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: // 8 bytes per each 4x4 block + { + int blockWidth = (width + 3)/4; + int blockHeight = (height + 3)/4; + unsigned long long dataSizeBytes = (unsigned long long)blockWidth*blockHeight*8; + if (dataSizeBytes < INT_MAX) dataSize = (int)dataSizeBytes; + + } break; + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: // 16 bytes per each 4x4 block + { + int blockWidth = (width + 3)/4; + int blockHeight = (height + 3)/4; + unsigned long long dataSizeBytes = (unsigned long long)blockWidth*blockHeight*16; + if (dataSizeBytes < INT_MAX) dataSize = (int)dataSizeBytes; + + } break; + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: // 4 bytes per each 4x4 block + { + int blockWidth = (width + 3)/4; + int blockHeight = (height + 3)/4; + unsigned long long dataSizeBytes = (unsigned long long)blockWidth*blockHeight*4; + if (dataSizeBytes < INT_MAX) dataSize = (int)dataSizeBytes; + + } break; + default: break; + } + + // Compute dataSize for uncompressed texture data (no blocks) + if ((format >= RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) && + (format <= RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) + { + unsigned long long dataSizeBytes = ((unsigned long long)width*height*bpp) >> 3; // Get size in bytes (dividing by 8) + if (dataSizeBytes < INT_MAX) dataSize = (int)dataSizeBytes; + } + + if (dataSize == 0) TRACELOG(LOG_WARNING, "Requested image size is larger than 2GB, it can not be allocated"); + + return dataSize; +} + +// Auxiliar math functions +//------------------------------------------------------------------------------- +// Get identity matrix +static Matrix rlMatrixIdentity(void) +{ + Matrix matIdentity = { 0 }; + matIdentity.m0 = 1.0f; + matIdentity.m5 = 1.0f; + matIdentity.m10 = 1.0f; + matIdentity.m15 = 1.0f; + + return matIdentity; +} +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Get float array of matrix data +// Explicit conversion to column-major memory layout +static rl_float16 rlMatrixToFloatV(Matrix mat) +{ + rl_float16 result = { 0 }; + + result.v[0] = mat.m0; + result.v[1] = mat.m1; + result.v[2] = mat.m2; + result.v[3] = mat.m3; + result.v[4] = mat.m4; + result.v[5] = mat.m5; + result.v[6] = mat.m6; + result.v[7] = mat.m7; + result.v[8] = mat.m8; + result.v[9] = mat.m9; + result.v[10] = mat.m10; + result.v[11] = mat.m11; + result.v[12] = mat.m12; + result.v[13] = mat.m13; + result.v[14] = mat.m14; + result.v[15] = mat.m15; + + return result; +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +static Matrix rlMatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +// Transposes provided matrix +static Matrix rlMatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +static Matrix rlMatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} +#endif + +#endif // RLGL_IMPLEMENTATION diff --git a/src/libraries/raylib/raylib/src/rmodels.c b/src/libraries/raylib/raylib/src/rmodels.c new file mode 100644 index 0000000..4dbfb4b --- /dev/null +++ b/src/libraries/raylib/raylib/src/rmodels.c @@ -0,0 +1,7359 @@ +/********************************************************************************************** +* +* rmodels - Basic functions to draw 3d shapes and load and draw 3d models +* +* CONFIGURATION: +* #define SUPPORT_MODULE_RMODELS 1 +* rmodels module is included in the build +* +* #define SUPPORT_FILEFORMAT_OBJ 1 +* #define SUPPORT_FILEFORMAT_MTL 1 +* #define SUPPORT_FILEFORMAT_IQM 1 +* #define SUPPORT_FILEFORMAT_GLTF 1 +* #define SUPPORT_FILEFORMAT_GLTF_WRITE 0 +* #define SUPPORT_FILEFORMAT_VOX 1 +* #define SUPPORT_FILEFORMAT_M3D 1 +* Selected desired fileformats to be supported for model data loading +* +* #define SUPPORT_MESH_GENERATION 1 +* Support procedural mesh generation functions, uses external par_shapes.h library +* NOTE: Some generated meshes DO NOT include generated texture coordinates +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +#include "config.h" // Defines module configuration flags + +#if SUPPORT_MODULE_RMODELS + +#include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 +#include "raymath.h" // Required for: Vector3, Quaternion and Matrix functionality + +#include // Required for: sprintf(), snprintf() +#include // Required for: malloc(), calloc(), free() +#include // Required for: strlen(), memcmp() +#include // Required for: sinf(), cosf(), sqrtf(), fabsf() + +#if SUPPORT_FILEFORMAT_OBJ || SUPPORT_FILEFORMAT_MTL + #define TINYOBJ_MALLOC RL_MALLOC + #define TINYOBJ_CALLOC RL_CALLOC + #define TINYOBJ_REALLOC RL_REALLOC + #define TINYOBJ_FREE RL_FREE + + #define TINYOBJ_LOADER_C_IMPLEMENTATION + #include "external/tinyobj_loader_c.h" // OBJ/MTL file formats loading +#endif + +#if SUPPORT_FILEFORMAT_GLTF + #define CGLTF_MALLOC RL_MALLOC + #define CGLTF_FREE RL_FREE + + #define CGLTF_IMPLEMENTATION + #include "external/cgltf.h" // glTF file format loading +#endif +#if SUPPORT_FILEFORMAT_GLTF_WRITE + // NOTE: No need for custom allocators, memory buffer provided + #define CGLTF_WRITE_IMPLEMENTATION + #include "external/cgltf_write.h" // glTF file format writing +#endif + +#if SUPPORT_FILEFORMAT_VOX + #define VOX_MALLOC RL_MALLOC + #define VOX_CALLOC RL_CALLOC + #define VOX_REALLOC RL_REALLOC + #define VOX_FREE RL_FREE + + #define VOX_LOADER_IMPLEMENTATION + #include "external/vox_loader.h" // VOX file format loading (MagicaVoxel) +#endif + +#if SUPPORT_FILEFORMAT_M3D + #define M3D_MALLOC RL_MALLOC + #define M3D_REALLOC RL_REALLOC + #define M3D_FREE RL_FREE + + #define M3D_IMPLEMENTATION + #include "external/m3d.h" // Model3D file format loading +#endif + +#if SUPPORT_MESH_GENERATION + #define PAR_MALLOC(T, N) ((T *)RL_MALLOC(N*sizeof(T))) + #define PAR_CALLOC(T, N) ((T *)RL_CALLOC(N*sizeof(T), 1)) + #define PAR_REALLOC(T, BUF, N) ((T *)RL_REALLOC(BUF, sizeof(T)*(N))) + #define PAR_FREE RL_FREE + + #if defined(_MSC_VER) // Disable some MSVC warning + #pragma warning(push) + #pragma warning(disable : 4244) + #pragma warning(disable : 4305) + #endif + + #define PAR_SHAPES_IMPLEMENTATION + #include "external/par_shapes.h" // Shapes 3d parametric generation + + #if defined(_MSC_VER) + #pragma warning(pop) // Disable MSVC warning suppression + #endif +#endif + +#if defined(_WIN32) + #include // Required for: _chdir() [Used in LoadOBJ()] + #define CHDIR _chdir +#else + #include // Required for: chdir() (POSIX) [Used in LoadOBJ()] + #define CHDIR chdir +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef MAX_MATERIAL_MAPS + #define MAX_MATERIAL_MAPS 12 // Maximum number of maps supported +#endif +#ifndef MAX_MESH_VERTEX_BUFFERS +#if SUPPORT_GPU_SKINNING + // NOTE: Two additional vertex buffers required to store bone indices and bone weights + // WARNING: Some GPUs could not support more than 8 VBOs + #define MAX_MESH_VERTEX_BUFFERS 9 // Maximum vertex buffers (VBO) per mesh +#else + #define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh +#endif +#endif +#ifndef MAX_FILEPATH_LENGTH + #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +#if SUPPORT_FILEFORMAT_OBJ +static Model LoadOBJ(const char *fileName); // Load OBJ mesh data +#endif +#if SUPPORT_FILEFORMAT_IQM +static Model LoadIQM(const char *fileName); // Load IQM mesh data +static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, int *animCount); // Load IQM animation data +#endif +#if SUPPORT_FILEFORMAT_GLTF +static Model LoadGLTF(const char *fileName); // Load GLTF mesh data +static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCount); // Load GLTF animation data +#endif +#if SUPPORT_FILEFORMAT_VOX +static Model LoadVOX(const char *filename); // Load VOX mesh data +#endif +#if SUPPORT_FILEFORMAT_M3D +static Model LoadM3D(const char *filename); // Load M3D mesh data +static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, int *animCount); // Load M3D animation data +#endif +#if SUPPORT_FILEFORMAT_OBJ || SUPPORT_FILEFORMAT_MTL +static void ProcessMaterialsOBJ(Material *rayMaterials, tinyobj_material_t *materials, int materialCount); // Process obj materials +#endif + +// Update model vertex data (positions and normals) +static void UpdateModelAnimationVertexBuffers(Model model); + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +// Draw a line in 3D world space +void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex3f(startPos.x, startPos.y, startPos.z); + rlVertex3f(endPos.x, endPos.y, endPos.z); + rlEnd(); +} + +// Draw a point in 3D space, actually a small line +// WARNING: OpenGL ES 2.0 does not support point mode drawing +void DrawPoint3D(Vector3 position, Color color) +{ + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex3f(0.0f, 0.0f, 0.0f); + rlVertex3f(0.0f, 0.0f, 0.1f); + rlEnd(); + rlPopMatrix(); +} + +// Draw a circle in 3D world space +void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color) +{ + rlPushMatrix(); + rlTranslatef(center.x, center.y, center.z); + rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z); + + rlBegin(RL_LINES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex3f(sinf(DEG2RAD*i)*radius, cosf(DEG2RAD*i)*radius, 0.0f); + rlVertex3f(sinf(DEG2RAD*(i + 10))*radius, cosf(DEG2RAD*(i + 10))*radius, 0.0f); + } + rlEnd(); + rlPopMatrix(); +} + +// Draw a color-filled triangle (vertex in counter-clockwise order!) +void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color) +{ + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex3f(v1.x, v1.y, v1.z); + rlVertex3f(v2.x, v2.y, v2.z); + rlVertex3f(v3.x, v3.y, v3.z); + rlEnd(); +} + +// Draw a triangle strip defined by points +void DrawTriangleStrip3D(const Vector3 *points, int pointCount, Color color) +{ + if (pointCount < 3) return; // Security check + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 2; i < pointCount; i++) + { + if ((i%2) == 0) + { + rlVertex3f(points[i].x, points[i].y, points[i].z); + rlVertex3f(points[i - 2].x, points[i - 2].y, points[i - 2].z); + rlVertex3f(points[i - 1].x, points[i - 1].y, points[i - 1].z); + } + else + { + rlVertex3f(points[i].x, points[i].y, points[i].z); + rlVertex3f(points[i - 1].x, points[i - 1].y, points[i - 1].z); + rlVertex3f(points[i - 2].x, points[i - 2].y, points[i - 2].z); + } + } + rlEnd(); +} + +// Draw cube +// NOTE: Cube position is the center position +void DrawCube(Vector3 position, float width, float height, float length, Color color) +{ + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) + rlTranslatef(position.x, position.y, position.z); + //rlRotatef(45, 0, 1, 0); + //rlScalef(1.0f, 1.0f, 1.0f); // NOTE: Vertices are directly scaled on definition + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // Front face + rlNormal3f(0.0f, 0.0f, 1.0f); + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + + // Back face + rlNormal3f(0.0f, 0.0f, -1.0f); + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Left + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + + // Top face + rlNormal3f(0.0f, 1.0f, 0.0f); + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x - width/2, y + height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right + + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right + + // Bottom face + rlNormal3f(0.0f, -1.0f, 0.0f); + rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + + rlVertex3f(x + width/2, y - height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left + + // Right face + rlNormal3f(1.0f, 0.0f, 0.0f); + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left + + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left + + // Left face + rlNormal3f(-1.0f, 0.0f, 0.0f); + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Right + + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right + rlEnd(); + rlPopMatrix(); +} + +// Draw cube (Vector version) +void DrawCubeV(Vector3 position, Vector3 size, Color color) +{ + DrawCube(position, size.x, size.y, size.z, color); +} + +// Draw cube wires +void DrawCubeWires(Vector3 position, float width, float height, float length, Color color) +{ + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // Front face + //------------------------------------------------------------------ + // Bottom line + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom right + + // Left line + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom right + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top right + + // Top line + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top left + + // Right line + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top left + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom left + + // Back face + //------------------------------------------------------------------ + // Bottom line + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom left + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom right + + // Left line + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom right + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top right + + // Top line + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top right + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top left + + // Right line + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top left + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom left + + // Top face + //------------------------------------------------------------------ + // Left line + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top left front + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top left back + + // Right line + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top right front + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top right back + + // Bottom face + //------------------------------------------------------------------ + // Left line + rlVertex3f(x - width/2, y - height/2, z + length/2); // Top left front + rlVertex3f(x - width/2, y - height/2, z - length/2); // Top left back + + // Right line + rlVertex3f(x + width/2, y - height/2, z + length/2); // Top right front + rlVertex3f(x + width/2, y - height/2, z - length/2); // Top right back + rlEnd(); + rlPopMatrix(); +} + +// Draw cube wires (vector version) +void DrawCubeWiresV(Vector3 position, Vector3 size, Color color) +{ + DrawCubeWires(position, size.x, size.y, size.z, color); +} + +// Draw sphere +void DrawSphere(Vector3 centerPos, float radius, Color color) +{ + DrawSphereEx(centerPos, radius, 16, 16, color); +} + +// Draw sphere with defined rings and slices +void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color) +{ +#if 0 + // Basic implementation, do not use it! + // For a sphere with 16 rings and 16 slices it requires 8640 cos()/sin() function calls! + // New optimized version below only requires 4 cos()/sin() calls + + rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> translate) + rlTranslatef(centerPos.x, centerPos.y, centerPos.z); + rlScalef(radius, radius, radius); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < (rings + 2); i++) + { + for (int j = 0; j < slices; j++) + { + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*j/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*j/slices))); + + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices))); + } + } + rlEnd(); + rlPopMatrix(); +#endif + + rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> translate) + rlTranslatef(centerPos.x, centerPos.y, centerPos.z); + rlScalef(radius, radius, radius); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + float ringangle = DEG2RAD*(180.0f/rings); // Angle between latitudinal parallels + float sliceangle = DEG2RAD*(360.0f/slices); // Angle between longitudinal meridians + + float cosring = cosf(ringangle); + float sinring = sinf(ringangle); + float cosslice = cosf(sliceangle); + float sinslice = sinf(sliceangle); + + Vector3 vertices[4] = { 0 }; // Required to store face vertices + vertices[2] = (Vector3){ 0, 1, 0 }; + vertices[3] = (Vector3){ sinring, cosring, 0 }; + + for (int i = 0; i < rings; i++) + { + for (int j = 0; j < slices; j++) + { + vertices[0] = vertices[2]; // Rotate around y axis to set up vertices for next face + vertices[1] = vertices[3]; + vertices[2] = (Vector3){ cosslice*vertices[2].x - sinslice*vertices[2].z, vertices[2].y, sinslice*vertices[2].x + cosslice*vertices[2].z }; // Rotation matrix around y axis + vertices[3] = (Vector3){ cosslice*vertices[3].x - sinslice*vertices[3].z, vertices[3].y, sinslice*vertices[3].x + cosslice*vertices[3].z }; + + rlNormal3f(vertices[0].x, vertices[0].y, vertices[0].z); + rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z); + rlNormal3f(vertices[3].x, vertices[3].y, vertices[3].z); + rlVertex3f(vertices[3].x, vertices[3].y, vertices[3].z); + rlNormal3f(vertices[1].x, vertices[1].y, vertices[1].z); + rlVertex3f(vertices[1].x, vertices[1].y, vertices[1].z); + + rlNormal3f(vertices[0].x, vertices[0].y, vertices[0].z); + rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z); + rlNormal3f(vertices[2].x, vertices[2].y, vertices[2].z); + rlVertex3f(vertices[2].x, vertices[2].y, vertices[2].z); + rlNormal3f(vertices[3].x, vertices[3].y, vertices[3].z); + rlVertex3f(vertices[3].x, vertices[3].y, vertices[3].z); + } + + vertices[2] = vertices[3]; // Rotate around z axis to set up starting vertices for next ring + vertices[3] = (Vector3){ cosring*vertices[3].x + sinring*vertices[3].y, -sinring*vertices[3].x + cosring*vertices[3].y, vertices[3].z }; // Rotation matrix around z axis + } + rlEnd(); + rlPopMatrix(); +} + +// Draw sphere wires +void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color) +{ + rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> translate) + rlTranslatef(centerPos.x, centerPos.y, centerPos.z); + rlScalef(radius, radius, radius); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + float ringangle = DEG2RAD*(180.0f/rings); // Angle between latitudinal parallels + float sliceangle = DEG2RAD*(360.0f/slices); // Angle between longitudinal meridians + + float cosring = cosf(ringangle); + float sinring = sinf(ringangle); + float cosslice = cosf(sliceangle); + float sinslice = sinf(sliceangle); + + Vector3 vertices[4] = { 0 }; // Required to store face vertices + vertices[2] = (Vector3){ 0, 1, 0 }; + vertices[3] = (Vector3){ sinring, cosring, 0 }; + + for (int i = 0; i < rings; i++) + { + for (int j = 0; j < slices; j++) + { + vertices[0] = vertices[2]; // Rotate around y axis to set up vertices for next face + vertices[1] = vertices[3]; + vertices[2] = (Vector3){ cosslice*vertices[2].x - sinslice*vertices[2].z, vertices[2].y, sinslice*vertices[2].x + cosslice*vertices[2].z }; // Rotation matrix around y axis + vertices[3] = (Vector3){ cosslice*vertices[3].x - sinslice*vertices[3].z, vertices[3].y, sinslice*vertices[3].x + cosslice*vertices[3].z }; + + // Longitude Lines + rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z); + rlVertex3f(vertices[1].x, vertices[1].y, vertices[1].z); + + // Latitude Lines + rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z); + rlVertex3f(vertices[2].x, vertices[2].y, vertices[2].z); + + // Diagonal Lines + rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z); + rlVertex3f(vertices[3].x, vertices[3].y, vertices[3].z); + } + + vertices[2] = vertices[3]; // Rotate around z axis to set up starting vertices for next ring + vertices[3] = (Vector3){ cosring*vertices[3].x + sinring*vertices[3].y, -sinring*vertices[3].x + cosring*vertices[3].y, vertices[3].z }; // Rotation matrix around z axis + } + rlEnd(); + rlPopMatrix(); +} + +// Draw a cylinder +// NOTE: It could be also used for pyramid and cone +void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color) +{ + if (sides < 3) sides = 3; + + const float angleStep = 360.0f/sides; + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + if (radiusTop > 0) + { + // Draw Body ------------------------------------------------------------------------------------- + for (int i = 0; i < sides; i++) + { + rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom); //Bottom Left + rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusBottom, 0, cosf(DEG2RAD*(i+1)*angleStep)*radiusBottom); //Bottom Right + rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusTop, height, cosf(DEG2RAD*(i+1)*angleStep)*radiusTop); //Top Right + + rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusTop, height, cosf(DEG2RAD*i*angleStep)*radiusTop); //Top Left + rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom); //Bottom Left + rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusTop, height, cosf(DEG2RAD*(i+1)*angleStep)*radiusTop); //Top Right + } + + // Draw Cap -------------------------------------------------------------------------------------- + for (int i = 0; i < sides; i++) + { + rlVertex3f(0, height, 0); + rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusTop, height, cosf(DEG2RAD*i*angleStep)*radiusTop); + rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusTop, height, cosf(DEG2RAD*(i+1)*angleStep)*radiusTop); + } + } + else + { + // Draw Cone ------------------------------------------------------------------------------------- + for (int i = 0; i < sides; i++) + { + rlVertex3f(0, height, 0); + rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusBottom, 0, cosf(DEG2RAD*(i+1)*angleStep)*radiusBottom); + } + } + + // Draw Base ----------------------------------------------------------------------------------------- + for (int i = 0; i < sides; i++) + { + rlVertex3f(0, 0, 0); + rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusBottom, 0, cosf(DEG2RAD*(i+1)*angleStep)*radiusBottom); + rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom); + } + + rlEnd(); + rlPopMatrix(); +} + +// Draw a cylinder with base at startPos and top at endPos +// NOTE: It could be also used for pyramid and cone +void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color) +{ + if (sides < 3) sides = 3; + + Vector3 direction = { endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z }; + if ((direction.x == 0) && (direction.y == 0) && (direction.z == 0)) return; // Security check + + // Construct a basis of the base and the top face: + Vector3 b1 = Vector3Normalize(Vector3Perpendicular(direction)); + Vector3 b2 = Vector3Normalize(Vector3CrossProduct(b1, direction)); + + float baseAngle = (2.0f*PI)/sides; + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < sides; i++) + { + // Compute the four vertices + float s1 = sinf(baseAngle*(i + 0))*startRadius; + float c1 = cosf(baseAngle*(i + 0))*startRadius; + Vector3 w1 = { startPos.x + s1*b1.x + c1*b2.x, startPos.y + s1*b1.y + c1*b2.y, startPos.z + s1*b1.z + c1*b2.z }; + float s2 = sinf(baseAngle*(i + 1))*startRadius; + float c2 = cosf(baseAngle*(i + 1))*startRadius; + Vector3 w2 = { startPos.x + s2*b1.x + c2*b2.x, startPos.y + s2*b1.y + c2*b2.y, startPos.z + s2*b1.z + c2*b2.z }; + float s3 = sinf(baseAngle*(i + 0))*endRadius; + float c3 = cosf(baseAngle*(i + 0))*endRadius; + Vector3 w3 = { endPos.x + s3*b1.x + c3*b2.x, endPos.y + s3*b1.y + c3*b2.y, endPos.z + s3*b1.z + c3*b2.z }; + float s4 = sinf(baseAngle*(i + 1))*endRadius; + float c4 = cosf(baseAngle*(i + 1))*endRadius; + Vector3 w4 = { endPos.x + s4*b1.x + c4*b2.x, endPos.y + s4*b1.y + c4*b2.y, endPos.z + s4*b1.z + c4*b2.z }; + + if (startRadius > 0) + { + rlVertex3f(startPos.x, startPos.y, startPos.z); // | + rlVertex3f(w2.x, w2.y, w2.z); // T0 + rlVertex3f(w1.x, w1.y, w1.z); // | + } + // w2 x.-----------x startPos + rlVertex3f(w1.x, w1.y, w1.z); // | |\'. T0 / + rlVertex3f(w2.x, w2.y, w2.z); // T1 | \ '. / + rlVertex3f(w3.x, w3.y, w3.z); // | |T \ '. / + // | 2 \ T 'x w1 + rlVertex3f(w2.x, w2.y, w2.z); // | w4 x.---\-1-|---x endPos + rlVertex3f(w4.x, w4.y, w4.z); // T2 '. \ |T3/ + rlVertex3f(w3.x, w3.y, w3.z); // | '. \ | / + // '.\|/ + if (endRadius > 0) // 'x w3 + { + rlVertex3f(endPos.x, endPos.y, endPos.z); // | + rlVertex3f(w3.x, w3.y, w3.z); // T3 + rlVertex3f(w4.x, w4.y, w4.z); // | + } // + } + rlEnd(); +} + +// Draw a wired cylinder +// NOTE: It could be also used for pyramid and cone +void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color) +{ + if (sides < 3) sides = 3; + + const float angleStep = 360.0f/sides; + + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < sides; i++) + { + rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusBottom, 0, cosf(DEG2RAD*(i+1)*angleStep)*radiusBottom); + + rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusBottom, 0, cosf(DEG2RAD*(i+1)*angleStep)*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusTop, height, cosf(DEG2RAD*(i+1)*angleStep)*radiusTop); + + rlVertex3f(sinf(DEG2RAD*(i+1)*angleStep)*radiusTop, height, cosf(DEG2RAD*(i+1)*angleStep)*radiusTop); + rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusTop, height, cosf(DEG2RAD*i*angleStep)*radiusTop); + + rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusTop, height, cosf(DEG2RAD*i*angleStep)*radiusTop); + rlVertex3f(sinf(DEG2RAD*i*angleStep)*radiusBottom, 0, cosf(DEG2RAD*i*angleStep)*radiusBottom); + } + rlEnd(); + rlPopMatrix(); +} + +// Draw a wired cylinder with base at startPos and top at endPos +// NOTE: It could be also used for pyramid and cone +void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int slices, Color color) +{ + if (slices < 3) slices = 3; + + Vector3 direction = { endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z }; + if ((direction.x == 0) && (direction.y == 0) && (direction.z == 0)) return; // Security check + + // Construct a basis of the base and the top face: + Vector3 b1 = Vector3Normalize(Vector3Perpendicular(direction)); + Vector3 b2 = Vector3Normalize(Vector3CrossProduct(b1, direction)); + + float baseAngle = (2.0f*PI)/slices; + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < slices; i++) + { + // Compute the four vertices + float s1 = sinf(baseAngle*(i + 0))*startRadius; + float c1 = cosf(baseAngle*(i + 0))*startRadius; + Vector3 w1 = { startPos.x + s1*b1.x + c1*b2.x, startPos.y + s1*b1.y + c1*b2.y, startPos.z + s1*b1.z + c1*b2.z }; + float s2 = sinf(baseAngle*(i + 1))*startRadius; + float c2 = cosf(baseAngle*(i + 1))*startRadius; + Vector3 w2 = { startPos.x + s2*b1.x + c2*b2.x, startPos.y + s2*b1.y + c2*b2.y, startPos.z + s2*b1.z + c2*b2.z }; + float s3 = sinf(baseAngle*(i + 0))*endRadius; + float c3 = cosf(baseAngle*(i + 0))*endRadius; + Vector3 w3 = { endPos.x + s3*b1.x + c3*b2.x, endPos.y + s3*b1.y + c3*b2.y, endPos.z + s3*b1.z + c3*b2.z }; + float s4 = sinf(baseAngle*(i + 1))*endRadius; + float c4 = cosf(baseAngle*(i + 1))*endRadius; + Vector3 w4 = { endPos.x + s4*b1.x + c4*b2.x, endPos.y + s4*b1.y + c4*b2.y, endPos.z + s4*b1.z + c4*b2.z }; + + rlVertex3f(w1.x, w1.y, w1.z); + rlVertex3f(w2.x, w2.y, w2.z); + + rlVertex3f(w1.x, w1.y, w1.z); + rlVertex3f(w3.x, w3.y, w3.z); + + rlVertex3f(w3.x, w3.y, w3.z); + rlVertex3f(w4.x, w4.y, w4.z); + } + rlEnd(); +} + +// Draw a capsule with the center of its sphere caps at startPos and endPos +void DrawCapsule(Vector3 startPos, Vector3 endPos, float radius, int rings, int slices, Color color) +{ + if (slices < 3) slices = 3; + if (rings < 1) rings = 1; + Vector3 direction = { endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z }; + + // draw a sphere if start and end points are the same + bool sphereCase = (direction.x == 0) && (direction.y == 0) && (direction.z == 0); + if (sphereCase) direction = (Vector3){0.0f, 1.0f, 0.0f}; + + // Construct a basis of the base and the caps: + Vector3 b0 = Vector3Normalize(direction); + Vector3 b1 = Vector3Normalize(Vector3Perpendicular(direction)); + Vector3 b2 = Vector3Normalize(Vector3CrossProduct(b1, direction)); + Vector3 capCenter = endPos; + + float baseSliceAngle = (2.0f*PI)/slices; + float baseRingAngle = PI*0.5f/rings; + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // render both caps + for (int c = 0; c < 2; c++) + { + for (int i = 0; i < rings; i++) + { + for (int j = 0; j < slices; j++) + { + // Building up the rings from capCenter in the direction of the 'direction' vector computed earlier + + // Compute the four vertices + float ringSin1 = sinf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 0 )); + float ringCos1 = cosf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 0 )); + Vector3 w1 = (Vector3){ + capCenter.x + (sinf(baseRingAngle*( i + 0 ))*b0.x + ringSin1*b1.x + ringCos1*b2.x)*radius, + capCenter.y + (sinf(baseRingAngle*( i + 0 ))*b0.y + ringSin1*b1.y + ringCos1*b2.y)*radius, + capCenter.z + (sinf(baseRingAngle*( i + 0 ))*b0.z + ringSin1*b1.z + ringCos1*b2.z)*radius + }; + float ringSin2 = sinf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 0 )); + float ringCos2 = cosf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 0 )); + Vector3 w2 = (Vector3){ + capCenter.x + (sinf(baseRingAngle*( i + 0 ))*b0.x + ringSin2*b1.x + ringCos2*b2.x)*radius, + capCenter.y + (sinf(baseRingAngle*( i + 0 ))*b0.y + ringSin2*b1.y + ringCos2*b2.y)*radius, + capCenter.z + (sinf(baseRingAngle*( i + 0 ))*b0.z + ringSin2*b1.z + ringCos2*b2.z)*radius + }; + + float ringSin3 = sinf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 1 )); + float ringCos3 = cosf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 1 )); + Vector3 w3 = (Vector3){ + capCenter.x + (sinf(baseRingAngle*( i + 1 ))*b0.x + ringSin3*b1.x + ringCos3*b2.x)*radius, + capCenter.y + (sinf(baseRingAngle*( i + 1 ))*b0.y + ringSin3*b1.y + ringCos3*b2.y)*radius, + capCenter.z + (sinf(baseRingAngle*( i + 1 ))*b0.z + ringSin3*b1.z + ringCos3*b2.z)*radius + }; + float ringSin4 = sinf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 1 )); + float ringCos4 = cosf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 1 )); + Vector3 w4 = (Vector3){ + capCenter.x + (sinf(baseRingAngle*( i + 1 ))*b0.x + ringSin4*b1.x + ringCos4*b2.x)*radius, + capCenter.y + (sinf(baseRingAngle*( i + 1 ))*b0.y + ringSin4*b1.y + ringCos4*b2.y)*radius, + capCenter.z + (sinf(baseRingAngle*( i + 1 ))*b0.z + ringSin4*b1.z + ringCos4*b2.z)*radius + }; + + // Make sure cap triangle normals are facing outwards + if (c == 0) + { + rlVertex3f(w1.x, w1.y, w1.z); + rlVertex3f(w2.x, w2.y, w2.z); + rlVertex3f(w3.x, w3.y, w3.z); + + rlVertex3f(w2.x, w2.y, w2.z); + rlVertex3f(w4.x, w4.y, w4.z); + rlVertex3f(w3.x, w3.y, w3.z); + } + else + { + rlVertex3f(w1.x, w1.y, w1.z); + rlVertex3f(w3.x, w3.y, w3.z); + rlVertex3f(w2.x, w2.y, w2.z); + + rlVertex3f(w2.x, w2.y, w2.z); + rlVertex3f(w3.x, w3.y, w3.z); + rlVertex3f(w4.x, w4.y, w4.z); + } + } + } + capCenter = startPos; + b0 = Vector3Scale(b0, -1.0f); + } + // render middle + if (!sphereCase) + { + for (int j = 0; j < slices; j++) + { + // compute the four vertices + float ringSin1 = sinf(baseSliceAngle*(j + 0))*radius; + float ringCos1 = cosf(baseSliceAngle*(j + 0))*radius; + Vector3 w1 = { + startPos.x + ringSin1*b1.x + ringCos1*b2.x, + startPos.y + ringSin1*b1.y + ringCos1*b2.y, + startPos.z + ringSin1*b1.z + ringCos1*b2.z + }; + float ringSin2 = sinf(baseSliceAngle*(j + 1))*radius; + float ringCos2 = cosf(baseSliceAngle*(j + 1))*radius; + Vector3 w2 = { + startPos.x + ringSin2*b1.x + ringCos2*b2.x, + startPos.y + ringSin2*b1.y + ringCos2*b2.y, + startPos.z + ringSin2*b1.z + ringCos2*b2.z + }; + + float ringSin3 = sinf(baseSliceAngle*(j + 0))*radius; + float ringCos3 = cosf(baseSliceAngle*(j + 0))*radius; + Vector3 w3 = { + endPos.x + ringSin3*b1.x + ringCos3*b2.x, + endPos.y + ringSin3*b1.y + ringCos3*b2.y, + endPos.z + ringSin3*b1.z + ringCos3*b2.z + }; + float ringSin4 = sinf(baseSliceAngle*(j + 1))*radius; + float ringCos4 = cosf(baseSliceAngle*(j + 1))*radius; + Vector3 w4 = { + endPos.x + ringSin4*b1.x + ringCos4*b2.x, + endPos.y + ringSin4*b1.y + ringCos4*b2.y, + endPos.z + ringSin4*b1.z + ringCos4*b2.z + }; + // w2 x.-----------x startPos + rlVertex3f(w1.x, w1.y, w1.z); // | |\'. T0 / + rlVertex3f(w2.x, w2.y, w2.z); // T1 | \ '. / + rlVertex3f(w3.x, w3.y, w3.z); // | |T \ '. / + // | 2 \ T 'x w1 + rlVertex3f(w2.x, w2.y, w2.z); // | w4 x.---\-1-|---x endPos + rlVertex3f(w4.x, w4.y, w4.z); // T2 '. \ |T3/ + rlVertex3f(w3.x, w3.y, w3.z); // | '. \ | / + // '.\|/ + // 'x w3 + } + } + rlEnd(); +} + +// Draw capsule wires with the center of its sphere caps at startPos and endPos +void DrawCapsuleWires(Vector3 startPos, Vector3 endPos, float radius, int rings, int slices, Color color) +{ + if (slices < 3) slices = 3; + if (rings < 1) rings = 1; + Vector3 direction = { endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z }; + + // draw a sphere if start and end points are the same + bool sphereCase = (direction.x == 0) && (direction.y == 0) && (direction.z == 0); + if (sphereCase) direction = (Vector3){0.0f, 1.0f, 0.0f}; + + // Construct a basis of the base and the caps: + Vector3 b0 = Vector3Normalize(direction); + Vector3 b1 = Vector3Normalize(Vector3Perpendicular(direction)); + Vector3 b2 = Vector3Normalize(Vector3CrossProduct(b1, direction)); + Vector3 capCenter = endPos; + + float baseSliceAngle = (2.0f*PI)/slices; + float baseRingAngle = PI*0.5f/rings; + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // render both caps + for (int c = 0; c < 2; c++) + { + for (int i = 0; i < rings; i++) + { + for (int j = 0; j < slices; j++) + { + // Building up the rings from capCenter in the direction of the 'direction' vector computed earlier + + // Compute the four vertices + float ringSin1 = sinf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 0 )); + float ringCos1 = cosf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 0 )); + Vector3 w1 = (Vector3){ + capCenter.x + (sinf(baseRingAngle*( i + 0 ))*b0.x + ringSin1*b1.x + ringCos1*b2.x)*radius, + capCenter.y + (sinf(baseRingAngle*( i + 0 ))*b0.y + ringSin1*b1.y + ringCos1*b2.y)*radius, + capCenter.z + (sinf(baseRingAngle*( i + 0 ))*b0.z + ringSin1*b1.z + ringCos1*b2.z)*radius + }; + float ringSin2 = sinf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 0 )); + float ringCos2 = cosf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 0 )); + Vector3 w2 = (Vector3){ + capCenter.x + (sinf(baseRingAngle*( i + 0 ))*b0.x + ringSin2*b1.x + ringCos2*b2.x)*radius, + capCenter.y + (sinf(baseRingAngle*( i + 0 ))*b0.y + ringSin2*b1.y + ringCos2*b2.y)*radius, + capCenter.z + (sinf(baseRingAngle*( i + 0 ))*b0.z + ringSin2*b1.z + ringCos2*b2.z)*radius + }; + + float ringSin3 = sinf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 1 )); + float ringCos3 = cosf(baseSliceAngle*(j + 0))*cosf(baseRingAngle*( i + 1 )); + Vector3 w3 = (Vector3){ + capCenter.x + (sinf(baseRingAngle*( i + 1 ))*b0.x + ringSin3*b1.x + ringCos3*b2.x)*radius, + capCenter.y + (sinf(baseRingAngle*( i + 1 ))*b0.y + ringSin3*b1.y + ringCos3*b2.y)*radius, + capCenter.z + (sinf(baseRingAngle*( i + 1 ))*b0.z + ringSin3*b1.z + ringCos3*b2.z)*radius + }; + float ringSin4 = sinf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 1 )); + float ringCos4 = cosf(baseSliceAngle*(j + 1))*cosf(baseRingAngle*( i + 1 )); + Vector3 w4 = (Vector3){ + capCenter.x + (sinf(baseRingAngle*( i + 1 ))*b0.x + ringSin4*b1.x + ringCos4*b2.x)*radius, + capCenter.y + (sinf(baseRingAngle*( i + 1 ))*b0.y + ringSin4*b1.y + ringCos4*b2.y)*radius, + capCenter.z + (sinf(baseRingAngle*( i + 1 ))*b0.z + ringSin4*b1.z + ringCos4*b2.z)*radius + }; + + rlVertex3f(w1.x, w1.y, w1.z); + rlVertex3f(w2.x, w2.y, w2.z); + + rlVertex3f(w2.x, w2.y, w2.z); + rlVertex3f(w3.x, w3.y, w3.z); + + rlVertex3f(w1.x, w1.y, w1.z); + rlVertex3f(w3.x, w3.y, w3.z); + + rlVertex3f(w2.x, w2.y, w2.z); + rlVertex3f(w4.x, w4.y, w4.z); + + rlVertex3f(w3.x, w3.y, w3.z); + rlVertex3f(w4.x, w4.y, w4.z); + } + } + capCenter = startPos; + b0 = Vector3Scale(b0, -1.0f); + } + // render middle + if (!sphereCase) + { + for (int j = 0; j < slices; j++) + { + // compute the four vertices + float ringSin1 = sinf(baseSliceAngle*(j + 0))*radius; + float ringCos1 = cosf(baseSliceAngle*(j + 0))*radius; + Vector3 w1 = { + startPos.x + ringSin1*b1.x + ringCos1*b2.x, + startPos.y + ringSin1*b1.y + ringCos1*b2.y, + startPos.z + ringSin1*b1.z + ringCos1*b2.z + }; + float ringSin2 = sinf(baseSliceAngle*(j + 1))*radius; + float ringCos2 = cosf(baseSliceAngle*(j + 1))*radius; + Vector3 w2 = { + startPos.x + ringSin2*b1.x + ringCos2*b2.x, + startPos.y + ringSin2*b1.y + ringCos2*b2.y, + startPos.z + ringSin2*b1.z + ringCos2*b2.z + }; + + float ringSin3 = sinf(baseSliceAngle*(j + 0))*radius; + float ringCos3 = cosf(baseSliceAngle*(j + 0))*radius; + Vector3 w3 = { + endPos.x + ringSin3*b1.x + ringCos3*b2.x, + endPos.y + ringSin3*b1.y + ringCos3*b2.y, + endPos.z + ringSin3*b1.z + ringCos3*b2.z + }; + float ringSin4 = sinf(baseSliceAngle*(j + 1))*radius; + float ringCos4 = cosf(baseSliceAngle*(j + 1))*radius; + Vector3 w4 = { + endPos.x + ringSin4*b1.x + ringCos4*b2.x, + endPos.y + ringSin4*b1.y + ringCos4*b2.y, + endPos.z + ringSin4*b1.z + ringCos4*b2.z + }; + + rlVertex3f(w1.x, w1.y, w1.z); + rlVertex3f(w3.x, w3.y, w3.z); + + rlVertex3f(w2.x, w2.y, w2.z); + rlVertex3f(w4.x, w4.y, w4.z); + + rlVertex3f(w2.x, w2.y, w2.z); + rlVertex3f(w3.x, w3.y, w3.z); + } + } + rlEnd(); +} + +// Draw a plane +void DrawPlane(Vector3 centerPos, Vector2 size, Color color) +{ + // NOTE: Plane is always created on XZ ground + rlPushMatrix(); + rlTranslatef(centerPos.x, centerPos.y, centerPos.z); + rlScalef(size.x, 1.0f, size.y); + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + rlNormal3f(0.0f, 1.0f, 0.0f); + + rlVertex3f(-0.5f, 0.0f, -0.5f); + rlVertex3f(-0.5f, 0.0f, 0.5f); + rlVertex3f(0.5f, 0.0f, 0.5f); + rlVertex3f(0.5f, 0.0f, -0.5f); + rlEnd(); + rlPopMatrix(); +} + +// Draw a ray line +void DrawRay(Ray ray, Color color) +{ + float scale = 10000; + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex3f(ray.position.x, ray.position.y, ray.position.z); + rlVertex3f(ray.position.x + ray.direction.x*scale, ray.position.y + ray.direction.y*scale, ray.position.z + ray.direction.z*scale); + rlEnd(); +} + +// Draw a grid centered at (0, 0, 0) +void DrawGrid(int slices, float spacing) +{ + int halfSlices = slices/2; + + rlBegin(RL_LINES); + for (int i = -halfSlices; i <= halfSlices; i++) + { + if (i == 0) + { + rlColor3f(0.5f, 0.5f, 0.5f); + } + else + { + rlColor3f(0.75f, 0.75f, 0.75f); + } + + rlVertex3f((float)i*spacing, 0.0f, (float)-halfSlices*spacing); + rlVertex3f((float)i*spacing, 0.0f, (float)halfSlices*spacing); + + rlVertex3f((float)-halfSlices*spacing, 0.0f, (float)i*spacing); + rlVertex3f((float)halfSlices*spacing, 0.0f, (float)i*spacing); + } + rlEnd(); +} + +// Load model from files (mesh and material) +Model LoadModel(const char *fileName) +{ + Model model = { 0 }; + +#if SUPPORT_FILEFORMAT_OBJ + if (IsFileExtension(fileName, ".obj")) model = LoadOBJ(fileName); +#endif +#if SUPPORT_FILEFORMAT_IQM + if (IsFileExtension(fileName, ".iqm")) model = LoadIQM(fileName); +#endif +#if SUPPORT_FILEFORMAT_GLTF + if (IsFileExtension(fileName, ".gltf") || IsFileExtension(fileName, ".glb")) model = LoadGLTF(fileName); +#endif +#if SUPPORT_FILEFORMAT_VOX + if (IsFileExtension(fileName, ".vox")) model = LoadVOX(fileName); +#endif +#if SUPPORT_FILEFORMAT_M3D + if (IsFileExtension(fileName, ".m3d")) model = LoadM3D(fileName); +#endif + + // Make sure model transform is set to identity matrix! + model.transform = MatrixIdentity(); + + if ((model.meshCount != 0) && (model.meshes != NULL)) + { + // Upload vertex data to GPU (static meshes) + for (int i = 0; i < model.meshCount; i++) UploadMesh(&model.meshes[i], false); + } + else TRACELOG(LOG_WARNING, "MESH: [%s] Failed to load model mesh(es) data", fileName); + + if (model.materialCount == 0) + { + TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to load model material data, default to white material", fileName); + + model.materialCount = 1; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); + + if (model.meshMaterial == NULL) model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + } + + return model; +} + +// Load model from generated mesh +// WARNING: A shallow copy of mesh is generated, passed by value, +// as long as struct contains pointers to data and some values, get a copy +// of mesh pointing to same data as original version... be careful! +Model LoadModelFromMesh(Mesh mesh) +{ + Model model = { 0 }; + + model.transform = MatrixIdentity(); + + model.meshCount = 1; + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshes[0] = mesh; + + model.materialCount = 1; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); + + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + model.meshMaterial[0] = 0; // First material index + + return model; +} + +// Check if model is valid (loaded in GPU, VAO/VBOs) +bool IsModelValid(Model model) +{ + bool result = false; + + if ((model.meshes != NULL) && // Validate model contains some mesh + (model.materials != NULL) && // Validate model contains some material (at least default one) + (model.meshMaterial != NULL) && // Validate mesh-material linkage + (model.meshCount > 0) && // Validate mesh count + (model.materialCount > 0)) result = true; // Validate material count + + // NOTE: Many elements could be validated from a model, including every model mesh VAO/VBOs + // but some VBOs could not be used, it depends on Mesh vertex data + for (int i = 0; i < model.meshCount; i++) + { + if ((model.meshes[i].vertices != NULL) && (model.meshes[i].vboId[0] == 0)) { result = false; break; } // Vertex position buffer not uploaded to GPU + if ((model.meshes[i].texcoords != NULL) && (model.meshes[i].vboId[1] == 0)) { result = false; break; } // Vertex textcoords buffer not uploaded to GPU + if ((model.meshes[i].normals != NULL) && (model.meshes[i].vboId[2] == 0)) { result = false; break; } // Vertex normals buffer not uploaded to GPU + if ((model.meshes[i].colors != NULL) && (model.meshes[i].vboId[3] == 0)) { result = false; break; } // Vertex colors buffer not uploaded to GPU + if ((model.meshes[i].tangents != NULL) && (model.meshes[i].vboId[4] == 0)) { result = false; break; } // Vertex tangents buffer not uploaded to GPU + if ((model.meshes[i].texcoords2 != NULL) && (model.meshes[i].vboId[5] == 0)) { result = false; break; } // Vertex texcoords2 buffer not uploaded to GPU + if ((model.meshes[i].indices != NULL) && (model.meshes[i].vboId[6] == 0)) { result = false; break; } // Vertex indices buffer not uploaded to GPU +#if SUPPORT_GPU_SKINNING + if ((model.meshes[i].boneIndices != NULL) && (model.meshes[i].vboId[7] == 0)) { result = false; break; } // Vertex boneIndices buffer not uploaded to GPU + if ((model.meshes[i].boneWeights != NULL) && (model.meshes[i].vboId[8] == 0)) { result = false; break; } // Vertex boneWeights buffer not uploaded to GPU +#endif + // NOTE: Some OpenGL versions do not support VAO, so avoid below check + //if (model.meshes[i].vaoId == 0) { result = false; break } + } + + return result; +} + +// Unload model (meshes/materials) from memory (RAM and/or VRAM) +// NOTE: This function takes care of all model elements, for a detailed control +// over them, use UnloadMesh() and UnloadMaterial() +void UnloadModel(Model model) +{ + // Unload meshes + for (int i = 0; i < model.meshCount; i++) UnloadMesh(model.meshes[i]); + + // Unload materials maps + // NOTE: As the user could be sharing shaders and textures between models, + // don't unload the material but free its maps, + // the user is responsible for freeing models shaders and textures + for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps); + + // Unload arrays + RL_FREE(model.meshes); + RL_FREE(model.materials); + RL_FREE(model.meshMaterial); + + // Unload animation data + RL_FREE(model.skeleton.bones); + RL_FREE(model.skeleton.bindPose); + RL_FREE(model.currentPose); + RL_FREE(model.boneMatrices); + + TRACELOG(LOG_INFO, "MODEL: Unloaded model (and meshes) from RAM and VRAM"); +} + +// Compute model bounding box limits (considers all meshes) +BoundingBox GetModelBoundingBox(Model model) +{ + BoundingBox bounds = { 0 }; + + if (model.meshCount > 0) + { + Vector3 temp = { 0 }; + bounds = GetMeshBoundingBox(model.meshes[0]); + + for (int i = 1; i < model.meshCount; i++) + { + BoundingBox tempBounds = GetMeshBoundingBox(model.meshes[i]); + + temp.x = (bounds.min.x < tempBounds.min.x)? bounds.min.x : tempBounds.min.x; + temp.y = (bounds.min.y < tempBounds.min.y)? bounds.min.y : tempBounds.min.y; + temp.z = (bounds.min.z < tempBounds.min.z)? bounds.min.z : tempBounds.min.z; + bounds.min = temp; + + temp.x = (bounds.max.x > tempBounds.max.x)? bounds.max.x : tempBounds.max.x; + temp.y = (bounds.max.y > tempBounds.max.y)? bounds.max.y : tempBounds.max.y; + temp.z = (bounds.max.z > tempBounds.max.z)? bounds.max.z : tempBounds.max.z; + bounds.max = temp; + } + } + + // Apply model.transform to bounding box + // WARNING: Current BoundingBox structure design does not support rotation transformations, + // in those cases is up to the user to calculate the proper box bounds (8 vertices transformed) + bounds.min = Vector3Transform(bounds.min, model.transform); + bounds.max = Vector3Transform(bounds.max, model.transform); + + return bounds; +} + +// Upload vertex data into a VAO (if supported) and VBO +void UploadMesh(Mesh *mesh, bool dynamic) +{ + if (mesh->vaoId > 0) + { + // Check if mesh has already been loaded in GPU + TRACELOG(LOG_WARNING, "VAO: [ID %i] Trying to re-load an already loaded mesh", mesh->vaoId); + return; + } + + mesh->vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); + + mesh->vaoId = 0; // Vertex Array Object + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION] = 0; // Vertex buffer: positions + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD] = 0; // Vertex buffer: texcoords + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL] = 0; // Vertex buffer: normals + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] = 0; // Vertex buffer: colors + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT] = 0; // Vertex buffer: tangents + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2] = 0; // Vertex buffer: texcoords2 + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = 0; // Vertex buffer: indices +#if SUPPORT_GPU_SKINNING + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES] = 0; // Vertex buffer: boneIndices + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS] = 0; // Vertex buffer: boneWeights +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mesh->vaoId = rlLoadVertexArray(); + + rlEnableVertexArray(mesh->vaoId); + + // NOTE: Vertex attributes must be uploaded considering default locations points and available vertex data + + // Enable vertex attributes: position (shader-location = 0) + void *vertices = (mesh->animVertices != NULL)? mesh->animVertices : mesh->vertices; + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION] = rlLoadVertexBuffer(vertices, mesh->vertexCount*3*sizeof(float), dynamic); + rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + + // Enable vertex attributes: texcoords (shader-location = 1) + + if (mesh->texcoords != NULL) + { + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD] = rlLoadVertexBuffer(mesh->texcoords, mesh->vertexCount*2*sizeof(float), dynamic); + rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + } + else + { + float value[2] = { 0.0f, 0.0f }; + rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, value, SHADER_ATTRIB_VEC2, 2); + rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + } + // WARNING: When setting default vertex attribute values, the values for each generic vertex attribute + // is part of current state, and it is maintained even if a different program object is used + + if (mesh->normals != NULL) + { + // Enable vertex attributes: normals (shader-location = 2) + void *normals = (mesh->animNormals != NULL)? mesh->animNormals : mesh->normals; + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL] = rlLoadVertexBuffer(normals, mesh->vertexCount*3*sizeof(float), dynamic); + rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + } + else + { + // Default vertex attribute: normal + // WARNING: Default value provided to shader if location available + float value[3] = { 0.0f, 0.0f, 1.0f }; + rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, value, SHADER_ATTRIB_VEC3, 3); + rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + } + + if (mesh->colors != NULL) + { + // Enable vertex attribute: color (shader-location = 3) + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] = rlLoadVertexBuffer(mesh->colors, mesh->vertexCount*4*sizeof(unsigned char), dynamic); + rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, 4, RL_UNSIGNED_BYTE, 1, 0, 0); + rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR); + } + else + { + // Default vertex attribute: color + // WARNING: Default value provided to shader if location available + float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; // WHITE + rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR); + } + + if (mesh->tangents != NULL) + { + // Enable vertex attribute: tangent (shader-location = 4) + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), dynamic); + rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT); + } + else + { + // Default vertex attribute: tangent + // WARNING: Default value provided to shader if location available + float value[4] = { 1.0f, 0.0f, 0.0f, 1.0f }; + rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT); + } + + if (mesh->texcoords2 != NULL) + { + // Enable vertex attribute: texcoord2 (shader-location = 5) + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2] = rlLoadVertexBuffer(mesh->texcoords2, mesh->vertexCount*2*sizeof(float), dynamic); + rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2); + } + else + { + // Default vertex attribute: texcoord2 + // WARNING: Default value provided to shader if location available + float value[2] = { 0.0f, 0.0f }; + rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, value, SHADER_ATTRIB_VEC2, 2); + rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2); + } + +#if SUPPORT_GPU_SKINNING + if (mesh->boneIndices != NULL) + { + // Enable vertex attribute: boneIndices (shader-location = 7) + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES] = rlLoadVertexBuffer(mesh->boneIndices, mesh->vertexCount*4*sizeof(unsigned char), dynamic); + rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES, 4, RL_UNSIGNED_BYTE, 0, 0, 0); + rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES); + } + else + { + // Default vertex attribute: boneIndices + // WARNING: Default value provided to shader if location available + float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES, value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES); + } + + if (mesh->boneWeights != NULL) + { + // Enable vertex attribute: boneWeights (shader-location = 8) + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS] = rlLoadVertexBuffer(mesh->boneWeights, mesh->vertexCount*4*sizeof(float), dynamic); + rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS); + } + else + { + // Default vertex attribute: boneWeights + // WARNING: Default value provided to shader if location available + float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, value, SHADER_ATTRIB_VEC4, 2); + rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS); + } +#endif + + if (mesh->indices != NULL) + { + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = rlLoadVertexBufferElement(mesh->indices, mesh->triangleCount*3*sizeof(unsigned short), dynamic); + } + + if (mesh->vaoId > 0) TRACELOG(LOG_INFO, "VAO: [ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId); + else TRACELOG(LOG_INFO, "VBO: Mesh uploaded successfully to VRAM (GPU)"); + + rlDisableVertexArray(); +#endif +} + +// Update mesh vertex data in GPU for a specific buffer index +void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset) +{ + rlUpdateVertexBuffer(mesh.vboId[index], data, dataSize, offset); +} + +// Draw a 3d mesh with material and transform +void DrawMesh(Mesh mesh, Material material, Matrix transform) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_SOFTWARE) + #define GL_VERTEX_ARRAY 0x8074 + #define GL_NORMAL_ARRAY 0x8075 + #define GL_COLOR_ARRAY 0x8076 + #define GL_TEXTURE_COORD_ARRAY 0x8078 + + if ((mesh.texcoords != NULL) && (material.maps[MATERIAL_MAP_DIFFUSE].texture.id > 0)) rlEnableTexture(material.maps[MATERIAL_MAP_DIFFUSE].texture.id); + + if (mesh.animVertices != NULL) rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.animVertices); + else rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.vertices); + + if (mesh.texcoords) rlEnableStatePointer(GL_TEXTURE_COORD_ARRAY, mesh.texcoords); + + if (mesh.animNormals != NULL) rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.animNormals); + else if (mesh.normals != NULL) rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.normals); + + if (mesh.colors != NULL) rlEnableStatePointer(GL_COLOR_ARRAY, mesh.colors); + + rlPushMatrix(); + rlMultMatrixf(MatrixToFloat(transform)); + rlColor4ub(material.maps[MATERIAL_MAP_DIFFUSE].color.r, + material.maps[MATERIAL_MAP_DIFFUSE].color.g, + material.maps[MATERIAL_MAP_DIFFUSE].color.b, + material.maps[MATERIAL_MAP_DIFFUSE].color.a); + + if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, mesh.indices); + else rlDrawVertexArray(0, mesh.vertexCount); + rlPopMatrix(); + + rlDisableStatePointer(GL_VERTEX_ARRAY); + rlDisableStatePointer(GL_TEXTURE_COORD_ARRAY); + rlDisableStatePointer(GL_NORMAL_ARRAY); + rlDisableStatePointer(GL_COLOR_ARRAY); + + rlDisableTexture(); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Bind shader program + rlEnableShader(material.shader.id); + + if (material.shader.locs == NULL) return; + + // Send required data to shader (matrices, values) + //----------------------------------------------------- + // Upload to shader material.colDiffuse + if (material.shader.locs[SHADER_LOC_COLOR_DIFFUSE] != -1) + { + float values[4] = { + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.r/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.g/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.b/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.a/255.0f + }; + + rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_DIFFUSE], values, SHADER_UNIFORM_VEC4, 1); + } + + // Upload to shader material.colSpecular (if location available) + if (material.shader.locs[SHADER_LOC_COLOR_SPECULAR] != -1) + { + float values[4] = { + (float)material.maps[MATERIAL_MAP_SPECULAR].color.r/255.0f, + (float)material.maps[MATERIAL_MAP_SPECULAR].color.g/255.0f, + (float)material.maps[MATERIAL_MAP_SPECULAR].color.b/255.0f, + (float)material.maps[MATERIAL_MAP_SPECULAR].color.a/255.0f + }; + + rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_SPECULAR], values, SHADER_UNIFORM_VEC4, 1); + } + + // Get a copy of current matrices to work with, + // in case stereo render is required, and they need to be modified + // NOTE: At this point the modelview matrix contains the view matrix (camera) + // That's because BeginMode3D() sets it and there is no model-drawing function + // that modifies it, all use rlPushMatrix() and rlPopMatrix() + Matrix matModel = MatrixIdentity(); + Matrix matView = rlGetMatrixModelview(); + Matrix matModelView = MatrixIdentity(); + Matrix matProjection = rlGetMatrixProjection(); + + // Upload view and projection matrices (if locations available) + if (material.shader.locs[SHADER_LOC_MATRIX_VIEW] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_VIEW], matView); + if (material.shader.locs[SHADER_LOC_MATRIX_PROJECTION] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_PROJECTION], matProjection); + + // Accumulate several model transformations: + // transform: model transformation provided (includes DrawModel() params combined with model.transform) + // rlGetMatrixTransform(): rlgl internal transform matrix due to push/pop matrix stack + matModel = MatrixMultiply(transform, rlGetMatrixTransform()); + + // Model transformation matrix is sent to shader uniform location: SHADER_LOC_MATRIX_MODEL + if (material.shader.locs[SHADER_LOC_MATRIX_MODEL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MODEL], matModel); + + // Get model-view matrix + matModelView = MatrixMultiply(matModel, matView); + + // Upload model normal matrix (if locations available) + if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel))); + //----------------------------------------------------- + + // Bind active texture maps (if available) + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id > 0) + { + // Select current shader texture slot + rlActiveTextureSlot(i); + + // Enable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlEnableTextureCubemap(material.maps[i].texture.id); + else rlEnableTexture(material.maps[i].texture.id); + + rlSetUniform(material.shader.locs[SHADER_LOC_MAP_DIFFUSE + i], &i, SHADER_UNIFORM_INT, 1); + } + } + + // Try binding vertex array objects (VAO) or use VBOs if not possible + // WARNING: UploadMesh() enables all vertex attributes available in mesh and sets default attribute values + // for shader expected vertex attributes that are not provided by the mesh (i.e. colors) + // This could be a dangerous approach because different meshes with different shaders can enable/disable some attributes + if (!rlEnableVertexArray(mesh.vaoId)) + { + // Bind mesh VBO data: vertex position (shader-location = 0) + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]); + + // Bind mesh VBO data: vertex texcoords (shader-location = 1) + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); + + if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1) + { + // Bind mesh VBO data: vertex normals (shader-location = 2) + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]); + } + + // Bind mesh VBO data: vertex colors (shader-location = 3, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1) + { + if (mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] != 0) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + } + else + { + // Set default value for defined vertex attribute in shader but not provided by mesh + // WARNING: It could result in GPU undefined behaviour + float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + } + } + + // Bind mesh VBO data: vertex tangents (shader-location = 4, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]); + } + + // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); + } + +#if SUPPORT_GPU_SKINNING + // Bind mesh VBO data: vertex bone ids (shader-location = 6, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_BONEIDS] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS], 4, RL_UNSIGNED_BYTE, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS]); + } + + // Bind mesh VBO data: vertex bone weights (shader-location = 7, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS], 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS]); + } +#endif + + if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES]); + } + + int eyeCount = 1; + if (rlIsStereoRenderEnabled()) eyeCount = 2; + + for (int eye = 0; eye < eyeCount; eye++) + { + // Calculate model-view-projection matrix (MVP) + Matrix matModelViewProjection = MatrixIdentity(); + if (eyeCount == 1) matModelViewProjection = MatrixMultiply(matModelView, matProjection); + else + { + // Setup current eye viewport (half screen width) + rlViewport(eye*rlGetFramebufferWidth()/2, 0, rlGetFramebufferWidth()/2, rlGetFramebufferHeight()); + matModelViewProjection = MatrixMultiply(MatrixMultiply(matModelView, rlGetMatrixViewOffsetStereo(eye)), rlGetMatrixProjectionStereo(eye)); + } + + // Send combined model-view-projection matrix to shader + rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MVP], matModelViewProjection); + + // Draw mesh + if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, 0); + else rlDrawVertexArray(0, mesh.vertexCount); + } + + // Unbind all bound texture maps + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id > 0) + { + // Select current shader texture slot + rlActiveTextureSlot(i); + + // Disable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap(); + else rlDisableTexture(); + } + } + + // Disable all possible vertex array objects (or VBOs) + rlDisableVertexArray(); + rlDisableVertexBuffer(); + rlDisableVertexBufferElement(); + + // Disable shader program + rlDisableShader(); + + // Restore rlgl internal modelview and projection matrices + rlSetMatrixModelview(matView); + rlSetMatrixProjection(matProjection); +#endif +} + +// Draw multiple mesh instances with material and different transforms +void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Instancing required variables + float16 *instanceTransform = NULL; + unsigned int instancesVboId = 0; + + // Bind shader program + rlEnableShader(material.shader.id); + + // Send required data to shader (matrices, values) + //----------------------------------------------------- + // Upload to shader material.colDiffuse + if (material.shader.locs[SHADER_LOC_COLOR_DIFFUSE] != -1) + { + float values[4] = { + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.r/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.g/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.b/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.a/255.0f + }; + + rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_DIFFUSE], values, SHADER_UNIFORM_VEC4, 1); + } + + // Upload to shader material.colSpecular (if location available) + if (material.shader.locs[SHADER_LOC_COLOR_SPECULAR] != -1) + { + float values[4] = { + (float)material.maps[MATERIAL_MAP_SPECULAR].color.r/255.0f, + (float)material.maps[MATERIAL_MAP_SPECULAR].color.g/255.0f, + (float)material.maps[MATERIAL_MAP_SPECULAR].color.b/255.0f, + (float)material.maps[MATERIAL_MAP_SPECULAR].color.a/255.0f + }; + + rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_SPECULAR], values, SHADER_UNIFORM_VEC4, 1); + } + + // Get a copy of current matrices to work with, + // in case stereo render is required, and they need to be modified + // NOTE: At this point the modelview matrix contains the view matrix (camera) + // That's because BeginMode3D() sets it and there is no model-drawing function + // that modifies it, all use rlPushMatrix() and rlPopMatrix() + Matrix matModel = MatrixIdentity(); + Matrix matView = rlGetMatrixModelview(); + Matrix matModelView = MatrixIdentity(); + Matrix matProjection = rlGetMatrixProjection(); + + // Upload view and projection matrices (if locations available) + if (material.shader.locs[SHADER_LOC_MATRIX_VIEW] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_VIEW], matView); + if (material.shader.locs[SHADER_LOC_MATRIX_PROJECTION] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_PROJECTION], matProjection); + + // Create instances buffer + instanceTransform = (float16 *)RL_CALLOC(instances, sizeof(float16)); + + // Fill buffer with instances transformations as float16 arrays + for (int i = 0; i < instances; i++) instanceTransform[i] = MatrixToFloatV(transforms[i]); + + // Enable mesh VAO to attach new buffer + rlEnableVertexArray(mesh.vaoId); + + // This could alternatively use a static VBO and either glMapBuffer() or glBufferSubData() + // It isn't clear which would be reliably faster in all cases and on all platforms, + // anecdotally glMapBuffer() seems quite slow (syncs) while glBufferSubData() seems + // no faster, since all the transform matrices are transferred anyway + instancesVboId = rlLoadVertexBuffer(instanceTransform, instances*sizeof(float16), false); + + // Instances transformation matrices are sent to shader attribute location: SHADER_LOC_VERTEX_INSTANCETRANSFORM + if (material.shader.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] != -1) + { + for (unsigned int i = 0; i < 4; i++) + { + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] + i); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] + i, 4, RL_FLOAT, 0, sizeof(Matrix), i*sizeof(Vector4)); + rlSetVertexAttributeDivisor(material.shader.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] + i, 1); + } + } + + rlDisableVertexBuffer(); + rlDisableVertexArray(); + + // Accumulate internal matrix transform (push/pop) and view matrix + // NOTE: In this case, model instance transformation must be computed in the shader + matModelView = MatrixMultiply(rlGetMatrixTransform(), matView); + + // Upload model normal matrix (if locations available) + if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel))); + //----------------------------------------------------- + + // Bind active texture maps (if available) + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id > 0) + { + // Select current shader texture slot + rlActiveTextureSlot(i); + + // Enable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlEnableTextureCubemap(material.maps[i].texture.id); + else rlEnableTexture(material.maps[i].texture.id); + + rlSetUniform(material.shader.locs[SHADER_LOC_MAP_DIFFUSE + i], &i, SHADER_UNIFORM_INT, 1); + } + } + + // Try binding vertex array objects (VAO) + // or use VBOs if not possible + if (!rlEnableVertexArray(mesh.vaoId)) + { + // Bind mesh VBO data: vertex position (shader-location = 0) + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]); + + // Bind mesh VBO data: vertex texcoords (shader-location = 1) + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); + + if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1) + { + // Bind mesh VBO data: vertex normals (shader-location = 2) + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]); + } + + // Bind mesh VBO data: vertex colors (shader-location = 3, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1) + { + if (mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] != 0) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + } + else + { + // Set default value for unused attribute + // NOTE: Required when using default shader and no VAO support + float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + } + } + + // Bind mesh VBO data: vertex tangents (shader-location = 4, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]); + } + + // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); + } + +#if SUPPORT_GPU_SKINNING + // Bind mesh VBO data: vertex bone ids (shader-location = 6, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_BONEIDS] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS], 4, RL_UNSIGNED_BYTE, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS]); + } + + // Bind mesh VBO data: vertex bone weights (shader-location = 7, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS], 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS]); + } +#endif + + if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES]); + } + + int eyeCount = 1; + if (rlIsStereoRenderEnabled()) eyeCount = 2; + + for (int eye = 0; eye < eyeCount; eye++) + { + // Calculate model-view-projection matrix (MVP) + Matrix matModelViewProjection = MatrixIdentity(); + if (eyeCount == 1) matModelViewProjection = MatrixMultiply(matModelView, matProjection); + else + { + // Setup current eye viewport (half screen width) + rlViewport(eye*rlGetFramebufferWidth()/2, 0, rlGetFramebufferWidth()/2, rlGetFramebufferHeight()); + matModelViewProjection = MatrixMultiply(MatrixMultiply(matModelView, rlGetMatrixViewOffsetStereo(eye)), rlGetMatrixProjectionStereo(eye)); + } + + // Send combined model-view-projection matrix to shader + rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MVP], matModelViewProjection); + + // Draw mesh instanced + if (mesh.indices != NULL) rlDrawVertexArrayElementsInstanced(0, mesh.triangleCount*3, 0, instances); + else rlDrawVertexArrayInstanced(0, mesh.vertexCount, instances); + } + + // Unbind all bound texture maps + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id > 0) + { + // Select current shader texture slot + rlActiveTextureSlot(i); + + // Disable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap(); + else rlDisableTexture(); + } + } + + // Disable all possible vertex array objects (or VBOs) + rlDisableVertexArray(); + rlDisableVertexBuffer(); + rlDisableVertexBufferElement(); + + // Disable shader program + rlDisableShader(); + + // Remove instance transforms buffer + rlUnloadVertexBuffer(instancesVboId); + RL_FREE(instanceTransform); +#endif +} + +// Unload mesh from memory (RAM and VRAM) +void UnloadMesh(Mesh mesh) +{ + // Unload rlgl mesh vboId data + rlUnloadVertexArray(mesh.vaoId); + + if (mesh.vboId != NULL) for (int i = 0; i < MAX_MESH_VERTEX_BUFFERS; i++) rlUnloadVertexBuffer(mesh.vboId[i]); + RL_FREE(mesh.vboId); + + // Unload mesh vertex buffers + RL_FREE(mesh.vertices); + RL_FREE(mesh.texcoords); + RL_FREE(mesh.normals); + RL_FREE(mesh.colors); + RL_FREE(mesh.tangents); + RL_FREE(mesh.texcoords2); + RL_FREE(mesh.indices); + + // Unload mesh skin animation data + RL_FREE(mesh.boneWeights); + RL_FREE(mesh.boneIndices); + + // Unload mesh runtime CPU skinning data + RL_FREE(mesh.animVertices); + RL_FREE(mesh.animNormals); +} + +// Export mesh data to file +bool ExportMesh(Mesh mesh, const char *fileName) +{ + bool result = false; + + if (IsFileExtension(fileName, ".obj")) + { + // Estimated data size, it should be enough... + int vc = mesh.vertexCount; + int dataSize = vc*(int)strlen("v -0000.000000f -0000.000000f -0000.000000f\n") + + vc*(int)strlen("vt -0.000000f -0.000000f\n") + + vc*(int)strlen("vn -0.0000f -0.0000f -0.0000f\n") + + mesh.triangleCount*snprintf(NULL, 0, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", vc, vc, vc, vc, vc, vc, vc, vc, vc); + + // NOTE: Text data buffer size is estimated considering mesh data size + char *txtData = (char *)RL_CALLOC(dataSize + 1000, sizeof(char)); + + int byteCount = 0; + byteCount += sprintf(txtData + byteCount, "# //////////////////////////////////////////////////////////////////////////////////\n"); + byteCount += sprintf(txtData + byteCount, "# // //\n"); + byteCount += sprintf(txtData + byteCount, "# // rMeshOBJ exporter v1.0 - Mesh exported as triangle faces and not optimized //\n"); + byteCount += sprintf(txtData + byteCount, "# // //\n"); + byteCount += sprintf(txtData + byteCount, "# // more info and bugs-report: github.com/raysan5/raylib //\n"); + byteCount += sprintf(txtData + byteCount, "# // feedback and support: ray[at]raylib.com //\n"); + byteCount += sprintf(txtData + byteCount, "# // //\n"); + byteCount += sprintf(txtData + byteCount, "# // Copyright (c) 2018-2026 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "# // //\n"); + byteCount += sprintf(txtData + byteCount, "# //////////////////////////////////////////////////////////////////////////////////\n\n"); + byteCount += sprintf(txtData + byteCount, "# Vertex Count: %i\n", mesh.vertexCount); + byteCount += sprintf(txtData + byteCount, "# Triangle Count: %i\n\n", mesh.triangleCount); + + byteCount += sprintf(txtData + byteCount, "g mesh\n"); + + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3) + { + byteCount += sprintf(txtData + byteCount, "v %.6f %.6f %.6f\n", mesh.vertices[v], mesh.vertices[v + 1], mesh.vertices[v + 2]); + } + + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 2) + { + byteCount += sprintf(txtData + byteCount, "vt %.6f %.6f\n", mesh.texcoords[v], mesh.texcoords[v + 1]); + } + + for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3) + { + byteCount += sprintf(txtData + byteCount, "vn %.4f %.4f %.4f\n", mesh.normals[v], mesh.normals[v + 1], mesh.normals[v + 2]); + } + + if (mesh.indices != NULL) + { + for (int i = 0, v = 0; i < mesh.triangleCount; i++, v += 3) + { + byteCount += sprintf(txtData + byteCount, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", + mesh.indices[v] + 1, mesh.indices[v] + 1, mesh.indices[v] + 1, + mesh.indices[v + 1] + 1, mesh.indices[v + 1] + 1, mesh.indices[v + 1] + 1, + mesh.indices[v + 2] + 1, mesh.indices[v + 2] + 1, mesh.indices[v + 2] + 1); + } + } + else + { + for (int i = 0, v = 1; i < mesh.triangleCount; i++, v += 3) + { + byteCount += sprintf(txtData + byteCount, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", v, v, v, v + 1, v + 1, v + 1, v + 2, v + 2, v + 2); + } + } + + // NOTE: Text data length exported is determined by '\0' (NULL) character + result = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + } + else if (IsFileExtension(fileName, ".gltf")) // Or .glb + { + // TODO: Implement gltf/glb support + /* + cgltf_size expected = cgltf_write(options, NULL, 0, data); + char *buffer = (char *)RL_CALLOC(expected, 0); + cgltf_size actual = cgltf_write(options, buffer, expected, data); + + // NOTE: cgltf_write() includes a NULL terminator that should be ommited in case of a .glb + if (options->type == cgltf_file_type_glb) cgltf_write_glb(file, buffer, actual - 1, data->bin, data->bin_size); + else SaveFileText(fileName, buffer); // Write a plain JSON file + */ + } + else if (IsFileExtension(fileName, ".raw")) + { + // TODO: Support additional file formats to export mesh vertex data + } + + return result; +} + +// Export mesh as code file (.h) defining multiple arrays of vertex attributes +bool ExportMeshAsCode(Mesh mesh, const char *fileName) +{ + bool result = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + // NOTE: Text data buffer size is fixed to 64MB + char *txtData = (char *)RL_CALLOC(64*1024*1024, sizeof(char)); // 64 MB + + int byteCount = 0; + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// MeshAsCode exporter v1.0 - Mesh vertex data exported as arrays //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2023 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path and convert variable name to uppercase + char varFileName[256] = { 0 }; + snprintf(varFileName, 256, "%s", GetFileNameWithoutExt(fileName)); // NOTE: Using function provided by [rcore] module + for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } + + // Add image information + byteCount += sprintf(txtData + byteCount, "// Mesh basic information\n"); + byteCount += sprintf(txtData + byteCount, "#define %s_VERTEX_COUNT %i\n", varFileName, mesh.vertexCount); + byteCount += sprintf(txtData + byteCount, "#define %s_TRIANGLE_COUNT %i\n\n", varFileName, mesh.triangleCount); + + // Define vertex attributes data as separate arrays + //----------------------------------------------------------------------------------------- + if (mesh.vertices != NULL) // Vertex position (XYZ - 3 components per vertex - float) + { + byteCount += sprintf(txtData + byteCount, "static float %s_VERTEX_DATA[%i] = { ", varFileName, mesh.vertexCount*3); + for (int i = 0; i < mesh.vertexCount*3 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.3ff,\n" : "%.3ff, "), mesh.vertices[i]); + byteCount += sprintf(txtData + byteCount, "%.3ff };\n\n", mesh.vertices[mesh.vertexCount*3 - 1]); + } + + if (mesh.texcoords != NULL) // Vertex texture coordinates (UV - 2 components per vertex - float) + { + byteCount += sprintf(txtData + byteCount, "static float %s_TEXCOORD_DATA[%i] = { ", varFileName, mesh.vertexCount*2); + for (int i = 0; i < mesh.vertexCount*2 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.3ff,\n" : "%.3ff, "), mesh.texcoords[i]); + byteCount += sprintf(txtData + byteCount, "%.3ff };\n\n", mesh.texcoords[mesh.vertexCount*2 - 1]); + } + + if (mesh.texcoords2 != NULL) // Vertex texture coordinates (UV - 2 components per vertex - float) + { + byteCount += sprintf(txtData + byteCount, "static float %s_TEXCOORD2_DATA[%i] = { ", varFileName, mesh.vertexCount*2); + for (int i = 0; i < mesh.vertexCount*2 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.3ff,\n" : "%.3ff, "), mesh.texcoords2[i]); + byteCount += sprintf(txtData + byteCount, "%.3ff };\n\n", mesh.texcoords2[mesh.vertexCount*2 - 1]); + } + + if (mesh.normals != NULL) // Vertex normals (XYZ - 3 components per vertex - float) + { + byteCount += sprintf(txtData + byteCount, "static float %s_NORMAL_DATA[%i] = { ", varFileName, mesh.vertexCount*3); + for (int i = 0; i < mesh.vertexCount*3 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.3ff,\n" : "%.3ff, "), mesh.normals[i]); + byteCount += sprintf(txtData + byteCount, "%.3ff };\n\n", mesh.normals[mesh.vertexCount*3 - 1]); + } + + if (mesh.tangents != NULL) // Vertex tangents (XYZW - 4 components per vertex - float) + { + byteCount += sprintf(txtData + byteCount, "static float %s_TANGENT_DATA[%i] = { ", varFileName, mesh.vertexCount*4); + for (int i = 0; i < mesh.vertexCount*4 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.3ff,\n" : "%.3ff, "), mesh.tangents[i]); + byteCount += sprintf(txtData + byteCount, "%.3ff };\n\n", mesh.tangents[mesh.vertexCount*4 - 1]); + } + + if (mesh.colors != NULL) // Vertex colors (RGBA - 4 components per vertex - unsigned char) + { + byteCount += sprintf(txtData + byteCount, "static unsigned char %s_COLOR_DATA[%i] = { ", varFileName, mesh.vertexCount*4); + for (int i = 0; i < mesh.vertexCount*4 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), mesh.colors[i]); + byteCount += sprintf(txtData + byteCount, "0x%x };\n\n", mesh.colors[mesh.vertexCount*4 - 1]); + } + + if (mesh.indices != NULL) // Vertex indices (3 index per triangle - unsigned short) + { + byteCount += sprintf(txtData + byteCount, "static unsigned short %s_INDEX_DATA[%i] = { ", varFileName, mesh.triangleCount*3); + for (int i = 0; i < mesh.triangleCount*3 - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%i,\n" : "%i, "), mesh.indices[i]); + byteCount += sprintf(txtData + byteCount, "%i };\n", mesh.indices[mesh.triangleCount*3 - 1]); + } + //----------------------------------------------------------------------------------------- + + // NOTE: Text data size exported is determined by '\0' (NULL) character + result = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + //if (result != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image as code exported successfully", fileName); + //else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image as code", fileName); + + return result; +} + +#if SUPPORT_FILEFORMAT_OBJ || SUPPORT_FILEFORMAT_MTL +// Process obj materials +static void ProcessMaterialsOBJ(Material *materials, tinyobj_material_t *mats, int materialCount) +{ + // Init model mats + for (int m = 0; m < materialCount; m++) + { + // Init material to default + // NOTE: Uses default shader, which only supports MATERIAL_MAP_DIFFUSE + materials[m] = LoadMaterialDefault(); + + if (mats == NULL) continue; + + // Get default texture, in case no texture is defined + // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 + materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = (Texture2D){ rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; + + if (mats[m].diffuse_texname != NULL) materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture(mats[m].diffuse_texname); //char *diffuse_texname; // map_Kd + else materials[m].maps[MATERIAL_MAP_DIFFUSE].color = (Color){ (unsigned char)(mats[m].diffuse[0]*255.0f), (unsigned char)(mats[m].diffuse[1]*255.0f), (unsigned char)(mats[m].diffuse[2]*255.0f), 255 }; //float diffuse[3]; + materials[m].maps[MATERIAL_MAP_DIFFUSE].value = 0.0f; + + if (mats[m].specular_texname != NULL) materials[m].maps[MATERIAL_MAP_SPECULAR].texture = LoadTexture(mats[m].specular_texname); //char *specular_texname; // map_Ks + materials[m].maps[MATERIAL_MAP_SPECULAR].color = (Color){ (unsigned char)(mats[m].specular[0]*255.0f), (unsigned char)(mats[m].specular[1]*255.0f), (unsigned char)(mats[m].specular[2]*255.0f), 255 }; //float specular[3]; + materials[m].maps[MATERIAL_MAP_SPECULAR].value = 0.0f; + + if (mats[m].bump_texname != NULL) materials[m].maps[MATERIAL_MAP_NORMAL].texture = LoadTexture(mats[m].bump_texname); //char *bump_texname; // map_bump, bump + materials[m].maps[MATERIAL_MAP_NORMAL].color = WHITE; + materials[m].maps[MATERIAL_MAP_NORMAL].value = mats[m].shininess; + + materials[m].maps[MATERIAL_MAP_EMISSION].color = (Color){ (unsigned char)(mats[m].emission[0]*255.0f), (unsigned char)(mats[m].emission[1]*255.0f), (unsigned char)(mats[m].emission[2]*255.0f), 255 }; //float emission[3]; + + if (mats[m].displacement_texname != NULL) materials[m].maps[MATERIAL_MAP_HEIGHT].texture = LoadTexture(mats[m].displacement_texname); //char *displacement_texname; // disp + } +} +#endif + +// Load materials from model file +Material *LoadMaterials(const char *fileName, int *materialCount) +{ + Material *materials = NULL; + unsigned int count = 0; + + // TODO: Support IQM and GLTF for materials parsing + +#if SUPPORT_FILEFORMAT_MTL + if (IsFileExtension(fileName, ".mtl")) + { + tinyobj_material_t *mats = NULL; + + int result = tinyobj_parse_mtl_file(&mats, &count, fileName); + if (result != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to parse materials file", fileName); + + materials = (Material *)RL_CALLOC(count, sizeof(Material)); + ProcessMaterialsOBJ(materials, mats, count); + + tinyobj_materials_free(mats, count); + } +#else + TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load material file", fileName); +#endif + + *materialCount = count; + return materials; +} + +// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +Material LoadMaterialDefault(void) +{ + Material material = { 0 }; + material.maps = (MaterialMap *)RL_CALLOC(MAX_MATERIAL_MAPS, sizeof(MaterialMap)); + + // Using rlgl default shader + material.shader.id = rlGetShaderIdDefault(); + material.shader.locs = rlGetShaderLocsDefault(); + + // Using rlgl default texture (1x1 pixel, UNCOMPRESSED_R8G8B8A8, 1 mipmap) + material.maps[MATERIAL_MAP_DIFFUSE].texture = (Texture2D){ rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; + //material.maps[MATERIAL_MAP_NORMAL].texture; // NOTE: By default, not set + //material.maps[MATERIAL_MAP_SPECULAR].texture; // NOTE: By default, not set + + material.maps[MATERIAL_MAP_DIFFUSE].color = WHITE; // Diffuse color + material.maps[MATERIAL_MAP_SPECULAR].color = WHITE; // Specular color + + return material; +} + +// Check if material is valid (map textures loaded in GPU) +bool IsMaterialValid(Material material) +{ + bool result = false; + + if ((material.maps != NULL) && // Validate material contain some map + (material.shader.id > 0)) result = true; // Validate material shader is valid + + // NOTE: Checking if available maps contain loaded textures does not determine if + // a material is valid, there can be maps without a texture assigned, only properties + + return result; +} + +// Unload material from memory +void UnloadMaterial(Material material) +{ + // Unload material shader (avoid unloading default shader, managed by raylib) + if (material.shader.id != rlGetShaderIdDefault()) UnloadShader(material.shader); + + // Unload loaded texture maps (avoid unloading default texture, managed by raylib) + if (material.maps != NULL) + { + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id != rlGetTextureIdDefault()) rlUnloadTexture(material.maps[i].texture.id); + } + } + + RL_FREE(material.maps); +} + +// Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +// NOTE: Previous texture should be manually unloaded +void SetMaterialTexture(Material *material, int mapType, Texture2D texture) +{ + material->maps[mapType].texture = texture; +} + +// Set the material for a mesh +void SetModelMeshMaterial(Model *model, int meshId, int materialId) +{ + if (meshId >= model->meshCount) TRACELOG(LOG_WARNING, "MESH: Id greater than mesh count"); + else if (materialId >= model->materialCount) TRACELOG(LOG_WARNING, "MATERIAL: Id greater than material count"); + else model->meshMaterial[meshId] = materialId; +} + +// Load model animations from file +ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount) +{ + ModelAnimation *animations = NULL; + +#if SUPPORT_FILEFORMAT_IQM + if (IsFileExtension(fileName, ".iqm")) animations = LoadModelAnimationsIQM(fileName, animCount); +#endif +#if SUPPORT_FILEFORMAT_M3D + if (IsFileExtension(fileName, ".m3d")) animations = LoadModelAnimationsM3D(fileName, animCount); +#endif +#if SUPPORT_FILEFORMAT_GLTF + if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadModelAnimationsGLTF(fileName, animCount); +#endif + + return animations; +} + +// Update model animation data (vertex buffers / bone matrices) for a specific pose +// NOTE 1: Request frame could be fractional, using a lerp interpolation between two frames +// NOTE 2: Updated vertex animation data is uploaded to GPU in case of CPU skinning, +// for GPU skinning, bone matrices are uploaded to shader on DrawModelEx() +void UpdateModelAnimation(Model model, ModelAnimation anim, float frame) +{ + if (model.boneMatrices == NULL) return; + + //UpdateModelAnimationEx(model, anim, frame, anim, frame, 0.0f); + + // Update model animated bones transform matrices for a given frame + if ((anim.keyframeCount > 0) && (model.skeleton.bones != NULL) && (anim.keyframePoses != NULL)) + { + // Get frame and blending from frame factor required + int currentFrame = (int)frame; + int nextFrame = currentFrame + 1; + float blend = frame - currentFrame; + blend = Clamp(blend, 0.0f, 1.0f); + if (currentFrame >= anim.keyframeCount) currentFrame = currentFrame%anim.keyframeCount; + if (nextFrame >= anim.keyframeCount) nextFrame = nextFrame%anim.keyframeCount; + + Matrix bindPoseMatrix = { 0 }; + Matrix currentPoseMatrix = { 0 }; + + // Update all bones and bone matrices of model + for (unsigned int boneIndex = 0; boneIndex < model.skeleton.boneCount; boneIndex++) + { + // Compute interpolated pose between current and next frame + // NOTE: Storing animation frame data in model.currentPose + model.currentPose[boneIndex].translation = Vector3Lerp( + anim.keyframePoses[currentFrame][boneIndex].translation, + anim.keyframePoses[nextFrame][boneIndex].translation, blend); + model.currentPose[boneIndex].rotation = QuaternionSlerp( + anim.keyframePoses[currentFrame][boneIndex].rotation, + anim.keyframePoses[nextFrame][boneIndex].rotation, blend); + model.currentPose[boneIndex].scale = Vector3Lerp( + anim.keyframePoses[currentFrame][boneIndex].scale, + anim.keyframePoses[nextFrame][boneIndex].scale, blend); + + // Compute runtime bone matrix from model current pose + //----------------------------------------------------------------------------------- + Transform *bindPoseTransform = &model.skeleton.bindPose[boneIndex]; + bindPoseMatrix = MatrixMultiply( + MatrixMultiply(MatrixScale(bindPoseTransform->scale.x, bindPoseTransform->scale.y, bindPoseTransform->scale.z), + QuaternionToMatrix(bindPoseTransform->rotation)), + MatrixTranslate(bindPoseTransform->translation.x, bindPoseTransform->translation.y, bindPoseTransform->translation.z)); + + Transform *currentPoseTransform = &model.currentPose[boneIndex]; + currentPoseMatrix = MatrixMultiply( + MatrixMultiply(MatrixScale(currentPoseTransform->scale.x, currentPoseTransform->scale.y, currentPoseTransform->scale.z), + QuaternionToMatrix(currentPoseTransform->rotation)), + MatrixTranslate(currentPoseTransform->translation.x, currentPoseTransform->translation.y, currentPoseTransform->translation.z)); + + model.boneMatrices[boneIndex] = MatrixMultiply(MatrixInvert(bindPoseMatrix), currentPoseMatrix); + //----------------------------------------------------------------------------------- + } + + // CPU skinning, updates CPU buffers and uploads them to GPU + // NOTE: On GPU skinning not supported, use CPU skinning + UpdateModelAnimationVertexBuffers(model); + } +} + +// Update model animation data (vertex buffers / bone matrices) for a specific pose, +// defined by two different animations at specific frames blended together +// NOTE 1: Request frames could be fractional, using a lerp interpolation between two frames +// NOTE 2: Updated vertex animation data is uploaded to GPU in case of CPU skinning, +// for GPU skinning, bone matrices are uploaded to shader on DrawModelEx() +void UpdateModelAnimationEx(Model model, ModelAnimation animA, float frameA, ModelAnimation animB, float frameB, float blend) +{ + if (model.boneMatrices == NULL) return; + + if ((animA.keyframeCount > 0) && (animA.keyframePoses != NULL) && + (animB.keyframeCount > 0) && (animB.keyframePoses != NULL) && + (blend >= 0.0f) && (blend <= 1.0f)) + { + // Inter-frame interpolation values for first animation + int currentFrameA = (int)frameA%animA.keyframeCount; + int nextFrameA = currentFrameA + 1; + float blendA = frameA - currentFrameA; + blendA = Clamp(blendA, 0.0f, 1.0f); + if (currentFrameA >= animA.keyframeCount) currentFrameA = currentFrameA%animA.keyframeCount; + if (nextFrameA >= animA.keyframeCount) nextFrameA = nextFrameA%animA.keyframeCount; + + // Inter-frame interpolation values for second animation + int currentFrameB = (int)frameB%animB.keyframeCount; + int nextFrameB = currentFrameB + 1; + float blendB = frameB - currentFrameB; + blendB = Clamp(blendB, 0.0f, 1.0f); + if (currentFrameB >= animB.keyframeCount) currentFrameB = currentFrameB%animB.keyframeCount; + if (nextFrameB >= animB.keyframeCount) nextFrameB = nextFrameB%animB.keyframeCount; + + Matrix bindPoseMatrix = { 0 }; + Matrix currentPoseMatrix = { 0 }; + + for (unsigned int boneIndex = 0; boneIndex < model.skeleton.boneCount; boneIndex++) + { + // Get frame-interpolation for first animation + Vector3 frameATranslation = Vector3Lerp( + animA.keyframePoses[currentFrameA][boneIndex].translation, + animA.keyframePoses[nextFrameA][boneIndex].translation, blendA); + Quaternion frameARotation = QuaternionSlerp( + animA.keyframePoses[currentFrameA][boneIndex].rotation, + animA.keyframePoses[nextFrameA][boneIndex].rotation, blendA); + Vector3 frameAScale = Vector3Lerp( + animA.keyframePoses[currentFrameA][boneIndex].scale, + animA.keyframePoses[nextFrameA][boneIndex].scale, blendA); + + // Get frame-interpolation for second animation + Vector3 frameBTranslation = Vector3Lerp( + animB.keyframePoses[currentFrameB][boneIndex].translation, + animB.keyframePoses[nextFrameB][boneIndex].translation, blendB); + Quaternion frameBRotation = QuaternionSlerp( + animB.keyframePoses[currentFrameB][boneIndex].rotation, + animB.keyframePoses[nextFrameB][boneIndex].rotation, blendB); + Vector3 frameBScale = Vector3Lerp( + animB.keyframePoses[currentFrameB][boneIndex].scale, + animB.keyframePoses[nextFrameB][boneIndex].scale, blendB); + + // Compute interpolated pose between both animations frames + // NOTE: Storing animation frame data in model.currentPose + model.currentPose[boneIndex].translation = Vector3Lerp(frameATranslation, frameBTranslation, blend); + model.currentPose[boneIndex].rotation = QuaternionSlerp(frameARotation, frameBRotation, blend); + model.currentPose[boneIndex].scale = Vector3Lerp(frameAScale, frameBScale, blend); + + // Compute runtime bone matrix from model current pose + //----------------------------------------------------------------------------------- + Transform *bindPoseTransform = &model.skeleton.bindPose[boneIndex]; + bindPoseMatrix = MatrixMultiply( + MatrixMultiply(MatrixScale(bindPoseTransform->scale.x, bindPoseTransform->scale.y, bindPoseTransform->scale.z), + QuaternionToMatrix(bindPoseTransform->rotation)), + MatrixTranslate(bindPoseTransform->translation.x, bindPoseTransform->translation.y, bindPoseTransform->translation.z)); + + Transform *currentPoseTransform = &model.currentPose[boneIndex]; + currentPoseMatrix = MatrixMultiply( + MatrixMultiply(MatrixScale(currentPoseTransform->scale.x, currentPoseTransform->scale.y, currentPoseTransform->scale.z), + QuaternionToMatrix(currentPoseTransform->rotation)), + MatrixTranslate(currentPoseTransform->translation.x, currentPoseTransform->translation.y, currentPoseTransform->translation.z)); + + model.boneMatrices[boneIndex] = MatrixMultiply(MatrixInvert(bindPoseMatrix), currentPoseMatrix); + //----------------------------------------------------------------------------------- + + /* + Vector3 outATranslation = animA.keyframePoses[currentFrameA][boneIndex].translation; + Quaternion outARotation = animA.keyframePoses[currentFrameA][boneIndex].rotation; + Vector3 outAScale = animA.keyframePoses[currentFrameA][boneIndex].scale; + + Vector3 outBTranslation = animB.keyframePoses[currentFrameB][boneIndex].translation; + Quaternion outBRotation = animB.keyframePoses[currentFrameB][boneIndex].rotation; + Vector3 outBScale = animB.keyframePoses[currentFrameB][boneIndex].scale; + + // Invert bind pose transformation + Vector3 invBindTranslation = Vector3RotateByQuaternion( + Vector3Negate(model.skeleton.bindPose[boneIndex].translation), + QuaternionInvert(model.skeleton.bindPose[boneIndex].rotation)); + Quaternion invBindRotation = QuaternionInvert(model.skeleton.bindPose[boneIndex].rotation); + Vector3 invBindScale = Vector3Divide((Vector3){ 1.0f, 1.0f, 1.0f }, model.skeleton.bindPose[boneIndex].scale); + + Vector3 boneTranslation = Vector3Add(Vector3RotateByQuaternion( + Vector3Multiply(model.currentPose[boneIndex].scale, invBindTranslation), + model.currentPose[boneIndex].rotation), + model.currentPose[boneIndex].translation); + Quaternion boneRotation = QuaternionMultiply(model.currentPose[boneIndex].rotation, invBindRotation); + Vector3 boneScale = Vector3Multiply(model.currentPose[boneIndex].scale, invBindScale); + + model.boneMatrices[boneIndex] = MatrixMultiply( + MatrixMultiply(QuaternionToMatrix(boneRotation), + MatrixTranslate(boneTranslation.x, boneTranslation.y, boneTranslation.z)), + MatrixScale(boneScale.x, boneScale.y, boneScale.z)); + */ + } + + // CPU skinning, updates CPU buffers and uploads them to GPU (if available) + // NOTE: Fallback in case GPU skinning is not supported or enabled + UpdateModelAnimationVertexBuffers(model); + } +} + +// Update model vertex animation buffers (positions and normals) +// NOTE: Required for CPU skinning, uploads animated vertex buffers to GPU +static void UpdateModelAnimationVertexBuffers(Model model) +{ + for (int m = 0; m < model.meshCount; m++) + { + Mesh mesh = model.meshes[m]; + Vector3 animVertex = { 0 }; + Vector3 animNormal = { 0 }; + const int vertexValuesCount = mesh.vertexCount*3; + + int boneIndex = 0; + int boneCounter = 0; + float boneWeight = 0.0f; + bool bufferUpdateRequired = false; // Flag to check when anim vertex information is updated + + // Skip if missing bone data or missing anim buffers initialization + if ((mesh.boneWeights == NULL) || (mesh.boneIndices == NULL) || + (mesh.animVertices == NULL) || (mesh.animNormals == NULL)) continue; + + for (int vCounter = 0; vCounter < vertexValuesCount; vCounter += 3) + { + mesh.animVertices[vCounter] = 0; + mesh.animVertices[vCounter + 1] = 0; + mesh.animVertices[vCounter + 2] = 0; + if (mesh.animNormals != NULL) + { + mesh.animNormals[vCounter] = 0; + mesh.animNormals[vCounter + 1] = 0; + mesh.animNormals[vCounter + 2] = 0; + } + + // Iterates over 4 bones per vertex + for (int j = 0; j < 4; j++, boneCounter++) + { + boneWeight = mesh.boneWeights[boneCounter]; + boneIndex = mesh.boneIndices[boneCounter]; + + // Early stop when no transformation will be applied + if (boneWeight == 0.0f) continue; + animVertex = (Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] }; + animVertex = Vector3Transform(animVertex, model.boneMatrices[boneIndex]); + mesh.animVertices[vCounter] += animVertex.x*boneWeight; + mesh.animVertices[vCounter + 1] += animVertex.y*boneWeight; + mesh.animVertices[vCounter + 2] += animVertex.z*boneWeight; + bufferUpdateRequired = true; + + // Normals processing + // NOTE: Using meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) + if ((mesh.normals != NULL) && (mesh.animNormals != NULL )) + { + animNormal = (Vector3){ mesh.normals[vCounter], mesh.normals[vCounter + 1], mesh.normals[vCounter + 2] }; + animNormal = Vector3Transform(animNormal, MatrixTranspose(MatrixInvert(model.boneMatrices[boneIndex]))); + mesh.animNormals[vCounter] += animNormal.x*boneWeight; + mesh.animNormals[vCounter + 1] += animNormal.y*boneWeight; + mesh.animNormals[vCounter + 2] += animNormal.z*boneWeight; + } + } + } + + if (bufferUpdateRequired) + { + // Update GPU vertex buffers with updated data (position + normals) + rlUpdateVertexBuffer(mesh.vboId[SHADER_LOC_VERTEX_POSITION], mesh.animVertices, mesh.vertexCount*3*sizeof(float), 0); + if (mesh.normals != NULL) rlUpdateVertexBuffer(mesh.vboId[SHADER_LOC_VERTEX_NORMAL], mesh.animNormals, mesh.vertexCount*3*sizeof(float), 0); + } + } +} + +// Unload animation array data +void UnloadModelAnimations(ModelAnimation *animations, int animCount) +{ + for (int a = 0; a < animCount; a++) + { + for (int i = 0; i < animations[a].keyframeCount; i++) + RL_FREE(animations[a].keyframePoses[i]); + + RL_FREE(animations[a].keyframePoses); + } + + RL_FREE(animations); +} + +// Check model animation skeleton match +// NOTE: Only number of bones and parent connections are checked +bool IsModelAnimationValid(Model model, ModelAnimation anim) +{ + int result = true; + + if (model.skeleton.boneCount != anim.boneCount) result = false; + + return result; +} + +#if SUPPORT_MESH_GENERATION +// Generate polygonal mesh +Mesh GenMeshPoly(int sides, float radius) +{ + Mesh mesh = { 0 }; + + if (sides < 3) return mesh; // Security check + + int vertexCount = sides*3; + + // Vertices definition + Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + + float d = 0.0f, dStep = 360.0f/sides; + for (int v = 0; v < vertexCount - 2; v += 3) + { + vertices[v] = (Vector3){ 0.0f, 0.0f, 0.0f }; + vertices[v + 1] = (Vector3){ sinf(DEG2RAD*d)*radius, 0.0f, cosf(DEG2RAD*d)*radius }; + vertices[v + 2] = (Vector3){ sinf(DEG2RAD*(d+dStep))*radius, 0.0f, cosf(DEG2RAD*(d+dStep))*radius }; + d += dStep; + } + + // Normals definition + Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; + + // TexCoords definition + Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2)); + for (int n = 0; n < vertexCount; n++) texcoords[n] = (Vector2){ 0.0f, 0.0f }; + + mesh.vertexCount = vertexCount; + mesh.triangleCount = sides; + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + + // Mesh vertices position array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.vertices[3*i] = vertices[i].x; + mesh.vertices[3*i + 1] = vertices[i].y; + mesh.vertices[3*i + 2] = vertices[i].z; + } + + // Mesh texcoords array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.texcoords[2*i] = texcoords[i].x; + mesh.texcoords[2*i + 1] = texcoords[i].y; + } + + // Mesh normals array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.normals[3*i] = normals[i].x; + mesh.normals[3*i + 1] = normals[i].y; + mesh.normals[3*i + 2] = normals[i].z; + } + + RL_FREE(vertices); + RL_FREE(normals); + RL_FREE(texcoords); + + // Upload vertex data to GPU (static mesh) + // NOTE: mesh.vboId array is allocated inside UploadMesh() + UploadMesh(&mesh, false); + + return mesh; +} + +// Generate plane mesh (with subdivisions) +Mesh GenMeshPlane(float width, float length, int resX, int resZ) +{ + Mesh mesh = { 0 }; + +#define CUSTOM_MESH_GEN_PLANE +#if defined(CUSTOM_MESH_GEN_PLANE) + resX++; + resZ++; + + // Vertices definition + int vertexCount = resX*resZ; // vertices get reused for the faces + + Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + for (int z = 0; z < resZ; z++) + { + // [-length/2, length/2] + float zPos = ((float)z/(resZ - 1) - 0.5f)*length; + for (int x = 0; x < resX; x++) + { + // [-width/2, width/2] + float xPos = ((float)x/(resX - 1) - 0.5f)*width; + vertices[x + z*resX] = (Vector3){ xPos, 0.0f, zPos }; + } + } + + // Normals definition + Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); + for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; + + // TexCoords definition + Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2)); + for (int v = 0; v < resZ; v++) + { + for (int u = 0; u < resX; u++) + { + texcoords[u + v*resX] = (Vector2){ (float)u/(resX - 1), (float)v/(resZ - 1) }; + } + } + + // Triangles definition (indices) + int numFaces = (resX - 1)*(resZ - 1); + int *triangles = (int *)RL_MALLOC(numFaces*6*sizeof(int)); + int t = 0; + for (int face = 0; face < numFaces; face++) + { + // Retrieve lower left corner from face ind + int i = face + face/(resX - 1); + + triangles[t++] = i + resX; + triangles[t++] = i + 1; + triangles[t++] = i; + + triangles[t++] = i + resX; + triangles[t++] = i + resX + 1; + triangles[t++] = i + 1; + } + + mesh.vertexCount = vertexCount; + mesh.triangleCount = numFaces*2; + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.indices = (unsigned short *)RL_MALLOC(mesh.triangleCount*3*sizeof(unsigned short)); + + // Mesh vertices position array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.vertices[3*i] = vertices[i].x; + mesh.vertices[3*i + 1] = vertices[i].y; + mesh.vertices[3*i + 2] = vertices[i].z; + } + + // Mesh texcoords array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.texcoords[2*i] = texcoords[i].x; + mesh.texcoords[2*i + 1] = texcoords[i].y; + } + + // Mesh normals array + for (int i = 0; i < mesh.vertexCount; i++) + { + mesh.normals[3*i] = normals[i].x; + mesh.normals[3*i + 1] = normals[i].y; + mesh.normals[3*i + 2] = normals[i].z; + } + + // Mesh indices array initialization + for (int i = 0; i < mesh.triangleCount*3; i++) mesh.indices[i] = triangles[i]; + + RL_FREE(vertices); + RL_FREE(normals); + RL_FREE(texcoords); + RL_FREE(triangles); + +#else // Use par_shapes library to generate plane mesh + + par_shapes_mesh *plane = par_shapes_create_plane(resX, resZ); // No normals/texcoords generated!!! + par_shapes_scale(plane, width, length, 1.0f); + par_shapes_rotate(plane, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_translate(plane, -width/2, 0.0f, length/2); + + mesh.vertices = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(plane->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = plane->ntriangles*3; + mesh.triangleCount = plane->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = plane->points[plane->triangles[k]*3]; + mesh.vertices[k*3 + 1] = plane->points[plane->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = plane->points[plane->triangles[k]*3 + 2]; + + mesh.normals[k*3] = plane->normals[plane->triangles[k]*3]; + mesh.normals[k*3 + 1] = plane->normals[plane->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = plane->normals[plane->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = plane->tcoords[plane->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = plane->tcoords[plane->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(plane); +#endif + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} + +// Generated cuboid mesh +Mesh GenMeshCube(float width, float height, float length) +{ + Mesh mesh = { 0 }; + +#define CUSTOM_MESH_GEN_CUBE +#if defined(CUSTOM_MESH_GEN_CUBE) + float vertices[] = { + -width/2, -height/2, length/2, + width/2, -height/2, length/2, + width/2, height/2, length/2, + -width/2, height/2, length/2, + -width/2, -height/2, -length/2, + -width/2, height/2, -length/2, + width/2, height/2, -length/2, + width/2, -height/2, -length/2, + -width/2, height/2, -length/2, + -width/2, height/2, length/2, + width/2, height/2, length/2, + width/2, height/2, -length/2, + -width/2, -height/2, -length/2, + width/2, -height/2, -length/2, + width/2, -height/2, length/2, + -width/2, -height/2, length/2, + width/2, -height/2, -length/2, + width/2, height/2, -length/2, + width/2, height/2, length/2, + width/2, -height/2, length/2, + -width/2, -height/2, -length/2, + -width/2, -height/2, length/2, + -width/2, height/2, length/2, + -width/2, height/2, -length/2 + }; + + float texcoords[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + + float normals[] = { + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 0.0f,-1.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 0.0f,-1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f + }; + + mesh.vertices = (float *)RL_MALLOC(24*3*sizeof(float)); + memcpy(mesh.vertices, vertices, 24*3*sizeof(float)); + + mesh.texcoords = (float *)RL_MALLOC(24*2*sizeof(float)); + memcpy(mesh.texcoords, texcoords, 24*2*sizeof(float)); + + mesh.normals = (float *)RL_MALLOC(24*3*sizeof(float)); + memcpy(mesh.normals, normals, 24*3*sizeof(float)); + + mesh.indices = (unsigned short *)RL_MALLOC(36*sizeof(unsigned short)); + + int k = 0; + + // Indices can be initialized right now + for (int i = 0; i < 36; i += 6) + { + mesh.indices[i] = 4*k; + mesh.indices[i + 1] = 4*k + 1; + mesh.indices[i + 2] = 4*k + 2; + mesh.indices[i + 3] = 4*k; + mesh.indices[i + 4] = 4*k + 2; + mesh.indices[i + 5] = 4*k + 3; + + k++; + } + + mesh.vertexCount = 24; + mesh.triangleCount = 12; + +#else // Use par_shapes library to generate cube mesh +/* +// Platonic solids: +par_shapes_mesh *par_shapes_create_tetrahedron(); // 4 sides polyhedron (pyramid) +par_shapes_mesh *par_shapes_create_cube(); // 6 sides polyhedron (cube) +par_shapes_mesh *par_shapes_create_octahedron(); // 8 sides polyhedron (diamond) +par_shapes_mesh *par_shapes_create_dodecahedron(); // 12 sides polyhedron +par_shapes_mesh *par_shapes_create_icosahedron(); // 20 sides polyhedron +*/ + // Platonic solid generation: cube (6 sides) + // NOTE: No normals/texcoords generated by default + par_shapes_mesh *cube = par_shapes_create_cube(); + cube->tcoords = PAR_MALLOC(float, 2*cube->npoints); + for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f; + par_shapes_scale(cube, width, height, length); + par_shapes_translate(cube, -width/2, 0.0f, -length/2); + par_shapes_compute_normals(cube); + + mesh.vertices = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(cube->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = cube->ntriangles*3; + mesh.triangleCount = cube->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = cube->points[cube->triangles[k]*3]; + mesh.vertices[k*3 + 1] = cube->points[cube->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = cube->points[cube->triangles[k]*3 + 2]; + + mesh.normals[k*3] = cube->normals[cube->triangles[k]*3]; + mesh.normals[k*3 + 1] = cube->normals[cube->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = cube->normals[cube->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = cube->tcoords[cube->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = cube->tcoords[cube->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(cube); +#endif + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} + +// Generate sphere mesh (standard sphere) +Mesh GenMeshSphere(float radius, int rings, int slices) +{ + Mesh mesh = { 0 }; + + if ((rings >= 3) && (slices >= 3)) + { + par_shapes_set_epsilon_degenerate_sphere(0.0); + par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings); + par_shapes_scale(sphere, radius, radius, radius); + // NOTE: Soft normals are computed internally + + mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = sphere->ntriangles*3; + mesh.triangleCount = sphere->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; + mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; + + mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; + mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(sphere); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: sphere"); + + return mesh; +} + +// Generate hemisphere mesh (half sphere, no bottom cap) +Mesh GenMeshHemiSphere(float radius, int rings, int slices) +{ + Mesh mesh = { 0 }; + + if ((rings >= 3) && (slices >= 3)) + { + if (radius < 0.0f) radius = 0.0f; + + par_shapes_mesh *sphere = par_shapes_create_hemisphere(slices, rings); + par_shapes_scale(sphere, radius, radius, radius); + // NOTE: Soft normals are computed internally + + mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = sphere->ntriangles*3; + mesh.triangleCount = sphere->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; + mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; + + mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; + mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(sphere); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: hemisphere"); + + return mesh; +} + +// Generate cylinder mesh +Mesh GenMeshCylinder(float radius, float height, int slices) +{ + Mesh mesh = { 0 }; + + if (slices >= 3) + { + // Instance a cylinder that sits on the Z=0 plane using the given tessellation + // levels across the UV domain. Think of "slices" like a number of pizza + // slices, and "stacks" like a number of stacked rings + // Height and radius are both 1.0, but they can easily be changed with par_shapes_scale + par_shapes_mesh *cylinder = par_shapes_create_cylinder(slices, 8); + par_shapes_scale(cylinder, radius, radius, height); + par_shapes_rotate(cylinder, -PI/2.0f, (float[]){ 1, 0, 0 }); + + // Generate an orientable disk shape (top cap) + par_shapes_mesh *capTop = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, 1 }); + capTop->tcoords = PAR_MALLOC(float, 2*capTop->npoints); + for (int i = 0; i < 2*capTop->npoints; i++) capTop->tcoords[i] = 0.0f; + par_shapes_rotate(capTop, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_rotate(capTop, 90*DEG2RAD, (float[]){ 0, 1, 0 }); + par_shapes_translate(capTop, 0, height, 0); + + // Generate an orientable disk shape (bottom cap) + par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, -1 }); + capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints); + for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f; + par_shapes_rotate(capBottom, PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_rotate(capBottom, -90*DEG2RAD, (float[]){ 0, 1, 0 }); + + par_shapes_merge_and_free(cylinder, capTop); + par_shapes_merge_and_free(cylinder, capBottom); + + mesh.vertices = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(cylinder->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = cylinder->ntriangles*3; + mesh.triangleCount = cylinder->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = cylinder->points[cylinder->triangles[k]*3]; + mesh.vertices[k*3 + 1] = cylinder->points[cylinder->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = cylinder->points[cylinder->triangles[k]*3 + 2]; + + mesh.normals[k*3] = cylinder->normals[cylinder->triangles[k]*3]; + mesh.normals[k*3 + 1] = cylinder->normals[cylinder->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = cylinder->normals[cylinder->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = cylinder->tcoords[cylinder->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = cylinder->tcoords[cylinder->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(cylinder); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cylinder"); + + return mesh; +} + +// Generate cone/pyramid mesh +Mesh GenMeshCone(float radius, float height, int slices) +{ + Mesh mesh = { 0 }; + + if (slices >= 3) + { + // Instance a cone that sits on the Z=0 plane using the given tessellation + // levels across the UV domain. Think of "slices" like a number of pizza + // slices, and "stacks" like a number of stacked rings + // Height and radius are both 1.0, but they can easily be changed with par_shapes_scale + par_shapes_mesh *cone = par_shapes_create_cone(slices, 8); + par_shapes_scale(cone, radius, radius, height); + par_shapes_rotate(cone, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_rotate(cone, PI/2.0f, (float[]){ 0, 1, 0 }); + + // Generate an orientable disk shape (bottom cap) + par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, -1 }); + capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints); + for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f; + par_shapes_rotate(capBottom, PI/2.0f, (float[]){ 1, 0, 0 }); + + par_shapes_merge_and_free(cone, capBottom); + + mesh.vertices = (float *)RL_MALLOC(cone->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(cone->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(cone->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = cone->ntriangles*3; + mesh.triangleCount = cone->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = cone->points[cone->triangles[k]*3]; + mesh.vertices[k*3 + 1] = cone->points[cone->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = cone->points[cone->triangles[k]*3 + 2]; + + mesh.normals[k*3] = cone->normals[cone->triangles[k]*3]; + mesh.normals[k*3 + 1] = cone->normals[cone->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = cone->normals[cone->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = cone->tcoords[cone->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = cone->tcoords[cone->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(cone); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cone"); + + return mesh; +} + +// Generate torus mesh +// NOTE: The distance between the center of the hole and the center of the +// tube is half size of the radius of the tube (radius*size/2) +Mesh GenMeshTorus(float radius, float size, int radSeg, int sides) +{ + Mesh mesh = { 0 }; + + if ((sides >= 3) && (radSeg >= 3)) + { + if (radius > 1.0f) radius = 1.0f; + else if (radius < 0.1f) radius = 0.1f; + + // Create a donut that sits on the Z=0 plane with the specified inner radius + // The outer radius can be controlled with par_shapes_scale + par_shapes_mesh *torus = par_shapes_create_torus(radSeg, sides, radius); + par_shapes_scale(torus, size/2, size/2, size/2); + + mesh.vertices = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(torus->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = torus->ntriangles*3; + mesh.triangleCount = torus->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = torus->points[torus->triangles[k]*3]; + mesh.vertices[k*3 + 1] = torus->points[torus->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = torus->points[torus->triangles[k]*3 + 2]; + + mesh.normals[k*3] = torus->normals[torus->triangles[k]*3]; + mesh.normals[k*3 + 1] = torus->normals[torus->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = torus->normals[torus->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = torus->tcoords[torus->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = torus->tcoords[torus->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(torus); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: torus"); + + return mesh; +} + +// Generate trefoil knot mesh +Mesh GenMeshKnot(float radius, float size, int radSeg, int sides) +{ + Mesh mesh = { 0 }; + + if ((sides >= 3) && (radSeg >= 3)) + { + if (radius > 3.0f) radius = 3.0f; + else if (radius < 0.5f) radius = 0.5f; + + par_shapes_mesh *knot = par_shapes_create_trefoil_knot(radSeg, sides, radius); + par_shapes_scale(knot, size, size, size); + + mesh.vertices = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(knot->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = knot->ntriangles*3; + mesh.triangleCount = knot->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = knot->points[knot->triangles[k]*3]; + mesh.vertices[k*3 + 1] = knot->points[knot->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = knot->points[knot->triangles[k]*3 + 2]; + + mesh.normals[k*3] = knot->normals[knot->triangles[k]*3]; + mesh.normals[k*3 + 1] = knot->normals[knot->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = knot->normals[knot->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = knot->tcoords[knot->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = knot->tcoords[knot->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(knot); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + } + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: knot"); + + return mesh; +} + +// Generate a mesh from heightmap +// NOTE: Vertex data is uploaded to GPU +Mesh GenMeshHeightmap(Image heightmap, Vector3 size) +{ + #define GRAY_VALUE(c) ((float)(c.r + c.g + c.b)/3.0f) + + Mesh mesh = { 0 }; + + int mapX = heightmap.width; + int mapZ = heightmap.height; + + Color *pixels = LoadImageColors(heightmap); + + // NOTE: One vertex per pixel + mesh.triangleCount = (mapX - 1)*(mapZ - 1)*2; // One quad every four pixels + + mesh.vertexCount = mesh.triangleCount*3; + + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.colors = NULL; + + int vCounter = 0; // Used to count vertices float by float + int tcCounter = 0; // Used to count texcoords float by float + int nCounter = 0; // Used to count normals float by float + + Vector3 scaleFactor = { size.x/(mapX - 1), size.y/255.0f, size.z/(mapZ - 1) }; + + Vector3 vA = { 0 }; + Vector3 vB = { 0 }; + Vector3 vC = { 0 }; + Vector3 vN = { 0 }; + + for (int z = 0; z < mapZ-1; z++) + { + for (int x = 0; x < mapX-1; x++) + { + // Fill vertices array with data + //---------------------------------------------------------- + + // one triangle - 3 vertex + mesh.vertices[vCounter] = (float)x*scaleFactor.x; + mesh.vertices[vCounter + 1] = GRAY_VALUE(pixels[x + z*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 2] = (float)z*scaleFactor.z; + + mesh.vertices[vCounter + 3] = (float)x*scaleFactor.x; + mesh.vertices[vCounter + 4] = GRAY_VALUE(pixels[x + (z + 1)*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 5] = (float)(z + 1)*scaleFactor.z; + + mesh.vertices[vCounter + 6] = (float)(x + 1)*scaleFactor.x; + mesh.vertices[vCounter + 7] = GRAY_VALUE(pixels[(x + 1) + z*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 8] = (float)z*scaleFactor.z; + + // Another triangle - 3 vertex + mesh.vertices[vCounter + 9] = mesh.vertices[vCounter + 6]; + mesh.vertices[vCounter + 10] = mesh.vertices[vCounter + 7]; + mesh.vertices[vCounter + 11] = mesh.vertices[vCounter + 8]; + + mesh.vertices[vCounter + 12] = mesh.vertices[vCounter + 3]; + mesh.vertices[vCounter + 13] = mesh.vertices[vCounter + 4]; + mesh.vertices[vCounter + 14] = mesh.vertices[vCounter + 5]; + + mesh.vertices[vCounter + 15] = (float)(x + 1)*scaleFactor.x; + mesh.vertices[vCounter + 16] = GRAY_VALUE(pixels[(x + 1) + (z + 1)*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 17] = (float)(z + 1)*scaleFactor.z; + vCounter += 18; // 6 vertex, 18 floats + + // Fill texcoords array with data + //-------------------------------------------------------------- + mesh.texcoords[tcCounter] = (float)x/(mapX - 1); + mesh.texcoords[tcCounter + 1] = (float)z/(mapZ - 1); + + mesh.texcoords[tcCounter + 2] = (float)x/(mapX - 1); + mesh.texcoords[tcCounter + 3] = (float)(z + 1)/(mapZ - 1); + + mesh.texcoords[tcCounter + 4] = (float)(x + 1)/(mapX - 1); + mesh.texcoords[tcCounter + 5] = (float)z/(mapZ - 1); + + mesh.texcoords[tcCounter + 6] = mesh.texcoords[tcCounter + 4]; + mesh.texcoords[tcCounter + 7] = mesh.texcoords[tcCounter + 5]; + + mesh.texcoords[tcCounter + 8] = mesh.texcoords[tcCounter + 2]; + mesh.texcoords[tcCounter + 9] = mesh.texcoords[tcCounter + 3]; + + mesh.texcoords[tcCounter + 10] = (float)(x + 1)/(mapX - 1); + mesh.texcoords[tcCounter + 11] = (float)(z + 1)/(mapZ - 1); + tcCounter += 12; // 6 texcoords, 12 floats + + // Fill normals array with data + //-------------------------------------------------------------- + for (int i = 0; i < 18; i += 9) + { + vA.x = mesh.vertices[nCounter + i]; + vA.y = mesh.vertices[nCounter + i + 1]; + vA.z = mesh.vertices[nCounter + i + 2]; + + vB.x = mesh.vertices[nCounter + i + 3]; + vB.y = mesh.vertices[nCounter + i + 4]; + vB.z = mesh.vertices[nCounter + i + 5]; + + vC.x = mesh.vertices[nCounter + i + 6]; + vC.y = mesh.vertices[nCounter + i + 7]; + vC.z = mesh.vertices[nCounter + i + 8]; + + vN = Vector3Normalize(Vector3CrossProduct(Vector3Subtract(vB, vA), Vector3Subtract(vC, vA))); + + mesh.normals[nCounter + i] = vN.x; + mesh.normals[nCounter + i + 1] = vN.y; + mesh.normals[nCounter + i + 2] = vN.z; + + mesh.normals[nCounter + i + 3] = vN.x; + mesh.normals[nCounter + i + 4] = vN.y; + mesh.normals[nCounter + i + 5] = vN.z; + + mesh.normals[nCounter + i + 6] = vN.x; + mesh.normals[nCounter + i + 7] = vN.y; + mesh.normals[nCounter + i + 8] = vN.z; + } + + nCounter += 18; // 6 vertex, 18 floats + } + } + + UnloadImageColors(pixels); // Unload pixels color data + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} + +// Generate a cubes mesh from pixel data +// NOTE: Vertex data is uploaded to GPU +Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) +{ + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) + + Mesh mesh = { 0 }; + + Color *pixels = LoadImageColors(cubicmap); + + // NOTE: Max possible number of triangles numCubes*(12 triangles by cube) + int maxTriangles = cubicmap.width*cubicmap.height*12; + + int vCounter = 0; // Used to count vertices + int tcCounter = 0; // Used to count texcoords + int nCounter = 0; // Used to count normals + + float w = cubeSize.x; + float h = cubeSize.z; + float h2 = cubeSize.y; + + Vector3 *mapVertices = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3)); + Vector2 *mapTexcoords = (Vector2 *)RL_MALLOC(maxTriangles*3*sizeof(Vector2)); + Vector3 *mapNormals = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3)); + + // Define the 6 normals of the cube, combined accordingly later + Vector3 n1 = { 1.0f, 0.0f, 0.0f }; + Vector3 n2 = { -1.0f, 0.0f, 0.0f }; + Vector3 n3 = { 0.0f, 1.0f, 0.0f }; + Vector3 n4 = { 0.0f, -1.0f, 0.0f }; + Vector3 n5 = { 0.0f, 0.0f, -1.0f }; + Vector3 n6 = { 0.0f, 0.0f, 1.0f }; + + // NOTE: Using texture rectangles to define different + // textures for top-bottom-front-back-right-left (6) + typedef struct RectangleF { + float x; + float y; + float width; + float height; + } RectangleF; + + RectangleF rightTexUV = { 0.0f, 0.0f, 0.5f, 0.5f }; + RectangleF leftTexUV = { 0.5f, 0.0f, 0.5f, 0.5f }; + RectangleF frontTexUV = { 0.0f, 0.0f, 0.5f, 0.5f }; + RectangleF backTexUV = { 0.5f, 0.0f, 0.5f, 0.5f }; + RectangleF topTexUV = { 0.0f, 0.5f, 0.5f, 0.5f }; + RectangleF bottomTexUV = { 0.5f, 0.5f, 0.5f, 0.5f }; + + for (int z = 0; z < cubicmap.height; ++z) + { + for (int x = 0; x < cubicmap.width; x++) + { + // Define the 8 vertex of the cube, to be combined accordingly later + Vector3 v1 = { w*(x - 0.5f), h2, h*(z - 0.5f) }; + Vector3 v2 = { w*(x - 0.5f), h2, h*(z + 0.5f) }; + Vector3 v3 = { w*(x + 0.5f), h2, h*(z + 0.5f) }; + Vector3 v4 = { w*(x + 0.5f), h2, h*(z - 0.5f) }; + Vector3 v5 = { w*(x + 0.5f), 0, h*(z - 0.5f) }; + Vector3 v6 = { w*(x - 0.5f), 0, h*(z - 0.5f) }; + Vector3 v7 = { w*(x - 0.5f), 0, h*(z + 0.5f) }; + Vector3 v8 = { w*(x + 0.5f), 0, h*(z + 0.5f) }; + + // Check pixel color to be WHITE -> draw full cube + if (COLOR_EQUAL(pixels[z*cubicmap.width + x], WHITE)) + { + // Define triangles and checking collateral cubes + //------------------------------------------------ + + // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) + // WARNING: Not required for a WHITE cubes, created to allow seeing the map from outside + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v2; + mapVertices[vCounter + 2] = v3; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v3; + mapVertices[vCounter + 5] = v4; + vCounter += 6; + + mapNormals[nCounter] = n3; + mapNormals[nCounter + 1] = n3; + mapNormals[nCounter + 2] = n3; + mapNormals[nCounter + 3] = n3; + mapNormals[nCounter + 4] = n3; + mapNormals[nCounter + 5] = n3; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; + tcCounter += 6; + + // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) + mapVertices[vCounter] = v6; + mapVertices[vCounter + 1] = v8; + mapVertices[vCounter + 2] = v7; + mapVertices[vCounter + 3] = v6; + mapVertices[vCounter + 4] = v5; + mapVertices[vCounter + 5] = v8; + vCounter += 6; + + mapNormals[nCounter] = n4; + mapNormals[nCounter + 1] = n4; + mapNormals[nCounter + 2] = n4; + mapNormals[nCounter + 3] = n4; + mapNormals[nCounter + 4] = n4; + mapNormals[nCounter + 5] = n4; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + tcCounter += 6; + + // Checking cube on bottom of current cube + if (((z < cubicmap.height - 1) && COLOR_EQUAL(pixels[(z + 1)*cubicmap.width + x], BLACK)) || (z == cubicmap.height - 1)) + { + // Define front triangles (2 tris, 6 vertex) --> v2 v7 v3, v3 v7 v8 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v2; + mapVertices[vCounter + 1] = v7; + mapVertices[vCounter + 2] = v3; + mapVertices[vCounter + 3] = v3; + mapVertices[vCounter + 4] = v7; + mapVertices[vCounter + 5] = v8; + vCounter += 6; + + mapNormals[nCounter] = n6; + mapNormals[nCounter + 1] = n6; + mapNormals[nCounter + 2] = n6; + mapNormals[nCounter + 3] = n6; + mapNormals[nCounter + 4] = n6; + mapNormals[nCounter + 5] = n6; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ frontTexUV.x, frontTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y + frontTexUV.height }; + tcCounter += 6; + } + + // Checking cube on top of current cube + if (((z > 0) && COLOR_EQUAL(pixels[(z - 1)*cubicmap.width + x], BLACK)) || (z == 0)) + { + // Define back triangles (2 tris, 6 vertex) --> v1 v5 v6, v1 v4 v5 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v5; + mapVertices[vCounter + 2] = v6; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v4; + mapVertices[vCounter + 5] = v5; + vCounter += 6; + + mapNormals[nCounter] = n5; + mapNormals[nCounter + 1] = n5; + mapNormals[nCounter + 2] = n5; + mapNormals[nCounter + 3] = n5; + mapNormals[nCounter + 4] = n5; + mapNormals[nCounter + 5] = n5; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y + backTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ backTexUV.x, backTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; + tcCounter += 6; + } + + // Checking cube on right of current cube + if (((x < cubicmap.width - 1) && COLOR_EQUAL(pixels[z*cubicmap.width + (x + 1)], BLACK)) || (x == cubicmap.width - 1)) + { + // Define right triangles (2 tris, 6 vertex) --> v3 v8 v4, v4 v8 v5 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v3; + mapVertices[vCounter + 1] = v8; + mapVertices[vCounter + 2] = v4; + mapVertices[vCounter + 3] = v4; + mapVertices[vCounter + 4] = v8; + mapVertices[vCounter + 5] = v5; + vCounter += 6; + + mapNormals[nCounter] = n1; + mapNormals[nCounter + 1] = n1; + mapNormals[nCounter + 2] = n1; + mapNormals[nCounter + 3] = n1; + mapNormals[nCounter + 4] = n1; + mapNormals[nCounter + 5] = n1; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ rightTexUV.x, rightTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y + rightTexUV.height }; + tcCounter += 6; + } + + // Checking cube on left of current cube + if (((x > 0) && COLOR_EQUAL(pixels[z*cubicmap.width + (x - 1)], BLACK)) || (x == 0)) + { + // Define left triangles (2 tris, 6 vertex) --> v1 v7 v2, v1 v6 v7 + // NOTE: Collateral occluded faces are not generated + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v7; + mapVertices[vCounter + 2] = v2; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v6; + mapVertices[vCounter + 5] = v7; + vCounter += 6; + + mapNormals[nCounter] = n2; + mapNormals[nCounter + 1] = n2; + mapNormals[nCounter + 2] = n2; + mapNormals[nCounter + 3] = n2; + mapNormals[nCounter + 4] = n2; + mapNormals[nCounter + 5] = n2; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ leftTexUV.x, leftTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ leftTexUV.x, leftTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ leftTexUV.x, leftTexUV.y + leftTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; + tcCounter += 6; + } + } + // Check pixel color to be BLACK, in that case only drawing floor and roof + else if (COLOR_EQUAL(pixels[z*cubicmap.width + x], BLACK)) + { + // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v3; + mapVertices[vCounter + 2] = v2; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v4; + mapVertices[vCounter + 5] = v3; + vCounter += 6; + + mapNormals[nCounter] = n4; + mapNormals[nCounter + 1] = n4; + mapNormals[nCounter + 2] = n4; + mapNormals[nCounter + 3] = n4; + mapNormals[nCounter + 4] = n4; + mapNormals[nCounter + 5] = n4; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + tcCounter += 6; + + // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) + mapVertices[vCounter] = v6; + mapVertices[vCounter + 1] = v7; + mapVertices[vCounter + 2] = v8; + mapVertices[vCounter + 3] = v6; + mapVertices[vCounter + 4] = v8; + mapVertices[vCounter + 5] = v5; + vCounter += 6; + + mapNormals[nCounter] = n3; + mapNormals[nCounter + 1] = n3; + mapNormals[nCounter + 2] = n3; + mapNormals[nCounter + 3] = n3; + mapNormals[nCounter + 4] = n3; + mapNormals[nCounter + 5] = n3; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y }; + tcCounter += 6; + } + } + } + + // Move data from mapVertices temp arrays to vertices float array + mesh.vertexCount = vCounter; + mesh.triangleCount = vCounter/3; + + mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); + mesh.colors = NULL; + + int fCounter = 0; + + // Move vertices data + for (int i = 0; i < vCounter; i++) + { + mesh.vertices[fCounter] = mapVertices[i].x; + mesh.vertices[fCounter + 1] = mapVertices[i].y; + mesh.vertices[fCounter + 2] = mapVertices[i].z; + fCounter += 3; + } + + fCounter = 0; + + // Move normals data + for (int i = 0; i < nCounter; i++) + { + mesh.normals[fCounter] = mapNormals[i].x; + mesh.normals[fCounter + 1] = mapNormals[i].y; + mesh.normals[fCounter + 2] = mapNormals[i].z; + fCounter += 3; + } + + fCounter = 0; + + // Move texcoords data + for (int i = 0; i < tcCounter; i++) + { + mesh.texcoords[fCounter] = mapTexcoords[i].x; + mesh.texcoords[fCounter + 1] = mapTexcoords[i].y; + fCounter += 2; + } + + RL_FREE(mapVertices); + RL_FREE(mapNormals); + RL_FREE(mapTexcoords); + + UnloadImageColors(pixels); // Unload pixels color data + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); + + return mesh; +} +#endif // SUPPORT_MESH_GENERATION + +// Compute mesh bounding box limits +// NOTE: minVertex and maxVertex should be transformed by model transform matrix +BoundingBox GetMeshBoundingBox(Mesh mesh) +{ + // Get min and max vertex to construct bounds (AABB) + Vector3 minVertex = { 0 }; + Vector3 maxVertex = { 0 }; + + if (mesh.vertices != NULL) + { + minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + + for (int i = 1; i < mesh.vertexCount; i++) + { + minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); + maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] }); + } + } + + // Create the bounding box + BoundingBox box = { 0 }; + box.min = minVertex; + box.max = maxVertex; + + return box; +} + +// Compute mesh tangents +void GenMeshTangents(Mesh *mesh) +{ + // Check if input mesh data is useful + if ((mesh == NULL) || (mesh->vertices == NULL) || (mesh->texcoords == NULL) || (mesh->normals == NULL)) + { + TRACELOG(LOG_WARNING, "MESH: Tangents generation requires vertices, texcoords and normals vertex attribute data"); + return; + } + + // Allocate or reallocate tangents data + if (mesh->tangents == NULL) mesh->tangents = (float *)RL_MALLOC(mesh->vertexCount*4*sizeof(float)); + else + { + RL_FREE(mesh->tangents); + mesh->tangents = (float *)RL_MALLOC(mesh->vertexCount*4*sizeof(float)); + } + + // Allocate temporary arrays for tangents calculation + Vector3 *tan1 = (Vector3 *)RL_CALLOC(mesh->vertexCount, sizeof(Vector3)); + Vector3 *tan2 = (Vector3 *)RL_CALLOC(mesh->vertexCount, sizeof(Vector3)); + + if (tan1 == NULL || tan2 == NULL) + { + TRACELOG(LOG_WARNING, "MESH: Failed to allocate temporary memory for tangent calculation"); + if (tan1) RL_FREE(tan1); + if (tan2) RL_FREE(tan2); + return; + } + + // Process all triangles of the mesh + // 'triangleCount' must be always valid + for (int t = 0; t < mesh->triangleCount; t++) + { + // Get triangle vertex indices + int i0 = 0, i1 = 0, i2 = 0; + + if (mesh->indices != NULL) + { + // Use indices if available + i0 = mesh->indices[t*3 + 0]; + i1 = mesh->indices[t*3 + 1]; + i2 = mesh->indices[t*3 + 2]; + } + else + { + // Sequential access for non-indexed mesh + i0 = t*3 + 0; + i1 = t*3 + 1; + i2 = t*3 + 2; + } + + // Get triangle vertices position + Vector3 v1 = { mesh->vertices[i0*3 + 0], mesh->vertices[i0*3 + 1], mesh->vertices[i0*3 + 2] }; + Vector3 v2 = { mesh->vertices[i1*3 + 0], mesh->vertices[i1*3 + 1], mesh->vertices[i1*3 + 2] }; + Vector3 v3 = { mesh->vertices[i2*3 + 0], mesh->vertices[i2*3 + 1], mesh->vertices[i2*3 + 2] }; + + // Get triangle texcoords + Vector2 uv1 = { mesh->texcoords[i0*2 + 0], mesh->texcoords[i0*2 + 1] }; + Vector2 uv2 = { mesh->texcoords[i1*2 + 0], mesh->texcoords[i1*2 + 1] }; + Vector2 uv3 = { mesh->texcoords[i2*2 + 0], mesh->texcoords[i2*2 + 1] }; + + // Calculate triangle edges + float x1 = v2.x - v1.x; + float y1 = v2.y - v1.y; + float z1 = v2.z - v1.z; + float x2 = v3.x - v1.x; + float y2 = v3.y - v1.y; + float z2 = v3.z - v1.z; + + // Calculate texture coordinate differences + float s1 = uv2.x - uv1.x; + float t1 = uv2.y - uv1.y; + float s2 = uv3.x - uv1.x; + float t2 = uv3.y - uv1.y; + + // Calculate denominator and check for degenerate UV + float div = s1*t2 - s2*t1; + float r = (fabsf(div) < 0.0001f)? 0.0f : 1.0f/div; + + // Calculate tangent and bitangent directions + Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r }; + Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r }; + + // Accumulate tangents and bitangents for each vertex of the triangle + tan1[i0] = Vector3Add(tan1[i0], sdir); + tan1[i1] = Vector3Add(tan1[i1], sdir); + tan1[i2] = Vector3Add(tan1[i2], sdir); + + tan2[i0] = Vector3Add(tan2[i0], tdir); + tan2[i1] = Vector3Add(tan2[i1], tdir); + tan2[i2] = Vector3Add(tan2[i2], tdir); + } + + // Calculate final tangents for each vertex + for (int i = 0; i < mesh->vertexCount; i++) + { + Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; + Vector3 tangent = tan1[i]; + + // Handle zero tangent (can happen with degenerate UVs) + if (Vector3Length(tangent) < 0.0001f) + { + // Create a tangent perpendicular to the normal + if (fabsf(normal.z) > 0.707f) tangent = (Vector3){ 1.0f, 0.0f, 0.0f }; + else tangent = Vector3Normalize((Vector3){ -normal.y, normal.x, 0.0f }); + + mesh->tangents[i*4 + 0] = tangent.x; + mesh->tangents[i*4 + 1] = tangent.y; + mesh->tangents[i*4 + 2] = tangent.z; + mesh->tangents[i*4 + 3] = 1.0f; + continue; + } + + // Gram-Schmidt orthogonalization to make tangent orthogonal to normal + // T_prime = T - N*dot(N, T) + Vector3 orthogonalized = Vector3Subtract(tangent, Vector3Scale(normal, Vector3DotProduct(normal, tangent))); + + // Handle cases where orthogonalized vector is too small + if (Vector3Length(orthogonalized) < 0.0001f) + { + // Create a tangent perpendicular to the normal + if (fabsf(normal.z) > 0.707f) orthogonalized = (Vector3){ 1.0f, 0.0f, 0.0f }; + else orthogonalized = Vector3Normalize((Vector3){ -normal.y, normal.x, 0.0f }); + } + else + { + // Normalize the orthogonalized tangent + orthogonalized = Vector3Normalize(orthogonalized); + } + + // Store the calculated tangent + mesh->tangents[i*4 + 0] = orthogonalized.x; + mesh->tangents[i*4 + 1] = orthogonalized.y; + mesh->tangents[i*4 + 2] = orthogonalized.z; + + // Calculate the handedness (w component) + mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, orthogonalized), tan2[i]) < 0.0f)? -1.0f : 1.0f; + } + + // Free temporary arrays + RL_FREE(tan1); + RL_FREE(tan2); + + // Update vertex buffers if available + if (mesh->vboId != NULL) + { + if (mesh->vboId[SHADER_LOC_VERTEX_TANGENT] != 0) + { + // Update existing tangent vertex buffer + rlUpdateVertexBuffer(mesh->vboId[SHADER_LOC_VERTEX_TANGENT], mesh->tangents, mesh->vertexCount*4*sizeof(float), 0); + } + else + { + // Create new tangent vertex buffer + mesh->vboId[SHADER_LOC_VERTEX_TANGENT] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), false); + } + + // Set up vertex attributes for shader + rlEnableVertexArray(mesh->vaoId); + rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT); + rlDisableVertexArray(); + } + + TRACELOG(LOG_INFO, "MESH: Tangents data computed and uploaded for provided mesh"); +} + +// Draw a model (with texture if set) +void DrawModel(Model model, Vector3 position, float scale, Color tint) +{ + Vector3 vScale = { scale, scale, scale }; + Vector3 rotationAxis = { 0.0f, 1.0f, 0.0f }; + + DrawModelEx(model, position, rotationAxis, 0.0f, vScale, tint); +} + +// Draw a model with custom transform +void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) +{ + // Calculate transformation matrix from function parameters + // Get transform matrix (rotation -> scale -> translation) + Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); + Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD); + Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z); + + Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation); + + // Combine model transformation matrix (model.transform) with matrix generated by function parameters (matTransform) + model.transform = MatrixMultiply(model.transform, matTransform); + + for (int i = 0; i < model.meshCount; i++) + { + Material mat = model.materials[model.meshMaterial[i]]; + Color colDiffuse = mat.maps[MATERIAL_MAP_DIFFUSE].color; + + // Applying color tint directly to material diffuse map, + // because it comes as an input parameter to the function + Color colTinted = { 0 }; + colTinted.r = (unsigned char)(((int)colDiffuse.r*(int)tint.r)/255); + colTinted.g = (unsigned char)(((int)colDiffuse.g*(int)tint.g)/255); + colTinted.b = (unsigned char)(((int)colDiffuse.b*(int)tint.b)/255); + colTinted.a = (unsigned char)(((int)colDiffuse.a*(int)tint.a)/255); + + mat.maps[MATERIAL_MAP_DIFFUSE].color = colTinted; + + // Upload runtime bone transforms matrices, to compute skinning on the shader (GPU-skinning) + // NOTE: Required location must be found and Mesh bones indices and weights must be also uploaded to shader + if ((mat.shader.locs != NULL) && (mat.shader.locs[SHADER_LOC_MATRIX_BONETRANSFORMS] != -1) && (model.boneMatrices != NULL)) + { + rlEnableShader(mat.shader.id); // Enable shader to set bone transform matrices + rlSetUniformMatrices(mat.shader.locs[SHADER_LOC_MATRIX_BONETRANSFORMS], model.boneMatrices, model.skeleton.boneCount); + } + + DrawMesh(model.meshes[i], mat, model.transform); + + // Restore material diffuse map color (before tint applied) + mat.maps[MATERIAL_MAP_DIFFUSE].color = colDiffuse; + } +} + +// Draw a model wires (with texture if set) +void DrawModelWires(Model model, Vector3 position, float scale, Color tint) +{ + rlEnableWireMode(); + + DrawModel(model, position, scale, tint); + + rlDisableWireMode(); +} + +// Draw a model wires with custom transform +void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) +{ + rlEnableWireMode(); + + DrawModelEx(model, position, rotationAxis, rotationAngle, scale, tint); + + rlDisableWireMode(); +} + +// Draw a billboard +void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint) +{ + Rectangle rec = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; + + DrawBillboardRec(camera, texture, rec, position, (Vector2){ scale*fabsf((float)rec.width/rec.height), scale }, tint); +} + +// Draw a billboard (part of a texture defined by a rectangle) +void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle rec, Vector3 position, Vector2 size, Color tint) +{ + // NOTE: Billboard locked on axis-Y + Vector3 up = { 0.0f, 1.0f, 0.0f }; + + DrawBillboardPro(camera, texture, rec, position, up, size, Vector2Scale(size, 0.5), 0.0f, tint); +} + +// Draw a billboard texture defined by source rectangle with scaling and rotation +void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle rec, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint) +{ + // Compute the up vector and the right vector + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + Vector3 right = { matView.m0, matView.m4, matView.m8 }; + right = Vector3Scale(right, size.x); + up = Vector3Scale(up, size.y); + + // Flip the content of the billboard while maintaining the counterclockwise edge rendering order + if (size.x < 0.0f) + { + rec.x -= size.x; + rec.width *= -1.0; + right = Vector3Negate(right); + origin.x *= -1.0f; + } + if (size.y < 0.0f) + { + rec.y -= size.y; + rec.height *= -1.0; + up = Vector3Negate(up); + origin.y *= -1.0f; + } + + // Draw the texture region described by rec on the following rectangle in 3D space: + // + // size.x <--. + // 3 ^---------------------------+ 2 \ rotation + // | | / + // | | + // | origin.x position | + // up |.............. | size.y + // | . | + // | . origin.y | + // | . | + // 0 +---------------------------> 1 + // right + Vector3 forward; + if (rotation != 0.0) forward = Vector3CrossProduct(right, up); + + Vector3 origin3D = Vector3Add(Vector3Scale(Vector3Normalize(right), origin.x), Vector3Scale(Vector3Normalize(up), origin.y)); + + Vector3 points[4]; + points[0] = Vector3Zero(); + points[1] = right; + points[2] = Vector3Add(up, right); + points[3] = up; + + for (int i = 0; i < 4; i++) + { + points[i] = Vector3Subtract(points[i], origin3D); + if (rotation != 0.0) points[i] = Vector3RotateByAxisAngle(points[i], forward, rotation*DEG2RAD); + points[i] = Vector3Add(points[i], position); + } + + Vector2 texcoords[4]; + texcoords[0] = (Vector2){ (float)rec.x/texture.width, (float)(rec.y + rec.height)/texture.height }; + texcoords[1] = (Vector2){ (float)(rec.x + rec.width)/texture.width, (float)(rec.y + rec.height)/texture.height }; + texcoords[2] = (Vector2){ (float)(rec.x + rec.width)/texture.width, (float)rec.y/texture.height }; + texcoords[3] = (Vector2){ (float)rec.x/texture.width, (float)rec.y/texture.height }; + + rlSetTexture(texture.id); + rlBegin(RL_QUADS); + + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + for (int i = 0; i < 4; i++) + { + rlTexCoord2f(texcoords[i].x, texcoords[i].y); + rlVertex3f(points[i].x, points[i].y, points[i].z); + } + + rlEnd(); + rlSetTexture(0); +} + +// Draw a bounding box with wires +void DrawBoundingBox(BoundingBox box, Color color) +{ + Vector3 size = { 0 }; + + size.x = fabsf(box.max.x - box.min.x); + size.y = fabsf(box.max.y - box.min.y); + size.z = fabsf(box.max.z - box.min.z); + + Vector3 center = { box.min.x + size.x/2.0f, box.min.y + size.y/2.0f, box.min.z + size.z/2.0f }; + + DrawCubeWires(center, size.x, size.y, size.z, color); +} + +// Check collision between two spheres +bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2) +{ + bool collision = false; + + // Simple way to check for collision, checking distance between two points + // Unfortunately, sqrtf() is a costly operation, so avoid it with following solution + /* + float dx = center1.x - center2.x; // X distance between centers + float dy = center1.y - center2.y; // Y distance between centers + float dz = center1.z - center2.z; // Z distance between centers + + float distance = sqrtf(dx*dx + dy*dy + dz*dz); // Distance between centers + + if (distance <= (radius1 + radius2)) collision = true; + */ + + // Check for distances squared to avoid sqrtf() + float radSum = radius1 + radius2; + if (Vector3DistanceSqr(center1, center2) <= (radSum*radSum)) collision = true; + + return collision; +} + +// Check collision between two boxes +// NOTE: Boxes are defined by two points minimum and maximum +bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2) +{ + bool collision = true; + + if ((box1.max.x >= box2.min.x) && (box1.min.x <= box2.max.x)) + { + if ((box1.max.y < box2.min.y) || (box1.min.y > box2.max.y)) collision = false; + if ((box1.max.z < box2.min.z) || (box1.min.z > box2.max.z)) collision = false; + } + else collision = false; + + return collision; +} + +// Check collision between box and sphere +bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius) +{ + bool collision = false; + + Vector3 closestPoint = { + Clamp(center.x, box.min.x, box.max.x), + Clamp(center.y, box.min.y, box.max.y), + Clamp(center.z, box.min.z, box.max.z) + }; + + if (Vector3DistanceSqr(center, closestPoint) <= (radius*radius)) collision = true; + + return collision; +} + +// Get collision info between ray and sphere +RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius) +{ + RayCollision collision = { 0 }; + + Vector3 raySpherePos = Vector3Subtract(center, ray.position); + float vector = Vector3DotProduct(raySpherePos, ray.direction); + float distance = Vector3Length(raySpherePos); + float d = radius*radius - (distance*distance - vector*vector); + + collision.hit = d >= 0.0f; + + // Check if ray origin is inside the sphere to calculate the correct collision point + if (distance < radius) + { + collision.distance = vector + sqrtf(d); + + // Calculate collision point + collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, collision.distance)); + + // Calculate collision normal (pointing outwards) + collision.normal = Vector3Negate(Vector3Normalize(Vector3Subtract(collision.point, center))); + } + else + { + collision.distance = vector - sqrtf(d); + + // Calculate collision point + collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, collision.distance)); + + // Calculate collision normal (pointing inwards) + collision.normal = Vector3Normalize(Vector3Subtract(collision.point, center)); + } + + return collision; +} + +// Get collision info between ray and box +RayCollision GetRayCollisionBox(Ray ray, BoundingBox box) +{ + RayCollision collision = { 0 }; + + // NOTE: If ray.position is inside the box, the distance is negative (as if the ray was reversed) + // Reversing ray.direction will give use the correct result + bool insideBox = (ray.position.x > box.min.x) && (ray.position.x < box.max.x) && + (ray.position.y > box.min.y) && (ray.position.y < box.max.y) && + (ray.position.z > box.min.z) && (ray.position.z < box.max.z); + + if (insideBox) ray.direction = Vector3Negate(ray.direction); + + float t[11] = { 0 }; + + t[8] = 1.0f/ray.direction.x; + t[9] = 1.0f/ray.direction.y; + t[10] = 1.0f/ray.direction.z; + + t[0] = (box.min.x - ray.position.x)*t[8]; + t[1] = (box.max.x - ray.position.x)*t[8]; + t[2] = (box.min.y - ray.position.y)*t[9]; + t[3] = (box.max.y - ray.position.y)*t[9]; + t[4] = (box.min.z - ray.position.z)*t[10]; + t[5] = (box.max.z - ray.position.z)*t[10]; + t[6] = (float)fmax(fmax(fmin(t[0], t[1]), fmin(t[2], t[3])), fmin(t[4], t[5])); + t[7] = (float)fmin(fmin(fmax(t[0], t[1]), fmax(t[2], t[3])), fmax(t[4], t[5])); + + collision.hit = !((t[7] < 0) || (t[6] > t[7])); + collision.distance = t[6]; + collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, collision.distance)); + + // Get box center point + collision.normal = Vector3Lerp(box.min, box.max, 0.5f); + // Get vector center point->hit point + collision.normal = Vector3Subtract(collision.point, collision.normal); + // Scale vector to unit cube + // NOTE: Use an additional .01 to fix numerical errors + collision.normal = Vector3Scale(collision.normal, 2.01f); + collision.normal = Vector3Divide(collision.normal, Vector3Subtract(box.max, box.min)); + // The relevant elements of the vector are now slightly larger than 1.0f (or smaller than -1.0f) + // and the others are somewhere between -1.0 and 1.0 casting to int is exactly our wanted normal! + collision.normal.x = (float)((int)collision.normal.x); + collision.normal.y = (float)((int)collision.normal.y); + collision.normal.z = (float)((int)collision.normal.z); + + collision.normal = Vector3Normalize(collision.normal); + + if (insideBox) + { + // Reset ray.direction + ray.direction = Vector3Negate(ray.direction); + // Fix result + collision.distance *= -1.0f; + collision.normal = Vector3Negate(collision.normal); + } + + return collision; +} + +// Get collision info between ray and mesh +RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform) +{ + RayCollision collision = { 0 }; + + // Check if mesh vertex data on CPU for testing + if (mesh.vertices != NULL) + { + int triangleCount = mesh.triangleCount; + + // Test against all triangles in mesh + for (int i = 0; i < triangleCount; i++) + { + Vector3 a = { 0 }; + Vector3 b = { 0 }; + Vector3 c = { 0 }; + Vector3 *vertdata = (Vector3 *)mesh.vertices; + + if (mesh.indices) + { + a = vertdata[mesh.indices[i*3 + 0]]; + b = vertdata[mesh.indices[i*3 + 1]]; + c = vertdata[mesh.indices[i*3 + 2]]; + } + else + { + a = vertdata[i*3 + 0]; + b = vertdata[i*3 + 1]; + c = vertdata[i*3 + 2]; + } + + a = Vector3Transform(a, transform); + b = Vector3Transform(b, transform); + c = Vector3Transform(c, transform); + + RayCollision triHitInfo = GetRayCollisionTriangle(ray, a, b, c); + + if (triHitInfo.hit) + { + // Save the closest hit triangle + if ((!collision.hit) || (collision.distance > triHitInfo.distance)) collision = triHitInfo; + } + } + } + + return collision; +} + +// Get collision info between ray and triangle +// NOTE: The points are expected to be in counter-clockwise winding +// NOTE: Based on https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm +RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3) +{ + #define EPSILON 0.000001f // A small number + + RayCollision collision = { 0 }; + Vector3 edge1 = { 0 }; + Vector3 edge2 = { 0 }; + Vector3 p = { 0 }; + Vector3 q = { 0 }; + Vector3 tv = { 0 }; + float det = 0.0f, invDet = 0.0f, u = 0.0f, v = 0.0f, t = 0.0f; + + // Find vectors for two edges sharing V1 + edge1 = Vector3Subtract(p2, p1); + edge2 = Vector3Subtract(p3, p1); + + // Begin calculating determinant - also used to calculate u parameter + p = Vector3CrossProduct(ray.direction, edge2); + + // If determinant is near zero, ray lies in plane of triangle or ray is parallel to plane of triangle + det = Vector3DotProduct(edge1, p); + + // Avoid culling! + if ((det > -EPSILON) && (det < EPSILON)) return collision; + + invDet = 1.0f/det; + + // Calculate distance from V1 to ray origin + tv = Vector3Subtract(ray.position, p1); + + // Calculate u parameter and test bound + u = Vector3DotProduct(tv, p)*invDet; + + // The intersection lies outside the triangle + if ((u < 0.0f) || (u > 1.0f)) return collision; + + // Prepare to test v parameter + q = Vector3CrossProduct(tv, edge1); + + // Calculate V parameter and test bound + v = Vector3DotProduct(ray.direction, q)*invDet; + + // The intersection lies outside the triangle + if ((v < 0.0f) || ((u + v) > 1.0f)) return collision; + + t = Vector3DotProduct(edge2, q)*invDet; + + if (t > EPSILON) + { + // Ray hit, get hit point and normal + collision.hit = true; + collision.distance = t; + collision.normal = Vector3Normalize(Vector3CrossProduct(edge1, edge2)); + collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, t)); + } + + return collision; +} + +// Get collision info between ray and quad +// NOTE: The points are expected to be in counter-clockwise winding +RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4) +{ + RayCollision collision = { 0 }; + + collision = GetRayCollisionTriangle(ray, p1, p2, p4); + + if (!collision.hit) collision = GetRayCollisionTriangle(ray, p2, p3, p4); + + return collision; +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +#if SUPPORT_FILEFORMAT_IQM || SUPPORT_FILEFORMAT_GLTF +// Build pose from parent joints +// NOTE: Required for animations loading (required by IQM and GLTF) +static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform *transforms) +{ + for (int i = 0; i < boneCount; i++) + { + if (bones[i].parent >= 0) + { + if (bones[i].parent > i) + { + TRACELOG(LOG_WARNING, "Skipping bone not topologically sorted: Bone %d has parent %d", i, bones[i].parent); + continue; + } + transforms[i].rotation = QuaternionMultiply(transforms[bones[i].parent].rotation, transforms[i].rotation); + transforms[i].scale = Vector3Multiply(transforms[i].scale, transforms[bones[i].parent].scale); + transforms[i].translation = Vector3Multiply(transforms[i].translation, transforms[bones[i].parent].scale); + transforms[i].translation = Vector3RotateByQuaternion(transforms[i].translation, transforms[bones[i].parent].rotation); + transforms[i].translation = Vector3Add(transforms[i].translation, transforms[bones[i].parent].translation); + } + } +} +#endif + +#if SUPPORT_FILEFORMAT_OBJ +// Load OBJ mesh data +// Notes to keep in mind: +// - A mesh is created for every material present in the obj file +// - The model.meshCount is therefore the materialCount returned from tinyobj +// - The mesh is automatically triangulated by tinyobj +static Model LoadOBJ(const char *fileName) +{ + tinyobj_attrib_t objAttributes = { 0 }; + tinyobj_shape_t *objShapes = NULL; + unsigned int objShapeCount = 0; + + tinyobj_material_t *objMaterials = NULL; + unsigned int objMaterialCount = 0; + + Model model = { 0 }; + model.transform = MatrixIdentity(); + + char *fileText = LoadFileText(fileName); + + if (fileText == NULL) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Unable to read obj file", fileName); + return model; + } + + char currentDir[MAX_FILEPATH_LENGTH] = { 0 }; + snprintf(currentDir, MAX_FILEPATH_LENGTH, "%s", GetWorkingDirectory()); // Save current working directory + const char *workingDir = GetDirectoryPath(fileName); // Switch to OBJ directory for material path correctness + if (CHDIR(workingDir) != 0) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir); + + unsigned int dataSize = (unsigned int)strlen(fileText); + + unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; + int ret = tinyobj_parse_obj(&objAttributes, &objShapes, &objShapeCount, &objMaterials, &objMaterialCount, fileText, dataSize, flags); + + if (ret != TINYOBJ_SUCCESS) + { + TRACELOG(LOG_WARNING, "MODEL: Unable to read obj data %s", fileName); + return model; + } + + UnloadFileText(fileText); + + unsigned int faceVertIndex = 0; + unsigned int nextShape = 1; + int lastMaterial = -1; + unsigned int meshIndex = 0; + + // Count meshes + unsigned int nextShapeEnd = objAttributes.num_face_num_verts; + + // See how many verts till the next shape + if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset; + + // Walk all the faces + for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) + { + if (faceId >= nextShapeEnd) + { + // Try to find the last vert in the next shape + nextShape++; + if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; + else nextShapeEnd = objAttributes.num_face_num_verts; // This is actually the total number of face verts in the file, not faces + meshIndex++; + } + else if ((lastMaterial != -1) && (objAttributes.material_ids[faceId] != lastMaterial)) + { + meshIndex++; // If this is a new material, a new mesh is allocated + } + + lastMaterial = objAttributes.material_ids[faceId]; + faceVertIndex += objAttributes.face_num_verts[faceId]; + } + + // Allocate the base meshes and materials + model.meshCount = meshIndex + 1; + model.meshes = (Mesh *)MemAlloc(sizeof(Mesh)*model.meshCount); + + if (objMaterialCount > 0) + { + model.materialCount = objMaterialCount; + model.materials = (Material *)MemAlloc(sizeof(Material)*objMaterialCount); + } + else // Allocate at least one material + { + model.materialCount = 1; + model.materials = (Material *)MemAlloc(sizeof(Material)*1); + } + + model.meshMaterial = (int *)MemAlloc(sizeof(int)*model.meshCount); + + // See how many verts are in each mesh + unsigned int *localMeshVertexCounts = (unsigned int *)MemAlloc(sizeof(unsigned int)*model.meshCount); + + faceVertIndex = 0; + nextShapeEnd = objAttributes.num_face_num_verts; + lastMaterial = -1; + meshIndex = 0; + unsigned int localMeshVertexCount = 0; + + nextShape = 1; + if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset; + + // Walk all the faces + for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) + { + bool newMesh = false; // Is a new mesh required? + if (faceId >= nextShapeEnd) + { + // Try to find the last vert in the next shape + nextShape++; + if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; + else nextShapeEnd = objAttributes.num_face_num_verts; // this is actually the total number of face verts in the file, not faces + + newMesh = true; + } + else if ((lastMaterial != -1) && (objAttributes.material_ids[faceId] != lastMaterial)) + { + newMesh = true; + } + + lastMaterial = objAttributes.material_ids[faceId]; + + if (newMesh) + { + localMeshVertexCounts[meshIndex] = localMeshVertexCount; + + localMeshVertexCount = 0; + meshIndex++; + } + + faceVertIndex += objAttributes.face_num_verts[faceId]; + localMeshVertexCount += objAttributes.face_num_verts[faceId]; + } + + localMeshVertexCounts[meshIndex] = localMeshVertexCount; + + for (int i = 0; i < model.meshCount; i++) + { + // Allocate the buffers for each mesh + unsigned int vertexCount = localMeshVertexCounts[i]; + + model.meshes[i].vertexCount = vertexCount; + model.meshes[i].triangleCount = vertexCount/3; + + model.meshes[i].vertices = (float *)MemAlloc(sizeof(float)*vertexCount*3); + model.meshes[i].normals = (float *)MemAlloc(sizeof(float)*vertexCount*3); + #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + model.meshes[i].texcoords = (float *)MemAlloc(sizeof(float)*vertexCount*2); + model.meshes[i].colors = (unsigned char *)MemAlloc(sizeof(unsigned char)*vertexCount*4); + #else + if (objAttributes.texcoords != NULL && objAttributes.num_texcoords > 0) + model.meshes[i].texcoords = (float *)MemAlloc(sizeof(float)*vertexCount*2); + else model.meshes[i].texcoords = NULL; + model.meshes[i].colors = NULL; + #endif + } + + MemFree(localMeshVertexCounts); + localMeshVertexCounts = NULL; + + // Fill meshes + faceVertIndex = 0; + + nextShapeEnd = objAttributes.num_face_num_verts; + + // See how many verts till the next shape + nextShape = 1; + if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset; + lastMaterial = -1; + meshIndex = 0; + localMeshVertexCount = 0; + + // Walk all the faces + for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) + { + bool newMesh = false; // Is a new mesh required? + if (faceId >= nextShapeEnd) + { + // Try to find the last vert in the next shape + nextShape++; + if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; + else nextShapeEnd = objAttributes.num_face_num_verts; // This is actually the total number of face verts in the file, not faces + newMesh = true; + } + + // If this is a new material, a new mesh is allocated + if (lastMaterial != -1 && objAttributes.material_ids[faceId] != lastMaterial) newMesh = true; + lastMaterial = objAttributes.material_ids[faceId]; + + if (newMesh) + { + localMeshVertexCount = 0; + meshIndex++; + } + + int matId = 0; + if ((lastMaterial >= 0) && (lastMaterial < (int)objMaterialCount)) matId = lastMaterial; + + model.meshMaterial[meshIndex] = matId; + + for (int f = 0; f < objAttributes.face_num_verts[faceId]; f++) + { + int vertIndex = objAttributes.faces[faceVertIndex].v_idx; + int normalIndex = objAttributes.faces[faceVertIndex].vn_idx; + int texcordIndex = objAttributes.faces[faceVertIndex].vt_idx; + + for (int i = 0; i < 3; i++) model.meshes[meshIndex].vertices[localMeshVertexCount*3 + i] = objAttributes.vertices[vertIndex*3 + i]; + + if ((objAttributes.texcoords != NULL) && (texcordIndex != TINYOBJ_INVALID_INDEX) && (texcordIndex >= 0) && (model.meshes[meshIndex].texcoords)) + { + for (int i = 0; i < 2; i++) model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + i] = objAttributes.texcoords[texcordIndex*2 + i]; + model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1] = 1.0f - model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1]; + } + + if ((objAttributes.normals != NULL) && (normalIndex != TINYOBJ_INVALID_INDEX) && (normalIndex >= 0)) + { + for (int i = 0; i < 3; i++) model.meshes[meshIndex].normals[localMeshVertexCount*3 + i] = objAttributes.normals[normalIndex*3 + i]; + } + else + { + model.meshes[meshIndex].normals[localMeshVertexCount*3 + 0] = 0.0f; + model.meshes[meshIndex].normals[localMeshVertexCount*3 + 1] = 1.0f; + model.meshes[meshIndex].normals[localMeshVertexCount*3 + 2] = 0.0f; + } + #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + for (int i = 0; i < 4; i++) model.meshes[meshIndex].colors[localMeshVertexCount*4 + i] = 255; + #endif + faceVertIndex++; + localMeshVertexCount++; + } + } + + if (objMaterialCount > 0) ProcessMaterialsOBJ(model.materials, objMaterials, objMaterialCount); + else model.materials[0] = LoadMaterialDefault(); // Set default material for the mesh + + tinyobj_attrib_free(&objAttributes); + tinyobj_shapes_free(objShapes, objShapeCount); + tinyobj_materials_free(objMaterials, objMaterialCount); + + // Restore current working directory + if (CHDIR(currentDir) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir); + } + + return model; +} +#endif + +#if SUPPORT_FILEFORMAT_IQM +// Load IQM mesh data +static Model LoadIQM(const char *fileName) +{ + #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number + #define IQM_VERSION 2 // Only IQM version 2 supported + + #define BONE_NAME_LENGTH 32 // BoneInfo name string length + #define MESH_NAME_LENGTH 32 // Mesh name string length + #define MATERIAL_NAME_LENGTH 32 // Material name string length + + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + unsigned char *fileDataPtr = fileData; + + // IQM file structs + //----------------------------------------------------------------------------------- + typedef struct IQMHeader { + char magic[16]; + unsigned int version; + unsigned int dataSize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + } IQMHeader; + + typedef struct IQMMesh { + unsigned int name; + unsigned int material; + unsigned int first_vertex, num_vertexes; + unsigned int first_triangle, num_triangles; + } IQMMesh; + + typedef struct IQMTriangle { + unsigned int vertex[3]; + } IQMTriangle; + + typedef struct IQMJoint { + unsigned int name; + int parent; + float translate[3], rotate[4], scale[3]; + } IQMJoint; + + typedef struct IQMVertexArray { + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; + } IQMVertexArray; + + // NOTE: Below IQM structures are not used but listed for reference + /* + typedef struct IQMAdjacency { + unsigned int triangle[3]; + } IQMAdjacency; + + typedef struct IQMPose { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + } IQMPose; + + typedef struct IQMAnim { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + } IQMAnim; + + typedef struct IQMBounds { + float bbmin[3], bbmax[3]; + float xyradius, radius; + } IQMBounds; + */ + //----------------------------------------------------------------------------------- + + // IQM vertex data types + enum { + IQM_POSITION = 0, + IQM_TEXCOORD = 1, + IQM_NORMAL = 2, + IQM_TANGENT = 3, // NOTE: Tangents unused by default + IQM_BLENDINDEXES = 4, + IQM_BLENDWEIGHTS = 5, + IQM_COLOR = 6, + IQM_CUSTOM = 0x10 // NOTE: Custom vertex values unused by default + }; + + Model model = { 0 }; + + IQMMesh *imesh = NULL; + IQMTriangle *tri = NULL; + IQMVertexArray *va = NULL; + IQMJoint *ijoint = NULL; + + float *vertex = NULL; + float *normal = NULL; + float *text = NULL; + char *blendi = NULL; + unsigned char *blendw = NULL; + unsigned char *color = NULL; + + // In case file can not be read, return an empty model + if (fileDataPtr == NULL) return model; + + const char *basePath = GetDirectoryPath(fileName); + + // Read IQM header + IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr; + + if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName); + UnloadFileData(fileData); + return model; + } + + if (iqmHeader->version != IQM_VERSION) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version); + UnloadFileData(fileData); + return model; + } + + //fileDataPtr += sizeof(IQMHeader); // Move file data pointer + + // Meshes data processing + imesh = (IQMMesh *)RL_MALLOC(iqmHeader->num_meshes*sizeof(IQMMesh)); + //fseek(iqmFile, iqmHeader->ofs_meshes, SEEK_SET); + //fread(imesh, sizeof(IQMMesh)*iqmHeader->num_meshes, 1, iqmFile); + memcpy(imesh, fileDataPtr + iqmHeader->ofs_meshes, iqmHeader->num_meshes*sizeof(IQMMesh)); + + model.meshCount = iqmHeader->num_meshes; + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + + model.materialCount = model.meshCount; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + + char name[MESH_NAME_LENGTH] = { 0 }; + char material[MATERIAL_NAME_LENGTH] = { 0 }; + + for (int i = 0; i < model.meshCount; i++) + { + //fseek(iqmFile, iqmHeader->ofs_text + imesh[a].name, SEEK_SET); + //fread(name, sizeof(char), MESH_NAME_LENGTH, iqmFile); + memcpy(name, fileDataPtr + iqmHeader->ofs_text + imesh[i].name, MESH_NAME_LENGTH*sizeof(char)); + + //fseek(iqmFile, iqmHeader->ofs_text + imesh[a].material, SEEK_SET); + //fread(material, sizeof(char), MATERIAL_NAME_LENGTH, iqmFile); + memcpy(material, fileDataPtr + iqmHeader->ofs_text + imesh[i].material, MATERIAL_NAME_LENGTH*sizeof(char)); + + model.materials[i] = LoadMaterialDefault(); + model.materials[i].maps[MATERIAL_MAP_ALBEDO].texture = LoadTexture(TextFormat("%s/%s", basePath, material)); + + model.meshMaterial[i] = i; + + TRACELOG(LOG_DEBUG, "MODEL: [%s] mesh name (%s), material (%s)", fileName, name, material); + + model.meshes[i].vertexCount = imesh[i].num_vertexes; + + model.meshes[i].vertices = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex positions + model.meshes[i].normals = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex normals + model.meshes[i].texcoords = (float *)RL_CALLOC(model.meshes[i].vertexCount*2, sizeof(float)); // Default vertex texcoords + + model.meshes[i].boneIndices = (unsigned char *)RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(unsigned char)); // Up-to 4 bones supported! + model.meshes[i].boneWeights = (float *)RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(float)); // Up-to 4 bones supported! + + model.meshes[i].triangleCount = imesh[i].num_triangles; + model.meshes[i].indices = (unsigned short *)RL_CALLOC(model.meshes[i].triangleCount*3, sizeof(unsigned short)); + +#if !SUPPORT_GPU_SKINNING + // Animated vertex data, processed for rendering + // NOTE: Animated vertex should be re-uploaded to GPU (if not using GPU skinning) + model.meshes[i].animVertices = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); + model.meshes[i].animNormals = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); +#endif + } + + // Triangles data processing + tri = (IQMTriangle *)RL_MALLOC(iqmHeader->num_triangles*sizeof(IQMTriangle)); + //fseek(iqmFile, iqmHeader->ofs_triangles, SEEK_SET); + //fread(tri, sizeof(IQMTriangle), iqmHeader->num_triangles, iqmFile); + memcpy(tri, fileDataPtr + iqmHeader->ofs_triangles, iqmHeader->num_triangles*sizeof(IQMTriangle)); + + for (int m = 0; m < model.meshCount; m++) + { + int tcounter = 0; + + for (unsigned int i = imesh[m].first_triangle; i < (imesh[m].first_triangle + imesh[m].num_triangles); i++) + { + // IQM triangles indexes are stored in counter-clockwise, but raylib processes the index in linear order, + // expecting they point to the counter-clockwise vertex triangle, so triangle indexes need to be reversed + // NOTE: raylib renders vertex data in counter-clockwise order (standard convention) by default + model.meshes[m].indices[tcounter + 2] = tri[i].vertex[0] - imesh[m].first_vertex; + model.meshes[m].indices[tcounter + 1] = tri[i].vertex[1] - imesh[m].first_vertex; + model.meshes[m].indices[tcounter] = tri[i].vertex[2] - imesh[m].first_vertex; + tcounter += 3; + } + } + + // Vertex arrays data processing + va = (IQMVertexArray *)RL_MALLOC(iqmHeader->num_vertexarrays*sizeof(IQMVertexArray)); + //fseek(iqmFile, iqmHeader->ofs_vertexarrays, SEEK_SET); + //fread(va, sizeof(IQMVertexArray), iqmHeader->num_vertexarrays, iqmFile); + memcpy(va, fileDataPtr + iqmHeader->ofs_vertexarrays, iqmHeader->num_vertexarrays*sizeof(IQMVertexArray)); + + for (unsigned int i = 0; i < iqmHeader->num_vertexarrays; i++) + { + switch (va[i].type) + { + case IQM_POSITION: + { + vertex = (float *)RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float)); + //fseek(iqmFile, va[a].offset, SEEK_SET); + //fread(vertex, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile); + memcpy(vertex, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int vCounter = 0; + for (unsigned int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++) + { + model.meshes[m].vertices[vCounter] = vertex[i]; + if (model.meshes[m].animVertices != NULL) model.meshes[m].animVertices[vCounter] = vertex[i]; + vCounter++; + } + } + } break; + case IQM_NORMAL: + { + normal = (float *)RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float)); + //fseek(iqmFile, va[a].offset, SEEK_SET); + //fread(normal, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile); + memcpy(normal, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int vCounter = 0; + for (unsigned int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++) + { + model.meshes[m].normals[vCounter] = normal[i]; + if (model.meshes[m].animNormals != NULL) model.meshes[m].animNormals[vCounter] = normal[i]; + vCounter++; + } + } + } break; + case IQM_TEXCOORD: + { + text = (float *)RL_MALLOC(iqmHeader->num_vertexes*2*sizeof(float)); + //fseek(iqmFile, va[a].offset, SEEK_SET); + //fread(text, iqmHeader->num_vertexes*2*sizeof(float), 1, iqmFile); + memcpy(text, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*2*sizeof(float)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int vCounter = 0; + for (unsigned int i = imesh[m].first_vertex*2; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*2; i++) + { + model.meshes[m].texcoords[vCounter] = text[i]; + vCounter++; + } + } + } break; + case IQM_BLENDINDEXES: + { + blendi = (char *)RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(char)); + //fseek(iqmFile, va[a].offset, SEEK_SET); + //fread(blendi, iqmHeader->num_vertexes*4*sizeof(char), 1, iqmFile); + memcpy(blendi, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(char)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int boneCounter = 0; + for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) + { + model.meshes[m].boneIndices[boneCounter] = blendi[i]; + boneCounter++; + } + } + } break; + case IQM_BLENDWEIGHTS: + { + blendw = (unsigned char *)RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char)); + //fseek(iqmFile, va[a].offset, SEEK_SET); + //fread(blendw, iqmHeader->num_vertexes*4*sizeof(unsigned char), 1, iqmFile); + memcpy(blendw, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(unsigned char)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + int boneCounter = 0; + for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) + { + model.meshes[m].boneWeights[boneCounter] = blendw[i]/255.0f; + boneCounter++; + } + } + } break; + case IQM_COLOR: + { + color = (unsigned char *)RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char)); + //fseek(iqmFile, va[a].offset, SEEK_SET); + //fread(blendw, iqmHeader->num_vertexes*4*sizeof(unsigned char), 1, iqmFile); + memcpy(color, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(unsigned char)); + + for (unsigned int m = 0; m < iqmHeader->num_meshes; m++) + { + model.meshes[m].colors = (unsigned char *)RL_CALLOC(model.meshes[m].vertexCount*4, sizeof(unsigned char)); + + int vCounter = 0; + for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++) + { + model.meshes[m].colors[vCounter] = color[i]; + vCounter++; + } + } + } break; + } + } + + // Bones (joints) data processing + ijoint = (IQMJoint *)RL_MALLOC(iqmHeader->num_joints*sizeof(IQMJoint)); + //fseek(iqmFile, iqmHeader->ofs_joints, SEEK_SET); + //fread(ijoint, sizeof(IQMJoint), iqmHeader->num_joints, iqmFile); + memcpy(ijoint, fileDataPtr + iqmHeader->ofs_joints, iqmHeader->num_joints*sizeof(IQMJoint)); + + model.skeleton.boneCount = iqmHeader->num_joints; + model.skeleton.bones = (BoneInfo *)RL_CALLOC(iqmHeader->num_joints, sizeof(BoneInfo)); + model.skeleton.bindPose = (Transform *)RL_CALLOC(iqmHeader->num_joints, sizeof(Transform)); + + for (unsigned int i = 0; i < iqmHeader->num_joints; i++) + { + // Bones + model.skeleton.bones[i].parent = ijoint[i].parent; + //fseek(iqmFile, iqmHeader->ofs_text + ijoint[a].name, SEEK_SET); + //fread(model.bones[a].name, sizeof(char), BONE_NAME_LENGTH, iqmFile); + memcpy(model.skeleton.bones[i].name, fileDataPtr + iqmHeader->ofs_text + ijoint[i].name, BONE_NAME_LENGTH*sizeof(char)); + + // Bind pose (base pose) + model.skeleton.bindPose[i].translation.x = ijoint[i].translate[0]; + model.skeleton.bindPose[i].translation.y = ijoint[i].translate[1]; + model.skeleton.bindPose[i].translation.z = ijoint[i].translate[2]; + + model.skeleton.bindPose[i].rotation.x = ijoint[i].rotate[0]; + model.skeleton.bindPose[i].rotation.y = ijoint[i].rotate[1]; + model.skeleton.bindPose[i].rotation.z = ijoint[i].rotate[2]; + model.skeleton.bindPose[i].rotation.w = ijoint[i].rotate[3]; + + model.skeleton.bindPose[i].scale.x = ijoint[i].scale[0]; + model.skeleton.bindPose[i].scale.y = ijoint[i].scale[1]; + model.skeleton.bindPose[i].scale.z = ijoint[i].scale[2]; + } + + BuildPoseFromParentJoints(model.skeleton.bones, model.skeleton.boneCount, model.skeleton.bindPose); + + // Initialize runtime animation data: current pose and bone matrices + model.currentPose = (Transform *)RL_CALLOC(model.skeleton.boneCount, sizeof(Transform)); + model.boneMatrices = (Matrix *)RL_CALLOC(model.skeleton.boneCount, sizeof(Matrix)); + for (unsigned int j = 0; j < model.skeleton.boneCount; j++) model.boneMatrices[j] = MatrixIdentity(); + + UnloadFileData(fileData); + + RL_FREE(imesh); + RL_FREE(tri); + RL_FREE(va); + RL_FREE(vertex); + RL_FREE(normal); + RL_FREE(text); + RL_FREE(blendi); + RL_FREE(blendw); + RL_FREE(ijoint); + RL_FREE(color); + + return model; +} + +// Load IQM animation data +static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, int *animCount) +{ + #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number + #define IQM_VERSION 2 // only IQM version 2 supported + + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + unsigned char *fileDataPtr = fileData; + + typedef struct IQMHeader { + char magic[16]; + unsigned int version; + unsigned int dataSize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + } IQMHeader; + + typedef struct IQMJoint { + unsigned int name; + int parent; + float translate[3], rotate[4], scale[3]; + } IQMJoint; + + typedef struct IQMPose { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + } IQMPose; + + typedef struct IQMAnim { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + } IQMAnim; + + // In case file can not be read, return an empty model + if (fileDataPtr == NULL) return NULL; + + // Read IQM header + IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr; + + if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName); + UnloadFileData(fileData); + return NULL; + } + + if (iqmHeader->version != IQM_VERSION) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version); + UnloadFileData(fileData); + return NULL; + } + + // Get bones data + IQMPose *poses = (IQMPose *)RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose)); + //fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET); + //fread(poses, sizeof(IQMPose), iqmHeader->num_poses, iqmFile); + memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose)); + + // Get animations data + *animCount = iqmHeader->num_anims; + IQMAnim *anim = (IQMAnim *)RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim)); + //fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET); + //fread(anim, sizeof(IQMAnim), iqmHeader->num_anims, iqmFile); + memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim)); + + ModelAnimation *animations = (ModelAnimation *)RL_CALLOC(iqmHeader->num_anims, sizeof(ModelAnimation)); + + // frameposes + unsigned short *framedata = (unsigned short *)RL_CALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels, sizeof(unsigned short)); + //fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET); + //fread(framedata, sizeof(unsigned short), iqmHeader->num_frames*iqmHeader->num_framechannels, iqmFile); + memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); + + // joints + IQMJoint *joints = (IQMJoint *)RL_CALLOC(iqmHeader->num_joints, sizeof(IQMJoint)); + memcpy(joints, fileDataPtr + iqmHeader->ofs_joints, iqmHeader->num_joints*sizeof(IQMJoint)); + + for (unsigned int a = 0; a < iqmHeader->num_anims; a++) + { + animations[a].boneCount = iqmHeader->num_poses; + BoneInfo *bones = (BoneInfo *)RL_CALLOC(iqmHeader->num_poses, sizeof(BoneInfo)); + + animations[a].keyframeCount = anim[a].num_frames; + animations[a].keyframePoses = (Transform **)RL_CALLOC(anim[a].num_frames, sizeof(Transform *)); + memcpy(animations[a].name, fileDataPtr + iqmHeader->ofs_text + anim[a].name, 32); + // TODO: Use animation framerate data? + //animations[a].framerate = anim.framerate; + + TRACELOG(LOG_INFO, "MODEL: [%s] Loaded animation: %s | Frames: %d | Framerate: %f", fileName, animations[a].name, animations[a].keyframeCount, anim[a].framerate); + + for (unsigned int j = 0; j < iqmHeader->num_poses; j++) + { + bones[j].parent = poses[j].parent; + + // NOTE: No need to store bones names, bones only required to generate keyframe poses + //if (iqmHeader->num_joints > 0) memcpy(bones[j].name, fileDataPtr + iqmHeader->ofs_text + joints[j].name, BONE_NAME_LENGTH*sizeof(char)); + //else memcpy(bones[j].name, "ANIMJOINTNAME", 13); // Default bone name otherwise + } + + for (unsigned int j = 0; j < anim[a].num_frames; j++) + animations[a].keyframePoses[j] = (Transform *)RL_MALLOC(iqmHeader->num_poses*sizeof(Transform)); + + int dcounter = anim[a].first_frame*iqmHeader->num_framechannels; + + for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) + { + for (unsigned int i = 0; i < iqmHeader->num_poses; i++) + { + animations[a].keyframePoses[frame][i].translation.x = poses[i].channeloffset[0]; + + if (poses[i].mask & 0x01) + { + animations[a].keyframePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0]; + dcounter++; + } + + animations[a].keyframePoses[frame][i].translation.y = poses[i].channeloffset[1]; + + if (poses[i].mask & 0x02) + { + animations[a].keyframePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1]; + dcounter++; + } + + animations[a].keyframePoses[frame][i].translation.z = poses[i].channeloffset[2]; + + if (poses[i].mask & 0x04) + { + animations[a].keyframePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2]; + dcounter++; + } + + animations[a].keyframePoses[frame][i].rotation.x = poses[i].channeloffset[3]; + + if (poses[i].mask & 0x08) + { + animations[a].keyframePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3]; + dcounter++; + } + + animations[a].keyframePoses[frame][i].rotation.y = poses[i].channeloffset[4]; + + if (poses[i].mask & 0x10) + { + animations[a].keyframePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4]; + dcounter++; + } + + animations[a].keyframePoses[frame][i].rotation.z = poses[i].channeloffset[5]; + + if (poses[i].mask & 0x20) + { + animations[a].keyframePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5]; + dcounter++; + } + + animations[a].keyframePoses[frame][i].rotation.w = poses[i].channeloffset[6]; + + if (poses[i].mask & 0x40) + { + animations[a].keyframePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6]; + dcounter++; + } + + animations[a].keyframePoses[frame][i].scale.x = poses[i].channeloffset[7]; + + if (poses[i].mask & 0x80) + { + animations[a].keyframePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7]; + dcounter++; + } + + animations[a].keyframePoses[frame][i].scale.y = poses[i].channeloffset[8]; + + if (poses[i].mask & 0x100) + { + animations[a].keyframePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8]; + dcounter++; + } + + animations[a].keyframePoses[frame][i].scale.z = poses[i].channeloffset[9]; + + if (poses[i].mask & 0x200) + { + animations[a].keyframePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9]; + dcounter++; + } + + animations[a].keyframePoses[frame][i].rotation = QuaternionNormalize(animations[a].keyframePoses[frame][i].rotation); + } + } + + // Build frameposes + for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) + { + for (unsigned int i = 0; i < animations[a].boneCount; i++) + { + if (bones[i].parent >= 0) + { + animations[a].keyframePoses[frame][i].rotation = QuaternionMultiply(animations[a].keyframePoses[frame][bones[i].parent].rotation, animations[a].keyframePoses[frame][i].rotation); + animations[a].keyframePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].keyframePoses[frame][i].translation, animations[a].keyframePoses[frame][bones[i].parent].rotation); + animations[a].keyframePoses[frame][i].translation = Vector3Add(animations[a].keyframePoses[frame][i].translation, animations[a].keyframePoses[frame][bones[i].parent].translation); + animations[a].keyframePoses[frame][i].scale = Vector3Multiply(animations[a].keyframePoses[frame][i].scale, animations[a].keyframePoses[frame][bones[i].parent].scale); + } + } + } + + RL_FREE(bones); + } + + UnloadFileData(fileData); + + RL_FREE(joints); + RL_FREE(framedata); + RL_FREE(poses); + RL_FREE(anim); + + return animations; +} + +#endif + +#if SUPPORT_FILEFORMAT_GLTF +// Load file data callback for cgltf +static cgltf_result LoadFileGLTFCallback(const struct cgltf_memory_options *memoryOptions, const struct cgltf_file_options *fileOptions, const char *path, cgltf_size *size, void **data) +{ + int filesize; + unsigned char *filedata = LoadFileData(path, &filesize); + + if (filedata == NULL) return cgltf_result_io_error; + + *size = filesize; + *data = filedata; + + return cgltf_result_success; +} + +// Release file data callback for cgltf +static void ReleaseFileGLTFCallback(const struct cgltf_memory_options *memoryOptions, const struct cgltf_file_options *fileOptions, void *data, cgltf_size size) +{ + UnloadFileData((unsigned char *)data); +} + +// Load image from different glTF provided methods (uri, path, buffer_view) +static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPath) +{ + Image image = { 0 }; + + if (cgltfImage == NULL) return image; + + if (cgltfImage->uri != NULL) // Check if image data is provided as an uri (base64 or path) + { + if ((strlen(cgltfImage->uri) > 5) && + (cgltfImage->uri[0] == 'd') && + (cgltfImage->uri[1] == 'a') && + (cgltfImage->uri[2] == 't') && + (cgltfImage->uri[3] == 'a') && + (cgltfImage->uri[4] == ':')) // Check if image is provided as base64 text data + { + // Data URI Format: data:;base64, + + // Find the comma + int i = 0; + while ((cgltfImage->uri[i] != ',') && (cgltfImage->uri[i] != 0)) i++; + + if (cgltfImage->uri[i] == 0) TRACELOG(LOG_WARNING, "IMAGE: glTF data URI is not a valid image"); + else + { + int base64Size = (int)strlen(cgltfImage->uri + i + 1); + while (cgltfImage->uri[i + base64Size] == '=') base64Size--; // Ignore optional paddings + int numberOfEncodedBits = base64Size*6 - (base64Size*6)%8; // Encoded bits minus extra bits, so it becomes a multiple of 8 bits + int outSize = numberOfEncodedBits >> 3; // Actual encoded bytes + void *data = NULL; + + cgltf_options options = { 0 }; + options.file.read = LoadFileGLTFCallback; + options.file.release = ReleaseFileGLTFCallback; + cgltf_result result = cgltf_load_buffer_base64(&options, outSize, cgltfImage->uri + i + 1, &data); + + if (result == cgltf_result_success) + { + image = LoadImageFromMemory(".png", (unsigned char *)data, outSize); + RL_FREE(data); + } + } + } + else // Check if image is provided as image path + { + image = LoadImage(TextFormat("%s/%s", texPath, cgltfImage->uri)); + } + } + else if ((cgltfImage->buffer_view != NULL) && (cgltfImage->buffer_view->buffer->data != NULL)) // Check if image is provided as data buffer + { + unsigned char *data = (unsigned char *)RL_MALLOC(cgltfImage->buffer_view->size); + int offset = (int)cgltfImage->buffer_view->offset; + int stride = (int)cgltfImage->buffer_view->stride? (int)cgltfImage->buffer_view->stride : 1; + + // Copy buffer data to memory for loading + for (unsigned int i = 0; i < cgltfImage->buffer_view->size; i++) + { + data[i] = ((unsigned char *)cgltfImage->buffer_view->buffer->data)[offset]; + offset += stride; + } + + // Check mime_type for image: (cgltfImage->mime_type == "image/png") + // NOTE: Detected that some models define mime_type as "image\\/png" + if ((strcmp(cgltfImage->mime_type, "image\\/png") == 0) || (strcmp(cgltfImage->mime_type, "image/png") == 0)) + { + image = LoadImageFromMemory(".png", data, (int)cgltfImage->buffer_view->size); + } + else if ((strcmp(cgltfImage->mime_type, "image\\/jpeg") == 0) || (strcmp(cgltfImage->mime_type, "image/jpeg") == 0)) + { + image = LoadImageFromMemory(".jpg", data, (int)cgltfImage->buffer_view->size); + } + else TRACELOG(LOG_WARNING, "MODEL: glTF image data MIME type not recognized", TextFormat("%s/%s", texPath, cgltfImage->uri)); + + RL_FREE(data); + } + + return image; +} + +// Load bone info from GLTF skin data +static BoneInfo *LoadBoneInfoGLTF(cgltf_skin skin, unsigned int *boneCount) +{ + *boneCount = (unsigned int)skin.joints_count; + BoneInfo *bones = (BoneInfo *)RL_CALLOC(skin.joints_count, sizeof(BoneInfo)); + + for (unsigned int i = 0; i < skin.joints_count; i++) + { + cgltf_node node = *skin.joints[i]; + if (node.name != NULL) snprintf(bones[i].name, sizeof(bones[i].name), "%s", node.name); + + // Find parent bone index by walking up the node tree past any + // non-joint ancestors (intermediate transform nodes used by some + // DCC exporters), until we hit a node that is also in skin.joints. + int parentIndex = -1; + cgltf_node *ancestor = node.parent; + while (ancestor != NULL && parentIndex == -1) + { + for (unsigned int j = 0; j < skin.joints_count; j++) + { + if (skin.joints[j] == ancestor) { parentIndex = (int)j; break; } + } + if (parentIndex == -1) ancestor = ancestor->parent; + } + + bones[i].parent = parentIndex; + } + + return bones; +} + +// Load glTF file into model struct, .gltf and .glb supported +static Model LoadGLTF(const char *fileName) +{ + /********************************************************************************************* + + Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend) + Transform handling implemented by Paul Melis (@paulmelis) + Reviewed by Ramon Santamaria (@raysan5) + + FEATURES: + - Supports .gltf and .glb files + - Supports embedded (base64) or external textures + - Supports PBR metallic/roughness flow, loads material textures, values and colors + PBR specular/glossiness flow and extended texture flows not supported + - Supports multiple meshes per model (every primitives is loaded as a separate mesh) + - Supports basic animations + - Transforms, including parent-child relations, are applied on the mesh data, + but the hierarchy is not kept (as it can't be represented) + - Mesh instances in the glTF file (a.e. same mesh linked from multiple nodes) + are turned into separate raylib Meshes + + RESTRICTIONS: + - Only triangle meshes supported + - Vertex attribute types and formats supported: + > Vertices (position): vec3: float + > Normals: vec3: float + > Texcoords: vec2: float + > Colors: vec4: u8, u16, f32 (normalized) + > Indices: u16, u32 (truncated to u16) + - Scenes defined in the glTF file are ignored. All nodes in the file are used + + ***********************************************************************************************/ + + // Macro to simplify attributes loading code + #define LOAD_ATTRIBUTE(accesor, numComp, srcType, dstPtr) LOAD_ATTRIBUTE_CAST(accesor, numComp, srcType, dstPtr, srcType) + + #define LOAD_ATTRIBUTE_CAST(accesor, numComp, srcType, dstPtr, dstType) \ + { \ + int n = 0; \ + srcType *buffer = (srcType *)accesor->buffer_view->buffer->data + accesor->buffer_view->offset/sizeof(srcType) + accesor->offset/sizeof(srcType); \ + for (unsigned int k = 0; k < accesor->count; k++) \ + {\ + for (int l = 0; l < numComp; l++) \ + {\ + dstPtr[numComp*k + l] = (dstType)buffer[n + l];\ + }\ + n += (int)(accesor->stride/sizeof(srcType));\ + }\ + } + + Model model = { 0 }; + + // glTF file loading + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData == NULL) return model; + + // glTF data loading + cgltf_options options = { 0 }; + options.file.read = LoadFileGLTFCallback; + options.file.release = ReleaseFileGLTFCallback; + cgltf_data *data = NULL; + cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); + + if (result == cgltf_result_success) + { + if (data->file_type == cgltf_file_type_glb) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glb) loaded successfully", fileName); + else if (data->file_type == cgltf_file_type_gltf) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glTF) loaded successfully", fileName); + else TRACELOG(LOG_WARNING, "MODEL: [%s] Model format not recognized", fileName); + + TRACELOG(LOG_INFO, " > Meshes count: %i", data->meshes_count); + TRACELOG(LOG_INFO, " > Materials count: %i (+1 default)", data->materials_count); + TRACELOG(LOG_DEBUG, " > Buffers count: %i", data->buffers_count); + TRACELOG(LOG_DEBUG, " > Images count: %i", data->images_count); + TRACELOG(LOG_DEBUG, " > Textures count: %i", data->textures_count); + + // Force reading data buffers (fills buffer_view->buffer->data) + // NOTE: If an uri is defined to base64 data or external path, it's automatically loaded + result = cgltf_load_buffers(&options, data, fileName); + if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName); + + int primitivesCount = 0; + bool dracoCompression = false; + + // NOTE: Load every primitive in the glTF as a separate raylib Mesh + // Determine total number of meshes needed from the node hierarchy + for (unsigned int i = 0; i < data->nodes_count; i++) + { + cgltf_node *node = &(data->nodes[i]); + cgltf_mesh *mesh = node->mesh; + if (!mesh) continue; + + for (unsigned int p = 0; p < mesh->primitives_count; p++) + { + if (mesh->primitives[p].has_draco_mesh_compression) + { + dracoCompression = true; + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load mesh data, Draco compression not supported", fileName); + break; + } + else if (mesh->primitives[p].type == cgltf_primitive_type_triangles) primitivesCount++; + } + } + + if (dracoCompression) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); + cgltf_free(data); + UnloadFileData(fileData); + return model; + } + + TRACELOG(LOG_DEBUG, " > Primitives (triangles only) count based on hierarchy : %i", primitivesCount); + + // Load our model data: meshes and materials + model.meshCount = primitivesCount; + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + + // NOTE: Keep an extra slot for default material, in case some mesh requires it + model.materialCount = (int)data->materials_count + 1; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); // Load default material (index: 0) + + // Load mesh-material indices, by default all meshes are mapped to material index: 0 + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + + // Load materials data + //---------------------------------------------------------------------------------------------------- + for (unsigned int i = 0, j = 1; i < data->materials_count; i++, j++) + { + model.materials[j] = LoadMaterialDefault(); + const char *texPath = GetDirectoryPath(fileName); + + // Check glTF material flow: PBR metallic/roughness flow + // NOTE: Alternatively, materials can follow PBR specular/glossiness flow + if (data->materials[i].has_pbr_metallic_roughness) + { + // Load base color texture (albedo) + if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture) + { + Image imAlbedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath); + if (imAlbedo.data != NULL) + { + model.materials[j].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(imAlbedo); + UnloadImage(imAlbedo); + } + } + // Load base color factor (tint) + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255); + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255); + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255); + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255); + + // Load metallic/roughness texture + if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) + { + Image imMetallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath); + if (imMetallicRoughness.data != NULL) + { + Image imMetallic = { 0 }; + Image imRoughness = { 0 }; + + imMetallic.data = RL_MALLOC(imMetallicRoughness.width*imMetallicRoughness.height); + imRoughness.data = RL_MALLOC(imMetallicRoughness.width*imMetallicRoughness.height); + + imMetallic.width = imRoughness.width = imMetallicRoughness.width; + imMetallic.height = imRoughness.height = imMetallicRoughness.height; + + imMetallic.format = imRoughness.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + imMetallic.mipmaps = imRoughness.mipmaps = 1; + + for (int x = 0; x < imRoughness.width; x++) + { + for (int y = 0; y < imRoughness.height; y++) + { + Color color = GetImageColor(imMetallicRoughness, x, y); + + ((unsigned char *)imRoughness.data)[y*imRoughness.width + x] = color.g; // Roughness color channel + ((unsigned char *)imMetallic.data)[y*imMetallic.width + x] = color.b; // Metallic color channel + } + } + + model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(imRoughness); + model.materials[j].maps[MATERIAL_MAP_METALNESS].texture = LoadTextureFromImage(imMetallic); + + UnloadImage(imRoughness); + UnloadImage(imMetallic); + UnloadImage(imMetallicRoughness); + } + + // Load metallic/roughness material properties + float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor; + model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].value = roughness; + + float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor; + model.materials[j].maps[MATERIAL_MAP_METALNESS].value = metallic; + } + + // Load normal texture + if (data->materials[i].normal_texture.texture) + { + Image imNormal = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath); + if (imNormal.data != NULL) + { + model.materials[j].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(imNormal); + UnloadImage(imNormal); + } + } + + // Load ambient occlusion texture + if (data->materials[i].occlusion_texture.texture) + { + Image imOcclusion = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath); + if (imOcclusion.data != NULL) + { + model.materials[j].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(imOcclusion); + UnloadImage(imOcclusion); + } + } + + // Load emissive texture + if (data->materials[i].emissive_texture.texture) + { + Image imEmissive = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath); + if (imEmissive.data != NULL) + { + model.materials[j].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(imEmissive); + UnloadImage(imEmissive); + } + + // Load emissive color factor + model.materials[j].maps[MATERIAL_MAP_EMISSION].color.r = (unsigned char)(data->materials[i].emissive_factor[0]*255); + model.materials[j].maps[MATERIAL_MAP_EMISSION].color.g = (unsigned char)(data->materials[i].emissive_factor[1]*255); + model.materials[j].maps[MATERIAL_MAP_EMISSION].color.b = (unsigned char)(data->materials[i].emissive_factor[2]*255); + model.materials[j].maps[MATERIAL_MAP_EMISSION].color.a = 255; + } + } + + // Other possible materials not supported by raylib pipeline: + // has_clearcoat, has_transmission, has_volume, has_ior, has specular, has_sheen + } + //---------------------------------------------------------------------------------------------------- + + // Load meshes data + // + // NOTE: Visit each node in the hierarchy and process any mesh linked from it + // - Each primitive within a glTF node becomes a raylib Mesh + // - The local-to-world transform of each node is used to transform the points/normals/tangents of the created Mesh(es) + // - Any glTF mesh linked from more than one Node (a.e. instancing) is turned into multiple Mesh's, as each Node will have its own transform applied + // + // WARNING: The code below disregards the scenes defined in the file, all nodes are used + //---------------------------------------------------------------------------------------------------- + int meshIndex = 0; + for (unsigned int i = 0; i < data->nodes_count; i++) + { + cgltf_node *node = &(data->nodes[i]); + + cgltf_mesh *mesh = node->mesh; + if (!mesh) continue; + + cgltf_float worldTransform[16]; + cgltf_node_transform_world(node, worldTransform); + + Matrix worldMatrix = { + worldTransform[0], worldTransform[4], worldTransform[8], worldTransform[12], + worldTransform[1], worldTransform[5], worldTransform[9], worldTransform[13], + worldTransform[2], worldTransform[6], worldTransform[10], worldTransform[14], + worldTransform[3], worldTransform[7], worldTransform[11], worldTransform[15] + }; + + Matrix worldMatrixNormals = MatrixTranspose(MatrixInvert(worldMatrix)); + + for (unsigned int p = 0; p < mesh->primitives_count; p++) + { + // NOTE: Only support primitives defined by triangles + // Other alternatives: points, lines, line_strip, triangle_strip + if (mesh->primitives[p].type != cgltf_primitive_type_triangles) continue; + + // NOTE: Attributes data could be provided in several data formats (8, 8u, 16u, 32...), + // Only some formats for each attribute type are supported, read info at the top of this function! + + for (unsigned int j = 0; j < mesh->primitives[p].attributes_count; j++) + { + // Check the different attributes for every primitive + if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_position) // POSITION, vec3, float + { + cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; + + // WARNING: SPECS: POSITION accessor MUST have its min and max properties defined + + if (model.meshes[meshIndex].vertices != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data already loaded", fileName); + else + { + if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_32f)) + { + // Init raylib mesh vertices to copy glTF attribute data + model.meshes[meshIndex].vertexCount = (int)attribute->count; + model.meshes[meshIndex].vertices = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); + + // Load 3 components of float data type into mesh.vertices + LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].vertices) + + // Transform the vertices + float *vertices = model.meshes[meshIndex].vertices; + for (unsigned int k = 0; k < attribute->count; k++) + { + Vector3 vt = Vector3Transform((Vector3){ vertices[3*k], vertices[3*k+1], vertices[3*k+2] }, worldMatrix); + vertices[3*k] = vt.x; + vertices[3*k+1] = vt.y; + vertices[3*k+2] = vt.z; + } + } + else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_16u)) + { + // Init raylib mesh vertices to copy glTF attribute data + model.meshes[meshIndex].vertexCount = (int)attribute->count; + model.meshes[meshIndex].vertices = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*3*sizeof(unsigned short)); + LOAD_ATTRIBUTE(attribute, 3, unsigned short, temp); + + // Convert data to raylib vertex data type (float) the matrix will scale it to the correct size as a float + for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t]; + + RL_FREE(temp); + + // Transform the vertices + float *vertices = model.meshes[meshIndex].vertices; + for (unsigned int k = 0; k < attribute->count; k++) + { + Vector3 vt = Vector3Transform((Vector3){ vertices[3*k], vertices[3*k + 1], vertices[3*k + 2] }, worldMatrix); + vertices[3*k] = vt.x; + vertices[3*k + 1] = vt.y; + vertices[3*k + 2] = vt.z; + } + } + else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_16)) + { + // Init raylib mesh vertices to copy glTF attribute data + model.meshes[meshIndex].vertexCount = (int)attribute->count; + model.meshes[meshIndex].vertices = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + short *temp = (short *)RL_MALLOC(attribute->count*3*sizeof(short)); + LOAD_ATTRIBUTE(attribute, 3, short, temp); + + // Convert data to raylib vertex data type (float) the matrix will scale it to the correct size as a float + for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t]; + + RL_FREE(temp); + + // Transform the vertices + float *vertices = model.meshes[meshIndex].vertices; + for (unsigned int k = 0; k < attribute->count; k++) + { + Vector3 vt = Vector3Transform((Vector3){ vertices[3*k], vertices[3*k + 1], vertices[3*k + 2] }, worldMatrix); + vertices[3*k] = vt.x; + vertices[3*k + 1] = vt.y; + vertices[3*k + 2] = vt.z; + } + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data format not supported, use vec3 float", fileName); + } + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_normal) // NORMAL, vec3, float + { + cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; + + if (model.meshes[meshIndex].normals != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Normals attribute data already loaded", fileName); + else + { + if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_32f)) + { + // Init raylib mesh normals to copy glTF attribute data + model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); + + // Load 3 components of float data type into mesh.normals + LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].normals) + + // Transform the normals + float *normals = model.meshes[meshIndex].normals; + for (unsigned int k = 0; k < attribute->count; k++) + { + Vector3 nt = Vector3Transform((Vector3){ normals[3*k], normals[3*k+1], normals[3*k+2] }, worldMatrixNormals); + normals[3*k] = nt.x; + normals[3*k+1] = nt.y; + normals[3*k+2] = nt.z; + } + } + else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_16)) + { + // Init raylib mesh normals to copy glTF attribute data + model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + short *temp = (short *)RL_MALLOC(attribute->count*3*sizeof(short)); + LOAD_ATTRIBUTE(attribute, 3, short, temp); + + // Convert data to raylib normal data type (float) + for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; + + RL_FREE(temp); + + // Transform the normals + float *normals = model.meshes[meshIndex].normals; + for (unsigned int k = 0; k < attribute->count; k++) + { + Vector3 nt = Vector3Normalize(Vector3Transform((Vector3){ normals[3*k], normals[3*k + 1], normals[3*k + 2] }, worldMatrixNormals)); + normals[3*k] = nt.x; + normals[3*k + 1] = nt.y; + normals[3*k + 2] = nt.z; + } + } + else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_8u)) + { + // Init raylib mesh normals to copy glTF attribute data + model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*3*sizeof(unsigned char)); + LOAD_ATTRIBUTE(attribute, 3, unsigned char, temp); + + // Convert data to raylib normal data type (float) + for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; + + RL_FREE(temp); + + // Transform the normals + float *normals = model.meshes[meshIndex].normals; + for (unsigned int k = 0; k < attribute->count; k++) + { + Vector3 nt = Vector3Normalize(Vector3Transform((Vector3){ normals[3*k], normals[3*k + 1], normals[3*k + 2] }, worldMatrixNormals)); + normals[3*k] = nt.x; + normals[3*k + 1] = nt.y; + normals[3*k + 2] = nt.z; + } + } + else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_8)) + { + // Init raylib mesh normals to copy glTF attribute data + model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + char *temp = (char *)RL_MALLOC(attribute->count*3*sizeof(char)); + LOAD_ATTRIBUTE(attribute, 3, char, temp); + + // Convert data to raylib normal data type (float) + for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; + + RL_FREE(temp); + + // Transform the normals + float *normals = model.meshes[meshIndex].normals; + for (unsigned int k = 0; k < attribute->count; k++) + { + Vector3 nt = Vector3Normalize(Vector3Transform((Vector3){ normals[3*k], normals[3*k + 1], normals[3*k + 2] }, worldMatrixNormals)); + normals[3*k] = nt.x; + normals[3*k + 1] = nt.y; + normals[3*k + 2] = nt.z; + } + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Normals attribute data format not supported, use vec3 float", fileName); + } + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_tangent) // TANGENT, vec4, float, w is tangent basis sign + { + cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; + + if (model.meshes[meshIndex].tangents != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Tangents attribute data already loaded", fileName); + else + { + if ((attribute->type == cgltf_type_vec4) && (attribute->component_type == cgltf_component_type_r_32f)) + { + // Init raylib mesh tangent to copy glTF attribute data + model.meshes[meshIndex].tangents = (float *)RL_MALLOC(attribute->count*4*sizeof(float)); + + // Load 4 components of float data type into mesh.tangents + LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].tangents) + + // Transform the tangents + float *tangents = model.meshes[meshIndex].tangents; + for (unsigned int k = 0; k < attribute->count; k++) + { + Vector3 tt = Vector3Transform((Vector3){ tangents[4*k], tangents[4*k+1], tangents[4*k+2] }, worldMatrix); + tangents[4*k] = tt.x; + tangents[4*k+1] = tt.y; + tangents[4*k+2] = tt.z; + } + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Tangents attribute data format not supported, use vec4 float", fileName); + } + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) // TEXCOORD_n, vec2, float/u8n/u16n + { + // Support up to 2 texture coordinates attributes + float *texcoordPtr = NULL; + + cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; + + if (attribute->type == cgltf_type_vec2) + { + if (attribute->component_type == cgltf_component_type_r_32f) // vec2, float + { + // Init raylib mesh texcoords to copy glTF attribute data + texcoordPtr = (float *)RL_MALLOC(attribute->count*2*sizeof(float)); + + // Load 3 components of float data type into mesh.texcoords + LOAD_ATTRIBUTE(attribute, 2, float, texcoordPtr) + } + else if (attribute->component_type == cgltf_component_type_r_8u) // vec2, u8n + { + // Init raylib mesh texcoords to copy glTF attribute data + texcoordPtr = (float *)RL_MALLOC(attribute->count*2*sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*2*sizeof(unsigned char)); + LOAD_ATTRIBUTE(attribute, 2, unsigned char, temp); + + // Convert data to raylib texcoord data type (float) + for (unsigned int t = 0; t < attribute->count*2; t++) texcoordPtr[t] = (float)temp[t]/255.0f; + + RL_FREE(temp); + } + else if (attribute->component_type == cgltf_component_type_r_16u) // vec2, u16n + { + // Init raylib mesh texcoords to copy glTF attribute data + texcoordPtr = (float *)RL_MALLOC(attribute->count*2*sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*2*sizeof(unsigned short)); + LOAD_ATTRIBUTE(attribute, 2, unsigned short, temp); + + // Convert data to raylib texcoord data type (float) + for (unsigned int t = 0; t < attribute->count*2; t++) texcoordPtr[t] = (float)temp[t]/65535.0f; + + RL_FREE(temp); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported", fileName); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported, use vec2 float", fileName); + + int index = mesh->primitives[p].attributes[j].index; + if (index == 0) model.meshes[meshIndex].texcoords = texcoordPtr; + else if (index == 1) model.meshes[meshIndex].texcoords2 = texcoordPtr; + else + { + TRACELOG(LOG_WARNING, "MODEL: [%s] No more than 2 texture coordinates attributes supported", fileName); + if (texcoordPtr != NULL) RL_FREE(texcoordPtr); + } + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_color) // COLOR_n, vec3/vec4, float/u8n/u16n + { + cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; + + // WARNING: SPECS: All components of each COLOR_n accessor element MUST be clamped to [0.0, 1.0] range + + if (model.meshes[meshIndex].colors != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Colors attribute data already loaded", fileName); + else + { + if (attribute->type == cgltf_type_vec3) // RGB + { + if (attribute->component_type == cgltf_component_type_r_8u) + { + // Init raylib mesh color to copy glTF attribute data + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*3*sizeof(unsigned char)); + LOAD_ATTRIBUTE(attribute, 3, unsigned char, temp); + + // Convert data to raylib color data type (4 bytes) + for (unsigned int c = 0, k = 0; c < (attribute->count*4 - 3); c += 4, k += 3) + { + model.meshes[meshIndex].colors[c] = temp[k]; + model.meshes[meshIndex].colors[c + 1] = temp[k + 1]; + model.meshes[meshIndex].colors[c + 2] = temp[k + 2]; + model.meshes[meshIndex].colors[c + 3] = 255; + } + + RL_FREE(temp); + } + else if (attribute->component_type == cgltf_component_type_r_16u) + { + // Init raylib mesh color to copy glTF attribute data + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*3*sizeof(unsigned short)); + LOAD_ATTRIBUTE(attribute, 3, unsigned short, temp); + + // Convert data to raylib color data type (4 bytes) + for (unsigned int c = 0, k = 0; c < (attribute->count*4 - 3); c += 4, k += 3) + { + model.meshes[meshIndex].colors[c] = (unsigned char)(((float)temp[k]/65535.0f)*255.0f); + model.meshes[meshIndex].colors[c + 1] = (unsigned char)(((float)temp[k + 1]/65535.0f)*255.0f); + model.meshes[meshIndex].colors[c + 2] = (unsigned char)(((float)temp[k + 2]/65535.0f)*255.0f); + model.meshes[meshIndex].colors[c + 3] = 255; + } + + RL_FREE(temp); + } + else if (attribute->component_type == cgltf_component_type_r_32f) + { + // Init raylib mesh color to copy glTF attribute data + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + + // Load data into a temp buffer to be converted to raylib data type + float *temp = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); + LOAD_ATTRIBUTE(attribute, 3, float, temp); + + // Convert data to raylib color data type (4 bytes) + for (unsigned int c = 0, k = 0; c < (attribute->count*4 - 3); c += 4, k += 3) + { + model.meshes[meshIndex].colors[c] = (unsigned char)(temp[k]*255.0f); + model.meshes[meshIndex].colors[c + 1] = (unsigned char)(temp[k + 1]*255.0f); + model.meshes[meshIndex].colors[c + 2] = (unsigned char)(temp[k + 2]*255.0f); + model.meshes[meshIndex].colors[c + 3] = 255; + } + + RL_FREE(temp); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName); + } + else if (attribute->type == cgltf_type_vec4) // RGBA + { + if (attribute->component_type == cgltf_component_type_r_8u) + { + // Init raylib mesh color to copy glTF attribute data + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + + // Load 4 components of unsigned char data type into mesh.colors + LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[meshIndex].colors) + } + else if (attribute->component_type == cgltf_component_type_r_16u) + { + // Init raylib mesh color to copy glTF attribute data + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*4*sizeof(unsigned short)); + LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp); + + // Convert data to raylib color data type (4 bytes) + for (unsigned int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (unsigned char)(((float)temp[c]/65535.0f)*255.0f); + + RL_FREE(temp); + } + else if (attribute->component_type == cgltf_component_type_r_32f) + { + // Init raylib mesh color to copy glTF attribute data + model.meshes[meshIndex].colors = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + + // Load data into a temp buffer to be converted to raylib data type + float *temp = (float *)RL_MALLOC(attribute->count*4*sizeof(float)); + LOAD_ATTRIBUTE(attribute, 4, float, temp); + + // Convert data to raylib color data type (4 bytes), color data must be normalized + for (unsigned int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (unsigned char)(temp[c]*255.0f); + + RL_FREE(temp); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName); + } + } + + // NOTE: Attributes related to animations data are processed after mesh data loading + } + + // Load primitive indices data (if provided) + if ((mesh->primitives[p].indices != NULL) && (mesh->primitives[p].indices->buffer_view != NULL)) + { + cgltf_accessor *attribute = mesh->primitives[p].indices; + + model.meshes[meshIndex].triangleCount = (int)attribute->count/3; + + if (model.meshes[meshIndex].indices != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Indices attribute data already loaded", fileName); + else + { + if (attribute->component_type == cgltf_component_type_r_16u) + { + // Init raylib mesh indices to copy glTF attribute data + model.meshes[meshIndex].indices = (unsigned short *)RL_MALLOC(attribute->count*sizeof(unsigned short)); + + // Load unsigned short data type into mesh.indices + LOAD_ATTRIBUTE(attribute, 1, unsigned short, model.meshes[meshIndex].indices) + } + else if (attribute->component_type == cgltf_component_type_r_8u) + { + // Init raylib mesh indices to copy glTF attribute data + model.meshes[meshIndex].indices = (unsigned short *)RL_MALLOC(attribute->count*sizeof(unsigned short)); + LOAD_ATTRIBUTE_CAST(attribute, 1, unsigned char, model.meshes[meshIndex].indices, unsigned short) + + } + else if (attribute->component_type == cgltf_component_type_r_32u) + { + // Init raylib mesh indices to copy glTF attribute data + model.meshes[meshIndex].indices = (unsigned short *)RL_MALLOC(attribute->count*sizeof(unsigned short)); + LOAD_ATTRIBUTE_CAST(attribute, 1, unsigned int, model.meshes[meshIndex].indices, unsigned short); + + TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data converted from u32 to u16, possible loss of data", fileName); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data format not supported, use u16", fileName); + } + } + else model.meshes[meshIndex].triangleCount = model.meshes[meshIndex].vertexCount/3; // Unindexed mesh + + // Assign to the primitive mesh the corresponding material index + // NOTE: If no material defined, mesh uses the already assigned default material (index: 0) + for (unsigned int m = 0; m < data->materials_count; m++) + { + // The primitive actually keeps the pointer to the corresponding material, + // raylib instead assigns to the mesh the by its index, as loaded in model.materials array + // To get the index, check if material pointers match, and assign the corresponding index, + // skipping index 0, the default material + if (&data->materials[m] == mesh->primitives[p].material) + { + model.meshMaterial[meshIndex] = m + 1; + break; + } + } + + meshIndex++; // Move to next mesh + } + } + //---------------------------------------------------------------------------------------------------- + + // Load animation data + // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins + // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes + // + // LIMITATIONS: + // - Only supports 1 armature per file, and skips loading it if there are multiple armatures + // - Only supports linear interpolation (default method in Blender when checked "Always Sample Animations" when exporting a GLTF file) + // - Only supports translation/rotation/scale animation channel.path, weights not considered (a.e. morph targets) + //---------------------------------------------------------------------------------------------------- + if (data->skins_count > 0) + { + cgltf_skin skin = data->skins[0]; + model.skeleton.bones = LoadBoneInfoGLTF(skin, &model.skeleton.boneCount); + model.skeleton.bindPose = (Transform *)RL_CALLOC(model.skeleton.boneCount, sizeof(Transform)); + + for (unsigned int i = 0; i < model.skeleton.boneCount; i++) + { + Matrix bindMatrix = { 0 }; + cgltf_float inverseBindTransform[16] = { 0 }; + + if ((skin.inverse_bind_matrices != NULL) && + (skin.inverse_bind_matrices->count >= skin.joints_count) && + cgltf_accessor_read_float(skin.inverse_bind_matrices, i, inverseBindTransform, 16)) + { + Matrix inverseBindMatrix = { + inverseBindTransform[0], inverseBindTransform[4], inverseBindTransform[8], inverseBindTransform[12], + inverseBindTransform[1], inverseBindTransform[5], inverseBindTransform[9], inverseBindTransform[13], + inverseBindTransform[2], inverseBindTransform[6], inverseBindTransform[10], inverseBindTransform[14], + inverseBindTransform[3], inverseBindTransform[7], inverseBindTransform[11], inverseBindTransform[15] + }; + bindMatrix = MatrixInvert(inverseBindMatrix); + } + else + { + cgltf_float worldTransform[16] = { 0 }; + cgltf_node_transform_world(skin.joints[i], worldTransform); + + Matrix worldMatrix = { + worldTransform[0], worldTransform[4], worldTransform[8], worldTransform[12], + worldTransform[1], worldTransform[5], worldTransform[9], worldTransform[13], + worldTransform[2], worldTransform[6], worldTransform[10], worldTransform[14], + worldTransform[3], worldTransform[7], worldTransform[11], worldTransform[15] + }; + bindMatrix = worldMatrix; + } + + MatrixDecompose(bindMatrix, + &(model.skeleton.bindPose[i].translation), + &(model.skeleton.bindPose[i].rotation), + &(model.skeleton.bindPose[i].scale)); + } + + if (data->skins_count > 1) TRACELOG(LOG_WARNING, "MODEL: [%s] can only load one skin (armature) per model, but gltf skins_count == %i", fileName, data->skins_count); + } + + meshIndex = 0; + for (unsigned int i = 0; i < data->nodes_count; i++) + { + cgltf_node *node = &(data->nodes[i]); + + cgltf_mesh *mesh = node->mesh; + if (!mesh) continue; + + for (unsigned int p = 0; p < mesh->primitives_count; p++) + { + bool hasJoints = false; + + // NOTE: Only support primitives defined by triangles + if (mesh->primitives[p].type != cgltf_primitive_type_triangles) continue; + + for (unsigned int j = 0; j < mesh->primitives[p].attributes_count; j++) + { + // NOTE: JOINTS_1 + WEIGHT_1 will be used for +4 joints influencing a vertex -> Not supported by raylib + if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_joints) // JOINTS_n (vec4: 4 bones max per vertex / u8, u16) + { + hasJoints = true; + cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; + + // NOTE: JOINTS_n can only be vec4 and u8/u16 + // SPECS: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview + + // WARNING: raylib only supports model.meshes[].boneIndices as u8 (unsigned char), + // if data is provided in any other format, it is converted to supported format but + // it could imply data loss (a warning message is issued in that case) + + if (attribute->type == cgltf_type_vec4) + { + if (attribute->component_type == cgltf_component_type_r_8u) + { + // Init raylib mesh boneIndices to copy glTF attribute data + model.meshes[meshIndex].boneIndices = (unsigned char *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); + + // Load attribute: vec4, u8 (unsigned char) + LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[meshIndex].boneIndices) + } + else if (attribute->component_type == cgltf_component_type_r_16u) + { + // Init raylib mesh boneIndices to copy glTF attribute data + model.meshes[meshIndex].boneIndices = (unsigned char *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned short *temp = (unsigned short *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned short)); + LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp); + + // Convert data to raylib color data type (4 bytes) + bool boneIdOverflowWarning = false; + for (int b = 0; b < model.meshes[meshIndex].vertexCount*4; b++) + { + if ((temp[b] > 255) && !boneIdOverflowWarning) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format (u16) overflow", fileName); + boneIdOverflowWarning = true; + } + + // Despite the possible overflow, convert data to unsigned char + model.meshes[meshIndex].boneIndices[b] = (unsigned char)temp[b]; + } + + RL_FREE(temp); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported", fileName); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported", fileName); + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_weights) // WEIGHTS_n (vec4, u8n/u16n/f32) + { + cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; + + if (attribute->type == cgltf_type_vec4) + { + if (attribute->component_type == cgltf_component_type_r_8u) + { + // Init raylib mesh bone weight to copy glTF attribute data + model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); + LOAD_ATTRIBUTE(attribute, 4, unsigned char, temp); + + // Convert data to raylib bone weight data type (4 bytes) + for (unsigned int b = 0; b < attribute->count*4; b++) model.meshes[meshIndex].boneWeights[b] = (float)temp[b]/255.0f; + + RL_FREE(temp); + } + else if (attribute->component_type == cgltf_component_type_r_16u) + { + // Init raylib mesh bone weight to copy glTF attribute data + model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*4*sizeof(unsigned short)); + LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp); + + // Convert data to raylib bone weight data type + for (unsigned int b = 0; b < attribute->count*4; b++) model.meshes[meshIndex].boneWeights[b] = (float)temp[b]/65535.0f; + + RL_FREE(temp); + } + else if (attribute->component_type == cgltf_component_type_r_32f) + { + // Init raylib mesh bone weight to copy glTF attribute data + model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); + + // Load 4 components of float data type into mesh.boneWeights + // for cgltf_attribute_type_weights: + // - data.meshes[0] (256 vertices) + // - 256 values, provided as cgltf_type_vec4 of float (4 byte per joint, stride 16) + LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].boneWeights) + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint weight attribute data format not supported, use vec4 float", fileName); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint weight attribute data format not supported, use vec4 float", fileName); + } + } + + // Check if animated, and the mesh was not given any bone assignments, but is the child of a bone node + // in this case, all the verts need to be attached to the parent bone so it will animate with the bone + if ((data->skins_count > 0) && !hasJoints && (node->parent != NULL) && (node->parent->mesh == NULL)) + { + int parentBoneId = -1; + for (unsigned int joint = 0; joint < model.skeleton.boneCount; joint++) + { + if (data->skins[0].joints[joint] == node->parent) + { + parentBoneId = joint; + break; + } + } + + if (parentBoneId >= 0) + { + model.meshes[meshIndex].boneIndices = (unsigned char *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); + model.meshes[meshIndex].boneWeights = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); + + for (int vertexIndex = 0; vertexIndex < model.meshes[meshIndex].vertexCount*4; vertexIndex += 4) + { + model.meshes[meshIndex].boneIndices[vertexIndex] = (unsigned char)parentBoneId; + model.meshes[meshIndex].boneWeights[vertexIndex] = 1.0f; + } + } + } + +#if !SUPPORT_GPU_SKINNING + // Animated vertex data (CPU skinning) + model.meshes[meshIndex].animVertices = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float)); + memcpy(model.meshes[meshIndex].animVertices, model.meshes[meshIndex].vertices, model.meshes[meshIndex].vertexCount*3*sizeof(float)); + model.meshes[meshIndex].animNormals = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float)); + if (model.meshes[meshIndex].normals != NULL) memcpy(model.meshes[meshIndex].animNormals, model.meshes[meshIndex].normals, model.meshes[meshIndex].vertexCount*3*sizeof(float)); +#endif + model.meshes[meshIndex].boneCount = model.skeleton.boneCount; + + meshIndex++; // Move to next mesh + } + } + + // Initialize runtime animation data: current pose and bone matrices + model.currentPose = (Transform *)RL_CALLOC(model.skeleton.boneCount, sizeof(Transform)); + model.boneMatrices = (Matrix *)RL_CALLOC(model.skeleton.boneCount, sizeof(Matrix)); + for (unsigned int j = 0; j < model.skeleton.boneCount; j++) model.boneMatrices[j] = MatrixIdentity(); + //---------------------------------------------------------------------------------------------------- + + // Free unused allocated memory in case of no bones defined + if (model.skeleton.boneCount == 0) + { + RL_FREE(model.currentPose); + RL_FREE(model.boneMatrices); + model.currentPose = NULL; + model.boneMatrices = NULL; + } + + // Free all cgltf loaded data + cgltf_free(data); + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); + + // WARNING: cgltf requires the file pointer available while reading data + UnloadFileData(fileData); + + return model; +} + +// Get interpolated pose for bone sampler at a specific time. Returns true on success +static bool GetPoseAtTimeGLTF(cgltf_interpolation_type interpolationType, cgltf_accessor *input, cgltf_accessor *output, float time, void *data) +{ + if (interpolationType >= cgltf_interpolation_type_max_enum) return false; + + // Input and output should have the same count + float tstart = 0.0f; + float tend = 0.0f; + int keyframe = 0; // Defaults to first pose + bool found = false; + + for (int i = 0; i < (int)input->count - 1; i++) + { + cgltf_bool r1 = cgltf_accessor_read_float(input, i, &tstart, 1); + if (!r1) return false; + + cgltf_bool r2 = cgltf_accessor_read_float(input, i + 1, &tend, 1); + if (!r2) return false; + + if ((tstart <= time) && (time < tend)) + { + keyframe = i; + found = true; + break; + } + } + + // No interval contains a time at (or past) the last keyframe, because the + // search above requires time < tend: clamp to the edge interval instead of + // falling back to keyframe 0, which returns a pose from the start + if (!found && ((int)input->count >= 2)) + { + keyframe = (int)input->count - 2; + + float tfirst = 0.0f; + if (!cgltf_accessor_read_float(input, 0, &tfirst, 1)) return false; + if (time < tfirst) keyframe = 0; + + if (!cgltf_accessor_read_float(input, keyframe, &tstart, 1)) return false; + if (!cgltf_accessor_read_float(input, keyframe + 1, &tend, 1)) return false; + } + + // Constant animation, no need to interpolate + if (FloatEquals(tend, tstart)) + { + interpolationType = cgltf_interpolation_type_step; + } + + float duration = fmaxf((tend - tstart), EPSILON); + float t = (time - tstart)/duration; + t = (t < 0.0f)? 0.0f : t; + t = (t > 1.0f)? 1.0f : t; + + if (output->component_type != cgltf_component_type_r_32f) return false; + + if (output->type == cgltf_type_vec3) + { + switch (interpolationType) + { + case cgltf_interpolation_type_step: + { + float tmp[3] = { 0.0f }; + cgltf_accessor_read_float(output, keyframe, tmp, 3); + Vector3 v1 = {tmp[0], tmp[1], tmp[2]}; + Vector3 *r = (Vector3 *)data; + + *r = v1; + } break; + case cgltf_interpolation_type_linear: + { + float tmp[3] = { 0.0f }; + cgltf_accessor_read_float(output, keyframe, tmp, 3); + Vector3 v1 = {tmp[0], tmp[1], tmp[2]}; + cgltf_accessor_read_float(output, keyframe+1, tmp, 3); + Vector3 v2 = {tmp[0], tmp[1], tmp[2]}; + Vector3 *r = (Vector3 *)data; + + *r = Vector3Lerp(v1, v2, t); + } break; + case cgltf_interpolation_type_cubic_spline: + { + float tmp[3] = { 0.0f }; + cgltf_accessor_read_float(output, 3*keyframe+1, tmp, 3); + Vector3 v1 = {tmp[0], tmp[1], tmp[2]}; + cgltf_accessor_read_float(output, 3*keyframe+2, tmp, 3); + Vector3 tangent1 = {tmp[0], tmp[1], tmp[2]}; + cgltf_accessor_read_float(output, 3*(keyframe+1)+1, tmp, 3); + Vector3 v2 = {tmp[0], tmp[1], tmp[2]}; + cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 3); + Vector3 tangent2 = {tmp[0], tmp[1], tmp[2]}; + Vector3 *r = (Vector3 *)data; + + *r = Vector3CubicHermite(v1, tangent1, v2, tangent2, t); + } break; + default: break; + } + } + else if (output->type == cgltf_type_vec4) + { + // Only v4 is for rotations, so it's a quaternion + switch (interpolationType) + { + case cgltf_interpolation_type_step: + { + float tmp[4] = { 0.0f }; + cgltf_accessor_read_float(output, keyframe, tmp, 4); + Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]}; + Vector4 *r = (Vector4 *)data; + + *r = v1; + } break; + case cgltf_interpolation_type_linear: + { + float tmp[4] = { 0.0f }; + cgltf_accessor_read_float(output, keyframe, tmp, 4); + Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]}; + cgltf_accessor_read_float(output, keyframe+1, tmp, 4); + Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]}; + Vector4 *r = (Vector4 *)data; + + *r = QuaternionSlerp(v1, v2, t); + } break; + case cgltf_interpolation_type_cubic_spline: + { + float tmp[4] = { 0.0f }; + cgltf_accessor_read_float(output, 3*keyframe+1, tmp, 4); + Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]}; + cgltf_accessor_read_float(output, 3*keyframe+2, tmp, 4); + Vector4 outTangent1 = {tmp[0], tmp[1], tmp[2], 0.0f}; + cgltf_accessor_read_float(output, 3*(keyframe+1)+1, tmp, 4); + Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]}; + cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 4); + Vector4 inTangent2 = {tmp[0], tmp[1], tmp[2], 0.0f}; + Vector4 *r = (Vector4 *)data; + + v1 = QuaternionNormalize(v1); + v2 = QuaternionNormalize(v2); + + if (Vector4DotProduct(v1, v2) < 0.0f) + { + v2 = Vector4Negate(v2); + } + + outTangent1 = Vector4Scale(outTangent1, duration); + inTangent2 = Vector4Scale(inTangent2, duration); + + *r = QuaternionCubicHermiteSpline(v1, outTangent1, v2, inTangent2, t); + } break; + default: break; + } + } + + return true; +} + +#define GLTF_FRAMERATE 60.0f // glTF animation framerate (frames per second) + +static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCount) +{ + // glTF file loading + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + ModelAnimation *animations = NULL; + + // glTF data loading + cgltf_options options = { 0 }; + options.file.read = LoadFileGLTFCallback; + options.file.release = ReleaseFileGLTFCallback; + cgltf_data *data = NULL; + cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); + + if (result != cgltf_result_success) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); + *animCount = 0; + return NULL; + } + + result = cgltf_load_buffers(&options, data, fileName); + if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load animation buffers", fileName); + + if (result == cgltf_result_success) + { + if (data->skins_count > 0) + { + cgltf_skin skin = data->skins[0]; + + // Precompute, per joint, the static transform contributed by any + // intermediate non-joint nodes between the joint and its nearest + // joint ancestor. This handles exporters (e.g. wow.export) that + // store bone offsets on dummy parent nodes rather than on the + // joints themselves. Depends only on the skin, not the animation. + int jointCount = (int)skin.joints_count; + Matrix *extOffset = (Matrix *)RL_MALLOC(jointCount*sizeof(Matrix)); + + if (extOffset == NULL) + { + // Allocation failed: abort animation loading at the source rather than + // propagating a NULL pointer into the per-frame transform loop below + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to allocate joint offset buffer", fileName); + cgltf_free(data); + UnloadFileData(fileData); + *animCount = 0; + return NULL; + } + + *animCount = (int)data->animations_count; + animations = (ModelAnimation *)RL_CALLOC(data->animations_count, sizeof(ModelAnimation)); + + for (int k = 0; k < jointCount; k++) + { + extOffset[k] = MatrixIdentity(); + cgltf_node *n = skin.joints[k]->parent; + + while (n != NULL) + { + bool isJoint = false; + for (int jj = 0; jj < jointCount; jj++) + { + if (skin.joints[jj] == n) + { + isJoint = true; + break; + } + } + + if (isJoint) break; + + // Compose the intermediate node's local TRS (scale, then rotation, then translation) + Matrix nodeScale = MatrixScale(n->scale[0], n->scale[1], n->scale[2]); + Matrix nodeRotation = QuaternionToMatrix((Quaternion){ n->rotation[0], n->rotation[1], n->rotation[2], n->rotation[3] }); + Matrix nodeTranslation = MatrixTranslate(n->translation[0], n->translation[1], n->translation[2]); + Matrix nodeTransform = MatrixMultiply(MatrixMultiply(nodeScale, nodeRotation), nodeTranslation); + + extOffset[k] = MatrixMultiply(extOffset[k], nodeTransform); + n = n->parent; + } + } + + for (unsigned int a = 0; a < data->animations_count; a++) + { + BoneInfo *bones = LoadBoneInfoGLTF(skin, &animations[a].boneCount); + + cgltf_animation animData = data->animations[a]; + + struct Channels { + cgltf_animation_channel *translate; + cgltf_animation_channel *rotate; + cgltf_animation_channel *scale; + }; + + struct Channels *boneChannels = (struct Channels *)RL_CALLOC(animations[a].boneCount, sizeof(struct Channels)); + float animDuration = 0.0f; + + for (unsigned int j = 0; j < animData.channels_count; j++) + { + cgltf_animation_channel channel = animData.channels[j]; + int boneIndex = -1; + + for (unsigned int k = 0; k < skin.joints_count; k++) + { + if (animData.channels[j].target_node == skin.joints[k]) + { + boneIndex = k; + break; + } + } + + if (boneIndex == -1) continue; // Animation channel for a node not in the skeleton + + if (animData.channels[j].sampler->interpolation != cgltf_interpolation_type_max_enum) + { + if (channel.target_path == cgltf_animation_path_type_translation) + { + boneChannels[boneIndex].translate = &animData.channels[j]; + } + else if (channel.target_path == cgltf_animation_path_type_rotation) + { + boneChannels[boneIndex].rotate = &animData.channels[j]; + } + else if (channel.target_path == cgltf_animation_path_type_scale) + { + boneChannels[boneIndex].scale = &animData.channels[j]; + } + else + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Unsupported target_path on channel %d's sampler for animation %d. Skipping.", fileName, j, a); + } + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Invalid interpolation curve encountered for GLTF animation.", fileName); + + float time = 0.0f; + cgltf_bool result = cgltf_accessor_read_float(channel.sampler->input, channel.sampler->input->count - 1, &time, 1); + + if (!result) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load input time", fileName); + continue; + } + + animDuration = (time > animDuration)? time : animDuration; + } + + if (animData.name != NULL) snprintf(animations[a].name, sizeof(animations[a].name), "%s", animData.name); + + animations[a].keyframeCount = (int)(animDuration*GLTF_FRAMERATE) + 1; + animations[a].keyframePoses = (Transform **)RL_CALLOC(animations[a].keyframeCount, sizeof(Transform *)); + + for (int j = 0; j < animations[a].keyframeCount; j++) + { + animations[a].keyframePoses[j] = (Transform *)RL_CALLOC(animations[a].boneCount, sizeof(Transform)); + float time = (float)j / GLTF_FRAMERATE; + + for (unsigned int k = 0; k < animations[a].boneCount; k++) + { + Vector3 translation = {skin.joints[k]->translation[0], skin.joints[k]->translation[1], skin.joints[k]->translation[2]}; + Quaternion rotation = {skin.joints[k]->rotation[0], skin.joints[k]->rotation[1], skin.joints[k]->rotation[2], skin.joints[k]->rotation[3]}; + Vector3 scale = {skin.joints[k]->scale[0], skin.joints[k]->scale[1], skin.joints[k]->scale[2]}; + + if (boneChannels[k].translate) + { + if (!GetPoseAtTimeGLTF(boneChannels[k].translate->sampler->interpolation, boneChannels[k].translate->sampler->input, boneChannels[k].translate->sampler->output, time, &translation)) + { + TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load translate pose data for bone %s", fileName, bones[k].name); + } + } + + if (boneChannels[k].rotate) + { + if (!GetPoseAtTimeGLTF(boneChannels[k].rotate->sampler->interpolation, boneChannels[k].rotate->sampler->input, boneChannels[k].rotate->sampler->output, time, &rotation)) + { + TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load rotate pose data for bone %s", fileName, bones[k].name); + } + } + + if (boneChannels[k].scale) + { + if (!GetPoseAtTimeGLTF(boneChannels[k].scale->sampler->interpolation, boneChannels[k].scale->sampler->input, boneChannels[k].scale->sampler->output, time, &scale)) + { + TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load scale pose data for bone %s", fileName, bones[k].name); + } + } + + // Compose joint local TRS, then prepend the static + // intermediate non-joint offsets so the final TRS is + // expressed relative to the joint's skeleton parent. + Matrix S = MatrixScale(scale.x, scale.y, scale.z); + Matrix R = QuaternionToMatrix(rotation); + Matrix T = MatrixTranslate(translation.x, translation.y, translation.z); + Matrix jointLocal = MatrixMultiply(MatrixMultiply(S, R), T); + Matrix combined = MatrixMultiply(jointLocal, extOffset[k]); + + Transform tr; + MatrixDecompose(combined, &tr.translation, &tr.rotation, &tr.scale); + animations[a].keyframePoses[j][k] = tr; + } + + BuildPoseFromParentJoints(bones, animations[a].boneCount, animations[a].keyframePoses[j]); + } + + TRACELOG(LOG_INFO, "MODEL: [%s] Loaded animation: %s | Frames: %d | Duration: %fs", fileName, (animData.name != NULL)? animData.name : "NULL", animations[a].keyframeCount, animDuration); + RL_FREE(boneChannels); + RL_FREE(bones); + } + + RL_FREE(extOffset); + } + + if (data->skins_count > 1) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Expected one unique skin to load animation data from, but found %i", fileName, data->skins_count); + } + + cgltf_free(data); + } + + UnloadFileData(fileData); + + return animations; +} +#endif + +#if SUPPORT_FILEFORMAT_VOX +// Load VOX (MagicaVoxel) mesh data +static Model LoadVOX(const char *fileName) +{ + Model model = { 0 }; + + int nbvertices = 0; + int meshescount = 0; + + // Read vox file into buffer + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData == 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load VOX file", fileName); + return model; + } + + // Read and build voxarray description + VoxArray3D voxarray = { 0 }; + int ret = Vox_LoadFromMemory(fileData, dataSize, &voxarray); + + if (ret != VOX_SUCCESS) + { + // Error + UnloadFileData(fileData); + + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load VOX data", fileName); + return model; + } + else + { + // Success: Compute meshes count + nbvertices = voxarray.vertices.used; + meshescount = 1 + (nbvertices/65536); + + TRACELOG(LOG_INFO, "MODEL: [%s] VOX data loaded successfully : %i vertices/%i meshes", fileName, nbvertices, meshescount); + } + + // Build models from meshes + model.transform = MatrixIdentity(); + + model.meshCount = meshescount; + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + + model.materialCount = 1; + model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + model.materials[0] = LoadMaterialDefault(); + + // Init model meshes + int verticesRemain = voxarray.vertices.used; + int verticesMax = 65532; // 5461 voxels x 12 vertices per voxel -> 65532 (must be inf 65536) + + // 6*4 = 12 vertices per voxel + Vector3 *pvertices = (Vector3 *)voxarray.vertices.array; + Vector3 *pnormals = (Vector3 *)voxarray.normals.array; + Color *pcolors = (Color *)voxarray.colors.array; + + unsigned short *pindices = voxarray.indices.array; // 5461*6*6 = 196596 indices max per mesh + + int size = 0; + + for (int i = 0; i < meshescount; i++) + { + Mesh *pmesh = &model.meshes[i]; + memset(pmesh, 0, sizeof(Mesh)); + + // Copy vertices + pmesh->vertexCount = (int)fmin(verticesMax, verticesRemain); + + size = pmesh->vertexCount*sizeof(float)*3; + pmesh->vertices = (float *)RL_MALLOC(size); + memcpy(pmesh->vertices, pvertices, size); + + // Copy normals + pmesh->normals = (float *)RL_MALLOC(size); + memcpy(pmesh->normals, pnormals, size); + + // Copy indices + size = voxarray.indices.used*sizeof(unsigned short); + pmesh->indices = (unsigned short *)RL_MALLOC(size); + memcpy(pmesh->indices, pindices, size); + + pmesh->triangleCount = (pmesh->vertexCount/4)*2; + + // Copy colors + size = pmesh->vertexCount*sizeof(Color); + pmesh->colors = (unsigned char *)RL_MALLOC(size); + memcpy(pmesh->colors, pcolors, size); + + // First material index + model.meshMaterial[i] = 0; + + verticesRemain -= verticesMax; + pvertices += verticesMax; + pnormals += verticesMax; + pcolors += verticesMax; + } + + // Free buffers + Vox_FreeArrays(&voxarray); + UnloadFileData(fileData); + + return model; +} +#endif + +#if SUPPORT_FILEFORMAT_M3D +// Hook LoadFileData()/UnloadFileData() calls to M3D loaders +unsigned char *m3d_loaderhook(char *fn, unsigned int *len) { return LoadFileData((const char *)fn, (int *)len); } +void m3d_freehook(void *data) { UnloadFileData((unsigned char *)data); } + +// Load M3D mesh data +static Model LoadM3D(const char *fileName) +{ + Model model = { 0 }; + + m3d_t *m3d = NULL; + m3dp_t *prop = NULL; + int i, j, k, l, n, mi = -2, vcolor = 0; + + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData != NULL) + { + m3d = m3d_load(fileData, m3d_loaderhook, m3d_freehook, NULL); + + if (!m3d || M3D_ERR_ISFATAL(m3d->errcode)) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load M3D data, error code %d", fileName, m3d? m3d->errcode : -2); + if (m3d) m3d_free(m3d); + UnloadFileData(fileData); + return model; + } + else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i faces/%i materials", fileName, m3d->numface, m3d->nummaterial); + + // Check if face is found, if not, probably just a material library + if (!m3d->numface) + { + m3d_free(m3d); + UnloadFileData(fileData); + return model; + } + + if (m3d->nummaterial > 0) + { + model.meshCount = model.materialCount = m3d->nummaterial; + TRACELOG(LOG_INFO, "MODEL: model has %i material meshes", model.materialCount); + } + else + { + model.meshCount = 1; model.materialCount = 0; + TRACELOG(LOG_INFO, "MODEL: No materials, putting all meshes in a default material"); + } + + // A default material is always required, so adding +1 + model.materialCount++; + + // Faces must be in non-decreasing materialid order. Verify that quickly, sorting them otherwise + // WARNING: Sorting is not needed, valid M3D model files should already be sorted + // Keeping the sorting function for reference (Check PR #3363 #3385) + /* + for (a = 1; a < m3d->numface; a++) + { + if (m3d->face[a-1].materialid <= m3d->face[a].materialid) continue; + + // face[a-1] > face[a]. slide face[a] lower + m3df_t slider = m3d->face[a]; + j = a-1; + + do + { // face[j] > slider, face[j+1] is svailable vacant gap + m3d->face[j+1] = m3d->face[j]; + j = j-1; + } + while (j >= 0 && m3d->face[j].materialid > slider.materialid); + + m3d->face[j+1] = slider; + } + */ + + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + model.materials = (Material *)RL_CALLOC(model.materialCount + 1, sizeof(Material)); + + // Map no material to index 0 with default shader, everything else materialid + 1 + model.materials[0] = LoadMaterialDefault(); + + for (i = l = 0, k = -1; i < (int)m3d->numface; i++, l++) + { + // Materials are grouped together + if (mi != m3d->face[i].materialid) + { + // There should be only one material switch per material kind, + // but be bulletproof for non-optimal model files + if (k + 1 >= model.meshCount) + { + model.meshCount++; + + // Create a second buffer for mesh re-allocation + Mesh *tempMeshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + memcpy(tempMeshes, model.meshes, (model.meshCount - 1)*sizeof(Mesh)); + RL_FREE(model.meshes); + model.meshes = tempMeshes; + + // Create a second buffer for material re-allocation + int *tempMeshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + memcpy(tempMeshMaterial, model.meshMaterial, (model.meshCount - 1)*sizeof(int)); + RL_FREE(model.meshMaterial); + model.meshMaterial = tempMeshMaterial; + } + + k++; + mi = m3d->face[i].materialid; + + // Only allocate colors VertexBuffer if there's a color vertex in the model for this material batch + // if all colors are fully transparent black for all vertices of this material, then assuming no vertex colors + for (j = i, l = vcolor = 0; (j < (int)m3d->numface) && (mi == m3d->face[j].materialid); j++, l++) + { + if (!m3d->vertex[m3d->face[j].vertex[0]].color || + !m3d->vertex[m3d->face[j].vertex[1]].color || + !m3d->vertex[m3d->face[j].vertex[2]].color) vcolor = 1; + } + + model.meshes[k].vertexCount = l*3; + model.meshes[k].triangleCount = l; + model.meshes[k].vertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); + model.meshes[k].texcoords = (float *)RL_CALLOC(model.meshes[k].vertexCount*2, sizeof(float)); + model.meshes[k].normals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); + + // If no map is provided, or colors are defined, allocate storage for vertex colors + // M3D specs only consider vertex colors if no material is provided, however raylib uses both and mixes the colors + if ((mi == M3D_UNDEF) || vcolor) model.meshes[k].colors = (unsigned char *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char)); + + // If no map is provided and vertex colors are allocated, set them to white + if ((mi == M3D_UNDEF) && (model.meshes[k].colors != NULL)) + { + for (int c = 0; c < model.meshes[k].vertexCount*4; c++) model.meshes[k].colors[c] = 255; + } + + if (m3d->numbone && m3d->numskin) + { + model.meshes[k].boneIndices = (unsigned char *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char)); + model.meshes[k].boneWeights = (float *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(float)); +#if !SUPPORT_GPU_SKINNING + model.meshes[k].animVertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); + model.meshes[k].animNormals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); +#endif + } + + model.meshMaterial[k] = mi + 1; + l = 0; + } + + // Process meshes per material, add triangles + model.meshes[k].vertices[l*9 + 0] = m3d->vertex[m3d->face[i].vertex[0]].x*m3d->scale; + model.meshes[k].vertices[l*9 + 1] = m3d->vertex[m3d->face[i].vertex[0]].y*m3d->scale; + model.meshes[k].vertices[l*9 + 2] = m3d->vertex[m3d->face[i].vertex[0]].z*m3d->scale; + model.meshes[k].vertices[l*9 + 3] = m3d->vertex[m3d->face[i].vertex[1]].x*m3d->scale; + model.meshes[k].vertices[l*9 + 4] = m3d->vertex[m3d->face[i].vertex[1]].y*m3d->scale; + model.meshes[k].vertices[l*9 + 5] = m3d->vertex[m3d->face[i].vertex[1]].z*m3d->scale; + model.meshes[k].vertices[l*9 + 6] = m3d->vertex[m3d->face[i].vertex[2]].x*m3d->scale; + model.meshes[k].vertices[l*9 + 7] = m3d->vertex[m3d->face[i].vertex[2]].y*m3d->scale; + model.meshes[k].vertices[l*9 + 8] = m3d->vertex[m3d->face[i].vertex[2]].z*m3d->scale; + + // Without vertex color (full transparency), using the default color + if (model.meshes[k].colors != NULL) + { + if (m3d->vertex[m3d->face[i].vertex[0]].color & 0xff000000) + memcpy(&model.meshes[k].colors[l*12 + 0], &m3d->vertex[m3d->face[i].vertex[0]].color, 4); + if (m3d->vertex[m3d->face[i].vertex[1]].color & 0xff000000) + memcpy(&model.meshes[k].colors[l*12 + 4], &m3d->vertex[m3d->face[i].vertex[1]].color, 4); + if (m3d->vertex[m3d->face[i].vertex[2]].color & 0xff000000) + memcpy(&model.meshes[k].colors[l*12 + 8], &m3d->vertex[m3d->face[i].vertex[2]].color, 4); + } + + if (m3d->face[i].texcoord[0] != M3D_UNDEF) + { + model.meshes[k].texcoords[l*6 + 0] = m3d->tmap[m3d->face[i].texcoord[0]].u; + model.meshes[k].texcoords[l*6 + 1] = 1.0f - m3d->tmap[m3d->face[i].texcoord[0]].v; + model.meshes[k].texcoords[l*6 + 2] = m3d->tmap[m3d->face[i].texcoord[1]].u; + model.meshes[k].texcoords[l*6 + 3] = 1.0f - m3d->tmap[m3d->face[i].texcoord[1]].v; + model.meshes[k].texcoords[l*6 + 4] = m3d->tmap[m3d->face[i].texcoord[2]].u; + model.meshes[k].texcoords[l*6 + 5] = 1.0f - m3d->tmap[m3d->face[i].texcoord[2]].v; + } + + if (m3d->face[i].normal[0] != M3D_UNDEF) + { + model.meshes[k].normals[l*9 + 0] = m3d->vertex[m3d->face[i].normal[0]].x; + model.meshes[k].normals[l*9 + 1] = m3d->vertex[m3d->face[i].normal[0]].y; + model.meshes[k].normals[l*9 + 2] = m3d->vertex[m3d->face[i].normal[0]].z; + model.meshes[k].normals[l*9 + 3] = m3d->vertex[m3d->face[i].normal[1]].x; + model.meshes[k].normals[l*9 + 4] = m3d->vertex[m3d->face[i].normal[1]].y; + model.meshes[k].normals[l*9 + 5] = m3d->vertex[m3d->face[i].normal[1]].z; + model.meshes[k].normals[l*9 + 6] = m3d->vertex[m3d->face[i].normal[2]].x; + model.meshes[k].normals[l*9 + 7] = m3d->vertex[m3d->face[i].normal[2]].y; + model.meshes[k].normals[l*9 + 8] = m3d->vertex[m3d->face[i].normal[2]].z; + } + + // Add skin (vertex / bone weight pairs) + if (m3d->numbone && m3d->numskin) + { + for (n = 0; n < 3; n++) + { + int skinid = m3d->vertex[m3d->face[i].vertex[n]].skinid; + + // Check if there is a skin for this mesh + if ((skinid != M3D_UNDEF) && (skinid < (int)m3d->numskin)) + { + for (j = 0; j < 4; j++) + { + model.meshes[k].boneIndices[l*12 + n*4 + j] = m3d->skin[skinid].boneid[j]; + model.meshes[k].boneWeights[l*12 + n*4 + j] = m3d->skin[skinid].weight[j]; + } + } + else + { + // Boneless meshes with skeletal animations are not supported, so + // putting all vertices without a bone into a special "no bone" bone + model.meshes[k].boneIndices[l*12 + n*4] = m3d->numbone; + model.meshes[k].boneWeights[l*12 + n*4] = 1.0f; + } + } + } + } + + // Load materials + for (i = 0; i < (int)m3d->nummaterial; i++) + { + model.materials[i + 1] = LoadMaterialDefault(); + + for (j = 0; j < m3d->material[i].numprop; j++) + { + prop = &m3d->material[i].prop[j]; + + switch (prop->type) + { + case m3dp_Kd: + { + memcpy(&model.materials[i + 1].maps[MATERIAL_MAP_DIFFUSE].color, &prop->value.color, 4); + model.materials[i + 1].maps[MATERIAL_MAP_DIFFUSE].value = 0.0f; + } break; + case m3dp_Ks: + { + memcpy(&model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].color, &prop->value.color, 4); + } break; + case m3dp_Ns: + { + model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].value = prop->value.fnum; + } break; + case m3dp_Ke: + { + memcpy(&model.materials[i + 1].maps[MATERIAL_MAP_EMISSION].color, &prop->value.color, 4); + model.materials[i + 1].maps[MATERIAL_MAP_EMISSION].value = 0.0f; + } break; + case m3dp_Pm: + { + model.materials[i + 1].maps[MATERIAL_MAP_METALNESS].value = prop->value.fnum; + } break; + case m3dp_Pr: + { + model.materials[i + 1].maps[MATERIAL_MAP_ROUGHNESS].value = prop->value.fnum; + } break; + case m3dp_Ps: + { + model.materials[i + 1].maps[MATERIAL_MAP_NORMAL].color = WHITE; + model.materials[i + 1].maps[MATERIAL_MAP_NORMAL].value = prop->value.fnum; + } break; + default: + { + if (prop->type >= 128) + { + Image image = { 0 }; + image.data = m3d->texture[prop->value.textureid].d; + image.width = m3d->texture[prop->value.textureid].w; + image.height = m3d->texture[prop->value.textureid].h; + image.mipmaps = 1; + image.format = (m3d->texture[prop->value.textureid].f == 4)? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 : + ((m3d->texture[prop->value.textureid].f == 3)? PIXELFORMAT_UNCOMPRESSED_R8G8B8 : + ((m3d->texture[prop->value.textureid].f == 2)? PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA : PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)); + + switch (prop->type) + { + case m3dp_map_Kd: model.materials[i + 1].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTextureFromImage(image); break; + case m3dp_map_Ks: model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].texture = LoadTextureFromImage(image); break; + case m3dp_map_Ke: model.materials[i + 1].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(image); break; + case m3dp_map_Km: model.materials[i + 1].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(image); break; + case m3dp_map_Ka: model.materials[i + 1].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(image); break; + case m3dp_map_Pm: model.materials[i + 1].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(image); break; + default: break; + } + } + } break; + } + } + } + + // Load bones + if (m3d->numbone) + { + model.skeleton.boneCount = m3d->numbone + 1; + model.skeleton.bones = (BoneInfo *)RL_CALLOC(model.skeleton.boneCount, sizeof(BoneInfo)); + model.skeleton.bindPose = (Transform *)RL_CALLOC(model.skeleton.boneCount, sizeof(Transform)); + + for (i = 0; i < (int)m3d->numbone; i++) + { + model.skeleton.bones[i].parent = m3d->bone[i].parent; + snprintf(model.skeleton.bones[i].name, sizeof(model.skeleton.bones[i].name), "%s", m3d->bone[i].name); + model.skeleton.bindPose[i].translation.x = m3d->vertex[m3d->bone[i].pos].x*m3d->scale; + model.skeleton.bindPose[i].translation.y = m3d->vertex[m3d->bone[i].pos].y*m3d->scale; + model.skeleton.bindPose[i].translation.z = m3d->vertex[m3d->bone[i].pos].z*m3d->scale; + model.skeleton.bindPose[i].rotation.x = m3d->vertex[m3d->bone[i].ori].x; + model.skeleton.bindPose[i].rotation.y = m3d->vertex[m3d->bone[i].ori].y; + model.skeleton.bindPose[i].rotation.z = m3d->vertex[m3d->bone[i].ori].z; + model.skeleton.bindPose[i].rotation.w = m3d->vertex[m3d->bone[i].ori].w; + + // NOTE: If the orientation quaternion is not normalized, then that's encoding scaling + model.skeleton.bindPose[i].rotation = QuaternionNormalize(model.skeleton.bindPose[i].rotation); + model.skeleton.bindPose[i].scale.x = model.skeleton.bindPose[i].scale.y = model.skeleton.bindPose[i].scale.z = 1.0f; + + // Child bones are stored in parent bone relative space, convert that into model space + if (model.skeleton.bones[i].parent >= 0) + { + model.skeleton.bindPose[i].rotation = QuaternionMultiply(model.skeleton.bindPose[model.skeleton.bones[i].parent].rotation, model.skeleton.bindPose[i].rotation); + model.skeleton.bindPose[i].translation = Vector3RotateByQuaternion(model.skeleton.bindPose[i].translation, model.skeleton.bindPose[model.skeleton.bones[i].parent].rotation); + model.skeleton.bindPose[i].translation = Vector3Add(model.skeleton.bindPose[i].translation, model.skeleton.bindPose[model.skeleton.bones[i].parent].translation); + model.skeleton.bindPose[i].scale = Vector3Multiply(model.skeleton.bindPose[i].scale, model.skeleton.bindPose[model.skeleton.bones[i].parent].scale); + } + } + + // Add a special "no bone" bone + model.skeleton.bones[i].parent = -1; + memcpy(model.skeleton.bones[i].name, "NO BONE", 7); + model.skeleton.bindPose[i].translation.x = 0.0f; + model.skeleton.bindPose[i].translation.y = 0.0f; + model.skeleton.bindPose[i].translation.z = 0.0f; + model.skeleton.bindPose[i].rotation.x = 0.0f; + model.skeleton.bindPose[i].rotation.y = 0.0f; + model.skeleton.bindPose[i].rotation.z = 0.0f; + model.skeleton.bindPose[i].rotation.w = 1.0f; + model.skeleton.bindPose[i].scale.x = model.skeleton.bindPose[i].scale.y = model.skeleton.bindPose[i].scale.z = 1.0f; + } + + // Load bone-pose default mesh into animation vertices. These will be updated when UpdateModelAnimation gets + // called, but not before, however DrawMesh uses these if they exist (so not good if they are left empty) + if (m3d->numbone && m3d->numskin) + { + for (i = 0; i < model.meshCount; i++) + { + model.meshes[i].boneCount = model.skeleton.boneCount; + +#if !SUPPORT_GPU_SKINNING + // Initialize vertex buffers for CPU skinning + memcpy(model.meshes[i].animVertices, model.meshes[i].vertices, model.meshes[i].vertexCount*3*sizeof(float)); + memcpy(model.meshes[i].animNormals, model.meshes[i].normals, model.meshes[i].vertexCount*3*sizeof(float)); +#endif + } + + // Initialize runtime animation data: current pose and bone matrices + model.currentPose = (Transform *)RL_CALLOC(model.skeleton.boneCount, sizeof(Transform)); + model.boneMatrices = (Matrix *)RL_CALLOC(model.skeleton.boneCount, sizeof(Matrix)); + for (unsigned int j = 0; j < model.skeleton.boneCount; j++) model.boneMatrices[j] = MatrixIdentity(); + } + + m3d_free(m3d); + UnloadFileData(fileData); + } + + return model; +} + +#define M3D_ANIMDELAY 17 // Animation frames delay, (~1000 ms/60 FPS = 16.666666 ms) + +// Load M3D animation data +static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, int *animCount) +{ + ModelAnimation *animations = NULL; + + m3d_t *m3d = NULL; + int i = 0, j = 0; + *animCount = 0; + + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData != NULL) + { + m3d = m3d_load(fileData, m3d_loaderhook, m3d_freehook, NULL); + + if (!m3d || M3D_ERR_ISFATAL(m3d->errcode)) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load M3D data, error code %d", fileName, m3d? m3d->errcode : -2); + UnloadFileData(fileData); + return NULL; + } + else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i animations, %i bones, %i skins", fileName, m3d->numaction, m3d->numbone, m3d->numskin); + + // No animation or bones, exit out. skins are not required because some people use one animation for N models + if (!m3d->numaction || !m3d->numbone) + { + m3d_free(m3d); + UnloadFileData(fileData); + return NULL; + } + + animations = (ModelAnimation *)RL_CALLOC(m3d->numaction, sizeof(ModelAnimation)); + *animCount = m3d->numaction; + + for (unsigned int a = 0; a < m3d->numaction; a++) + { + animations[a].boneCount = m3d->numbone + 1; + BoneInfo *bones = (BoneInfo *)RL_CALLOC((m3d->numbone + 1), sizeof(BoneInfo)); + + animations[a].keyframeCount = m3d->action[a].durationmsec/M3D_ANIMDELAY; + animations[a].keyframePoses = (Transform **)RL_CALLOC(animations[a].keyframeCount, sizeof(Transform *)); + snprintf(animations[a].name, sizeof(animations[a].name), "%s", m3d->action[a].name); + + TRACELOG(LOG_INFO, "MODEL: [%s] Loaded animation: %s | Frames: %d | Duration: %fs", fileName, animations[a].name, animations[a].keyframeCount, m3d->action[a].durationmsec); + + for (i = 0; i < (int)m3d->numbone; i++) + { + bones[i].parent = m3d->bone[i].parent; + snprintf(bones[i].name, sizeof(bones[i].name), "%s", m3d->bone[i].name); + } + + // A special, never transformed "no bone" bone, used for boneless vertices + bones[i].parent = -1; + memcpy(bones[i].name, "NO BONE", 7); + + // M3D stores frames at arbitrary intervals with sparse skeletons; Full skeletons is required at + // regular intervals, so let the M3D SDK do the heavy lifting and calculate interpolated bones + for (i = 0; i < animations[a].keyframeCount; i++) + { + animations[a].keyframePoses[i] = (Transform *)RL_CALLOC((m3d->numbone + 1), sizeof(Transform)); + + m3db_t *pose = m3d_pose(m3d, a, i*M3D_ANIMDELAY); + + if (pose != NULL) + { + for (j = 0; j < (int)m3d->numbone; j++) + { + animations[a].keyframePoses[i][j].translation.x = m3d->vertex[pose[j].pos].x*m3d->scale; + animations[a].keyframePoses[i][j].translation.y = m3d->vertex[pose[j].pos].y*m3d->scale; + animations[a].keyframePoses[i][j].translation.z = m3d->vertex[pose[j].pos].z*m3d->scale; + animations[a].keyframePoses[i][j].rotation.x = m3d->vertex[pose[j].ori].x; + animations[a].keyframePoses[i][j].rotation.y = m3d->vertex[pose[j].ori].y; + animations[a].keyframePoses[i][j].rotation.z = m3d->vertex[pose[j].ori].z; + animations[a].keyframePoses[i][j].rotation.w = m3d->vertex[pose[j].ori].w; + animations[a].keyframePoses[i][j].rotation = QuaternionNormalize(animations[a].keyframePoses[i][j].rotation); + animations[a].keyframePoses[i][j].scale.x = animations[a].keyframePoses[i][j].scale.y = animations[a].keyframePoses[i][j].scale.z = 1.0f; + + // Child bones are stored in parent bone relative space, convert that into model space + if (bones[j].parent >= 0) + { + animations[a].keyframePoses[i][j].rotation = QuaternionMultiply(animations[a].keyframePoses[i][bones[j].parent].rotation, animations[a].keyframePoses[i][j].rotation); + animations[a].keyframePoses[i][j].translation = Vector3RotateByQuaternion(animations[a].keyframePoses[i][j].translation, animations[a].keyframePoses[i][bones[j].parent].rotation); + animations[a].keyframePoses[i][j].translation = Vector3Add(animations[a].keyframePoses[i][j].translation, animations[a].keyframePoses[i][bones[j].parent].translation); + animations[a].keyframePoses[i][j].scale = Vector3Multiply(animations[a].keyframePoses[i][j].scale, animations[a].keyframePoses[i][bones[j].parent].scale); + } + } + + // Default transform for the "no bone" bone + animations[a].keyframePoses[i][j].translation.x = 0.0f; + animations[a].keyframePoses[i][j].translation.y = 0.0f; + animations[a].keyframePoses[i][j].translation.z = 0.0f; + animations[a].keyframePoses[i][j].rotation.x = 0.0f; + animations[a].keyframePoses[i][j].rotation.y = 0.0f; + animations[a].keyframePoses[i][j].rotation.z = 0.0f; + animations[a].keyframePoses[i][j].rotation.w = 1.0f; + animations[a].keyframePoses[i][j].scale.x = 1.0f; + animations[a].keyframePoses[i][j].scale.y = 1.0f; + animations[a].keyframePoses[i][j].scale.z = 1.0f; + + RL_FREE(pose); + } + } + + RL_FREE(bones); + } + + m3d_free(m3d); + UnloadFileData(fileData); + } + + return animations; +} +#endif + +#endif // SUPPORT_MODULE_RMODELS diff --git a/src/libraries/raylib/raylib/src/rshapes.c b/src/libraries/raylib/raylib/src/rshapes.c new file mode 100644 index 0000000..f52344f --- /dev/null +++ b/src/libraries/raylib/raylib/src/rshapes.c @@ -0,0 +1,4552 @@ +/********************************************************************************************** +* +* rshapes - Basic functions to draw 2d shapes and check collisions +* +* ADDITIONAL NOTES: +* Shapes can be draw using 3 types of primitives: LINES, TRIANGLES and QUADS +* Some functions implement two drawing options: TRIANGLES and QUADS, by default TRIANGLES +* are used but QUADS implementation can be selected with SUPPORT_QUADS_DRAW_MODE define +* +* Some functions define texture coordinates (rlTexCoord2f()) for the shapes and use a +* user-provided texture with SetShapesTexture(), the purpose of this implementation +* is allowing to reduce draw calls when combined with a texture-atlas +* +* By default, raylib sets the default texture and rectangle at InitWindow()[rcore] to one +* white character of default font [rtext], this way, raylib text and shapes can be drawn with +* a single draw call and it also allows users to configure it the same way with their own fonts +* +* CONFIGURATION: +* #define SUPPORT_MODULE_RSHAPES 1 +* rshapes module is included in the build +* +* #define SUPPORT_QUADS_DRAW_MODE 1 +* Use QUADS instead of TRIANGLES for drawing when possible. Lines-based shapes still use LINES +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +#include "config.h" // Defines module configuration flags + +#if SUPPORT_MODULE_RSHAPES + +#include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 + +#include // Required for: sinf(), asinf(), cosf(), acosf(), sqrtf(), fabsf() +#include // Required for: FLT_EPSILON +#include // Required for: RL_FREE + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef SMOOTH_CIRCLE_ERROR_RATE + // Define error rate to calculate how many segments are needed to draw a smooth circle + // REF: https://stackoverflow.com/a/2244088 + #define SMOOTH_CIRCLE_ERROR_RATE 0.5f // Circle error rate +#endif +#ifndef SPLINE_SEGMENT_DIVISIONS + #define SPLINE_SEGMENT_DIVISIONS 24 // Spline segment divisions +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Not here... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static Texture2D texShapes = { 1, 1, 1, 1, 7 }; // Texture used on shapes drawing (white pixel loaded by rlgl) +static Rectangle texShapesRec = { 0.0f, 0.0f, 1.0f, 1.0f }; // Texture source rectangle used on shapes drawing + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +static float EaseCubicInOut(float t, float b, float c, float d); // Cubic easing + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +void SetShapesTexture(Texture2D texture, Rectangle rec) +{ + // Reset texture to default pixel if required + // WARNING: Shapes texture should be probably better validated, + // it can break the rendering of all shapes if misused + if ((texture.id == 0) || (rec.width == 0) || (rec.height == 0)) + { + texShapes = (Texture2D){ 1, 1, 1, 1, 7 }; + texShapesRec = (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }; + } + else + { + texShapes = texture; + texShapesRec = rec; + } +} + +// Get texture that is used for shapes drawing +Texture2D GetShapesTexture(void) +{ + return texShapes; +} + +// Get texture source rectangle that is used for shapes drawing +Rectangle GetShapesTextureRectangle(void) +{ + return texShapesRec; +} + +// Draw a pixel +void DrawPixel(int posX, int posY, Color color) +{ + DrawPixelV((Vector2){ (float)posX, (float)posY }, color); +} + +// Draw a pixel (Vector version) +void DrawPixelV(Vector2 position, Color color) +{ +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + rlNormal3f(0.0f, 0.0f, 1.0f); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(position.x, position.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(position.x, position.y + 1); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(position.x + 1, position.y + 1); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(position.x + 1, position.y); + + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(position.x, position.y); + rlVertex2f(position.x, position.y + 1); + rlVertex2f(position.x + 1, position.y); + + rlVertex2f(position.x + 1, position.y); + rlVertex2f(position.x, position.y + 1); + rlVertex2f(position.x + 1, position.y + 1); + + rlEnd(); +#endif +} + +// Draw a line (using gl lines) +void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f((float)startPosX, (float)startPosY); + rlVertex2f((float)endPosX, (float)endPosY); + rlEnd(); +} + +// Draw a line defining thickness +void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color) +{ + Vector2 delta = { endPos.x - startPos.x, endPos.y - startPos.y }; + float length = sqrtf(delta.x*delta.x + delta.y*delta.y); + + if ((length > 0) && (thick > 0)) + { + float scale = thick/(2*length); + + Vector2 radius = { -scale*delta.y, scale*delta.x }; + Vector2 strip[4] = { + { startPos.x - radius.x, startPos.y - radius.y }, + { startPos.x + radius.x, startPos.y + radius.y }, + { endPos.x - radius.x, endPos.y - radius.y }, + { endPos.x + radius.x, endPos.y + radius.y } + }; + + DrawTriangleStrip(strip, 4, color); + } +} + +// Draw a line (using gl lines) +void DrawLineV(Vector2 startPos, Vector2 endPos, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(startPos.x, startPos.y); + rlVertex2f(endPos.x, endPos.y); + rlEnd(); +} + +// Draw lines sequuence (using gl lines) +void DrawLineStrip(const Vector2 *points, int pointCount, Color color) +{ + if (pointCount < 2) return; // Security check + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < pointCount - 1; i++) + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i + 1].x, points[i + 1].y); + } + rlEnd(); +} + +// Draw line using cubic-bezier spline, in-out interpolation, no control points +void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color) +{ + Vector2 previous = startPos; + Vector2 current = { 0 }; + + Vector2 points[2*SPLINE_SEGMENT_DIVISIONS + 2] = { 0 }; + + for (int i = 1; i <= SPLINE_SEGMENT_DIVISIONS; i++) + { + // Cubic easing in-out + // NOTE: Easing is calculated only for y position value + current.y = EaseCubicInOut((float)i, startPos.y, endPos.y - startPos.y, (float)SPLINE_SEGMENT_DIVISIONS); + current.x = previous.x + (endPos.x - startPos.x)/(float)SPLINE_SEGMENT_DIVISIONS; + + float dy = current.y - previous.y; + float dx = current.x - previous.x; + float size = 0.5f*thick/sqrtf(dx*dx+dy*dy); + + if (i == 1) + { + points[0].x = previous.x + dy*size; + points[0].y = previous.y - dx*size; + points[1].x = previous.x - dy*size; + points[1].y = previous.y + dx*size; + } + + points[2*i + 1].x = current.x - dy*size; + points[2*i + 1].y = current.y + dx*size; + points[2*i].x = current.x + dy*size; + points[2*i].y = current.y - dx*size; + + previous = current; + } + + DrawTriangleStrip(points, 2*SPLINE_SEGMENT_DIVISIONS + 2, color); +} + +// Draw a dashed line +void DrawLineDashed(Vector2 startPos, Vector2 endPos, int dashSize, int spaceSize, Color color) +{ + // Calculate the vector and length of the line + float dx = endPos.x - startPos.x; + float dy = endPos.y - startPos.y; + float lineLength = sqrtf(dx*dx + dy*dy); + + // If the line is too short for dashing or dash size is invalid, draw a solid thick line + if ((lineLength < (dashSize + spaceSize)) || (dashSize <= 0)) + { + DrawLineV(startPos, endPos, color); + return; + } + + // Calculate the normalized direction vector of the line + float invLineLength = 1.0f/lineLength; + float dirX = dx*invLineLength; + float dirY = dy*invLineLength; + + Vector2 currentPos = startPos; + float distanceTraveled = 0; + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + while (distanceTraveled < lineLength) + { + // Calculate the end of the current dash + float dashEndDist = distanceTraveled + dashSize; + if (dashEndDist > lineLength) dashEndDist = lineLength; + + Vector2 dashEndPos = { startPos.x + dashEndDist*dirX, startPos.y + dashEndDist*dirY }; + + // Draw the dash segment + rlVertex2f(currentPos.x, currentPos.y); + rlVertex2f(dashEndPos.x, dashEndPos.y); + + // Update the distance traveled and move the current position for the next dash + distanceTraveled = dashEndDist + spaceSize; + currentPos.x = startPos.x + distanceTraveled*dirX; + currentPos.y = startPos.y + distanceTraveled*dirY; + } + rlEnd(); +} + +// Draw a triangle +// NOTE: Vertex must be provided in counter-clockwise order +void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) +{ + DrawTriangleGradient(v1, v2, v3, color, color, color); +} + +// Draw triangle with interpolated colors (vertex in counter-clockwise order!) +void DrawTriangleGradient(Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3) +{ +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + rlNormal3f(0.0f, 0.0f, 1.0f); + + rlColor4ub(c1.r, c1.g, c1.b, c1.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v1.x, v1.y); + + rlColor4ub(c2.r, c2.g, c2.b, c2.a); + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v2.x, v2.y); + + rlColor4ub(c3.r, c3.g, c3.b, c3.a); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v3.x, v3.y); + + rlColor4ub(c3.r, c3.g, c3.b, c3.a); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v3.x, v3.y); + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + rlColor4ub(c1.r, c1.g, c1.b, c1.a); + rlVertex2f(v1.x, v1.y); + rlColor4ub(c2.r, c2.g, c2.b, c2.a); + rlVertex2f(v2.x, v2.y); + rlColor4ub(c3.r, c3.g, c3.b, c3.a); + rlVertex2f(v3.x, v3.y); + rlEnd(); +#endif +} + +// Draw a triangle using lines +// NOTE: Vertex must be provided in counter-clockwise order +void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(v1.x, v1.y); + rlVertex2f(v2.x, v2.y); + + rlVertex2f(v2.x, v2.y); + rlVertex2f(v3.x, v3.y); + + rlVertex2f(v3.x, v3.y); + rlVertex2f(v1.x, v1.y); + rlEnd(); +} + +// Draw a triangle using lines with thickness +// NOTE: Vertex must be provided in counter-clockwise order +void DrawTriangleLinesEx(Vector2 v1, Vector2 v2, Vector2 v3, float thick, Color color) +{ + /* + A sketch to make things simpler + + The exterior points are v1-3, the interior points are v4-6, and the exterior edges are e1-3 + + v1 + /\ + /v4\ + // \\ + // \\ + e3 // \\ e2 + // \\ + // \\ + //v5 v6\\ + v2==============v3 + e1 + */ + + Vector2 e1 = {v2.x - v3.x, v2.y - v3.y}; + Vector2 e2 = {v3.x - v1.x, v3.y - v1.y}; + Vector2 e3 = {v1.x - v2.x, v1.y - v2.y}; + + float e1Length = sqrtf(e1.x*e1.x + e1.y*e1.y); + float e2Length = sqrtf(e2.x*e2.x + e2.y*e2.y); + float e3Length = sqrtf(e3.x*e3.x + e3.y*e3.y); + + float perimeter = e1Length + e2Length + e3Length; + float semiperimeter = perimeter/2.0f; + + // The incenter of a triangle is equidistant from each edge, which is useful for drawing a nice looking outline + Vector2 incenter = { + (e1Length*v1.x + e2Length*v2.x + e3Length*v3.x)/perimeter, + (e1Length*v1.y + e2Length*v2.y + e3Length*v3.y)/perimeter + }; + + // The inradius of a triangle is the radius of the biggest circle that can fit inside of said triangle + // That circle is also centered on the incenter + float inradius = sqrtf(((semiperimeter - e1Length)*(semiperimeter - e2Length)*(semiperimeter - e3Length))/semiperimeter); + + // The triangle (v1, v2, v3) will be scaled by this to get (v4, v5, v6) + float scale = 1.0f - thick/inradius; + + // Just a filled-in triangle + if (scale <= 0.0f) + { + DrawTriangle(v1, v2, v3, color); + return; + } + + // In order for the scaling to be correct, the incenter has to be at the origin (0, 0) when scaling + Vector2 v4 = {incenter.x + (v1.x - incenter.x)*scale, incenter.y + (v1.y - incenter.y)*scale}; + Vector2 v5 = {incenter.x + (v2.x - incenter.x)*scale, incenter.y + (v2.y - incenter.y)*scale}; + Vector2 v6 = {incenter.x + (v3.x - incenter.x)*scale, incenter.y + (v3.y - incenter.y)*scale}; + + // Swap the vertices so the winding order is correct + if (thick < 0.0f) + { + Vector2 temp = v1; + v1 = v4; + v4 = temp; + + temp = v2; + v2 = v5; + v5 = temp; + + temp = v3; + v3 = v6; + v6 = temp; + } + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + rlColor4ub(color.r, color.g, color.b, color.a); + + // Edge 3 + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v1.x, v1.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v2.x, v2.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v5.x, v5.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v4.x, v4.y); + + // Edge 1 + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v2.x, v2.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v3.x, v3.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v6.x, v6.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v5.x, v5.y); + + // Edge 2 + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v3.x, v3.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v1.x, v1.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v4.x, v4.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v6.x, v6.y); + + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + + rlColor4ub(color.r, color.g, color.b, color.a); + + // Edge 3 + rlVertex2f(v1.x, v1.y); + rlVertex2f(v2.x, v2.y); + rlVertex2f(v4.x, v4.y); + + rlVertex2f(v2.x, v2.y); + rlVertex2f(v5.x, v5.y); + rlVertex2f(v4.x, v4.y); + + // Edge 1 + rlVertex2f(v2.x, v2.y); + rlVertex2f(v3.x, v3.y); + rlVertex2f(v5.x, v5.y); + + rlVertex2f(v3.x, v3.y); + rlVertex2f(v6.x, v6.y); + rlVertex2f(v5.x, v5.y); + + // Edge 2 + rlVertex2f(v3.x, v3.y); + rlVertex2f(v1.x, v1.y); + rlVertex2f(v4.x, v4.y); + + rlVertex2f(v3.x, v3.y); + rlVertex2f(v4.x, v4.y); + rlVertex2f(v6.x, v6.y); + + rlEnd(); +#endif +} + +// Draw a triangle fan defined by points +// NOTE: First vertex provided is the center, shared by all triangles +// By default, following vertex should be provided in counter-clockwise order +void DrawTriangleFan(const Vector2 *points, int pointCount, Color color) +{ + if (pointCount >= 3) + { + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 1; i < pointCount - 1; i++) + { + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(points[0].x, points[0].y); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(points[i].x, points[i].y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(points[i + 1].x, points[i + 1].y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(points[i + 1].x, points[i + 1].y); + } + rlEnd(); + rlSetTexture(0); + } +} + +// Draw a triangle strip defined by points +// NOTE: Every new vertex connects with previous two +void DrawTriangleStrip(const Vector2 *points, int pointCount, Color color) +{ + if (pointCount >= 3) + { + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 2; i < pointCount; i++) + { + if ((i%2) == 0) + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i - 2].x, points[i - 2].y); + rlVertex2f(points[i - 1].x, points[i - 1].y); + } + else + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i - 1].x, points[i - 1].y); + rlVertex2f(points[i - 2].x, points[i - 2].y); + } + } + rlEnd(); + } +} + +// Draw a color-filled rectangle +void DrawRectangle(int posX, int posY, int width, int height, Color color) +{ + DrawRectangleV((Vector2){ (float)posX, (float)posY }, (Vector2){ (float)width, (float)height }, color); +} + +// Draw a color-filled rectangle (Vector version) +// NOTE: On OpenGL 3.3 and ES2 using QUADS to avoid drawing order issues +void DrawRectangleV(Vector2 position, Vector2 size, Color color) +{ + DrawRectanglePro((Rectangle){ position.x, position.y, size.x, size.y }, (Vector2){ 0.0f, 0.0f }, 0.0f, color); +} + +// Draw a color-filled rectangle +void DrawRectangleRec(Rectangle rec, Color color) +{ + DrawRectanglePro(rec, (Vector2){ 0.0f, 0.0f }, 0.0f, color); +} + +// Draw a color-filled rectangle with pro parameters +void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color) +{ + Vector2 topLeft = { 0 }; + Vector2 topRight = { 0 }; + Vector2 bottomLeft = { 0 }; + Vector2 bottomRight = { 0 }; + + // Only calculate rotation if needed + if (rotation == 0.0f) + { + float x = rec.x - origin.x; + float y = rec.y - origin.y; + topLeft = (Vector2){ x, y }; + topRight = (Vector2){ x + rec.width, y }; + bottomLeft = (Vector2){ x, y + rec.height }; + bottomRight = (Vector2){ x + rec.width, y + rec.height }; + } + else + { + float sinRotation = sinf(rotation*DEG2RAD); + float cosRotation = cosf(rotation*DEG2RAD); + float x = rec.x; + float y = rec.y; + float dx = -origin.x; + float dy = -origin.y; + + topLeft.x = x + dx*cosRotation - dy*sinRotation; + topLeft.y = y + dx*sinRotation + dy*cosRotation; + + topRight.x = x + (dx + rec.width)*cosRotation - dy*sinRotation; + topRight.y = y + (dx + rec.width)*sinRotation + dy*cosRotation; + + bottomLeft.x = x + dx*cosRotation - (dy + rec.height)*sinRotation; + bottomLeft.y = y + dx*sinRotation + (dy + rec.height)*cosRotation; + + bottomRight.x = x + (dx + rec.width)*cosRotation - (dy + rec.height)*sinRotation; + bottomRight.y = y + (dx + rec.width)*sinRotation + (dy + rec.height)*cosRotation; + } + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + rlNormal3f(0.0f, 0.0f, 1.0f); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(topLeft.x, topLeft.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(bottomLeft.x, bottomLeft.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(bottomRight.x, bottomRight.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(topRight.x, topRight.y); + + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(topLeft.x, topLeft.y); + rlVertex2f(bottomLeft.x, bottomLeft.y); + rlVertex2f(topRight.x, topRight.y); + + rlVertex2f(topRight.x, topRight.y); + rlVertex2f(bottomLeft.x, bottomLeft.y); + rlVertex2f(bottomRight.x, bottomRight.y); + + rlEnd(); +#endif +} + +// Draw a vertical-gradient-filled rectangle +void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom) +{ + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, top, bottom, bottom, top); +} + +// Draw a horizontal-gradient-filled rectangle +void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right) +{ + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, left, left, right, right); +} + +// Draw a gradient-filled rectangle with custom vertex colors, counter-clockwise color order +void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4) +{ + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + rlNormal3f(0.0f, 0.0f, 1.0f); + + // NOTE: Default raylib font character 95 is a white square + rlColor4ub(col1.r, col1.g, col1.b, col1.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(rec.x, rec.y); + + rlColor4ub(col2.r, col2.g, col2.b, col2.a); + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(rec.x, rec.y + rec.height); + + rlColor4ub(col3.r, col3.g, col3.b, col3.a); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y + rec.height); + + rlColor4ub(col4.r, col4.g, col4.b, col4.a); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y); + rlEnd(); + + rlSetTexture(0); +} + +// Draw rectangle outline +// WARNING: All Draw*Lines() functions use RL_LINES for drawing, +// it implies flushing the current batch and changing draw mode to RL_LINES +// but it solves another issue: https://github.com/raysan5/raylib/issues/3884 +void DrawRectangleLines(int posX, int posY, int width, int height, Color color) +{ + Matrix mat = rlGetMatrixTransform(); + float xOffset = 0.5f/mat.m0; + float yOffset = 0.5f/mat.m5; + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f((float)posX + xOffset, (float)posY + yOffset); + rlVertex2f((float)posX + (float)width - xOffset, (float)posY + yOffset); + + rlVertex2f((float)posX + (float)width - xOffset, (float)posY + yOffset); + rlVertex2f((float)posX + (float)width - xOffset, (float)posY + (float)height - yOffset); + + rlVertex2f((float)posX + (float)width - xOffset, (float)posY + (float)height - yOffset); + rlVertex2f((float)posX + xOffset, (float)posY + (float)height - yOffset); + + rlVertex2f((float)posX + xOffset, (float)posY + (float)height - yOffset); + rlVertex2f((float)posX + xOffset, (float)posY + yOffset); + rlEnd(); + +/* +// Previous implementation, it has issues... but it does not require view matrix... +#if SUPPORT_QUADS_DRAW_MODE + DrawRectangle(posX, posY, width, 1, color); + DrawRectangle(posX + width - 1, posY + 1, 1, height - 2, color); + DrawRectangle(posX, posY + height - 1, width, 1, color); + DrawRectangle(posX, posY + 1, 1, height - 2, color); +#else + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f((float)posX, (float)posY); + rlVertex2f((float)posX + (float)width, (float)posY + 1); + + rlVertex2f((float)posX + (float)width, (float)posY + 1); + rlVertex2f((float)posX + (float)width, (float)posY + (float)height); + + rlVertex2f((float)posX + (float)width, (float)posY + (float)height); + rlVertex2f((float)posX + 1, (float)posY + (float)height); + + rlVertex2f((float)posX + 1, (float)posY + (float)height); + rlVertex2f((float)posX + 1, (float)posY + 1); + rlEnd(); +#endif +*/ +} + +// Draw rectangle outline with line thickness +void DrawRectangleLinesEx(Rectangle rec, float thick, Color color) +{ + if ((thick > rec.width/2) || (thick > rec.height/2)) + { + if (rec.width >= rec.height) thick = rec.height/2; + else if (rec.width <= rec.height) thick = rec.width/2; + } + + if (thick > 0.0f) + { + // When rec = { x, y, 8.0f, 6.0f } and thick = 2, the following + // four rectangles are drawn ([T]op, [B]ottom, [L]eft, [R]ight): + // + // TTTTTTTT + // TTTTTTTT + // LL RR + // LL RR + // BBBBBBBB + // BBBBBBBB + // + + Rectangle top = { rec.x, rec.y, rec.width, thick }; + Rectangle bottom = { rec.x, rec.y - thick + rec.height, rec.width, thick }; + Rectangle left = { rec.x, rec.y + thick, thick, rec.height - thick*2.0f }; + Rectangle right = { rec.x - thick + rec.width, rec.y + thick, thick, rec.height - thick*2.0f }; + + DrawRectangleRec(top, color); + DrawRectangleRec(bottom, color); + DrawRectangleRec(left, color); + DrawRectangleRec(right, color); + } + else + { + // When rec = { x, y, 8.0f, 6.0f } and thick = -2, the following + // four rectangles are drawn ([T]op, [B]ottom, [L]eft, [R]ight): + // + // TTTTTTTTTTTT + // TTTTTTTTTTTT + // LL RR + // LL RR + // LL RR + // LL RR + // LL RR + // LL RR + // BBBBBBBBBBBB + // BBBBBBBBBBBB + // + + thick *= -1.0f; + + Rectangle top = { rec.x - thick, rec.y - thick, rec.width + thick*2.0f, thick }; + Rectangle bottom = { rec.x - thick, rec.y + rec.height, rec.width + thick*2.0f, thick}; + Rectangle left = { rec.x - thick, rec.y, thick, rec.height }; + Rectangle right = { rec.x + rec.width, rec.y, thick, rec.height }; + + DrawRectangleRec(top, color); + DrawRectangleRec(bottom, color); + DrawRectangleRec(left, color); + DrawRectangleRec(right, color); + } +} + +// Draw rectangle with rounded edges +void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color) +{ + // Not a rounded rectangle + if (roundness <= 0.0f) + { + DrawRectangleRec(rec, color); + return; + } + + if (roundness >= 1.0f) roundness = 1.0f; + + // Calculate corner radius + float radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)ceilf((2*PI/th)/4.0f); + if (segments <= 0) segments = 4; + } + + float stepLength = 90.0f/(float)segments; + + /* + Quick sketch to make sense of all of this, + there are 9 parts to draw, also mark the 12 points used + + P0____________________P1 + /| |\ + /1| 2 |3\ + P7 /__|____________________|__\ P2 + | |P8 P9| | + | 8 | 9 | 4 | + | __|____________________|__ | + P6 \ |P11 P10| / P3 + \7| 6 |5/ + \|____________________|/ + P5 P4 + */ + // Coordinates of the 12 points that define the rounded rect + const Vector2 point[12] = { + {(float)rec.x + radius, rec.y}, {(float)(rec.x + rec.width) - radius, rec.y}, { rec.x + rec.width, (float)rec.y + radius }, // PO, P1, P2 + {rec.x + rec.width, (float)(rec.y + rec.height) - radius}, {(float)(rec.x + rec.width) - radius, rec.y + rec.height}, // P3, P4 + {(float)rec.x + radius, rec.y + rec.height}, { rec.x, (float)(rec.y + rec.height) - radius}, {rec.x, (float)rec.y + radius}, // P5, P6, P7 + {(float)rec.x + radius, (float)rec.y + radius}, {(float)(rec.x + rec.width) - radius, (float)rec.y + radius}, // P8, P9 + {(float)(rec.x + rec.width) - radius, (float)(rec.y + rec.height) - radius}, {(float)rec.x + radius, (float)(rec.y + rec.height) - radius} // P10, P11 + }; + + const Vector2 centers[4] = { point[8], point[9], point[10], point[11] }; + const float angles[4] = { 180.0f, 270.0f, 0.0f, 90.0f }; + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + // Draw all the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + + // NOTE: Every QUAD actually represents two segments + for (int i = 0; i < segments/2; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength*2))*radius, center.y + sinf(DEG2RAD*(angle + stepLength*2))*radius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*radius, center.y + sinf(DEG2RAD*(angle + stepLength))*radius); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*radius, center.y + sinf(DEG2RAD*angle)*radius); + + angle += (stepLength*2); + } + + // NOTE: In case number of segments is odd, adding one last piece to the cake + if (segments%2) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*radius, center.y + sinf(DEG2RAD*(angle + stepLength))*radius); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*radius, center.y + sinf(DEG2RAD*angle)*radius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + } + } + + // [2] Upper Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[0].x, point[0].y); + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[1].x, point[1].y); + + // [4] Right Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[2].x, point[2].y); + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[3].x, point[3].y); + + // [6] Bottom Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[5].x, point[5].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[4].x, point[4].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + + // [8] Left Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[7].x, point[7].y); + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[6].x, point[6].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + + // [9] Middle Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + + rlEnd(); + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + + // Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*radius, center.y + sinf(DEG2RAD*(angle + stepLength))*radius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*radius, center.y + sinf(DEG2RAD*angle)*radius); + angle += stepLength; + } + } + + // [2] Upper Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[1].x, point[1].y); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[9].x, point[9].y); + + // [4] Right Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[3].x, point[3].y); + rlVertex2f(point[2].x, point[2].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[3].x, point[3].y); + + // [6] Bottom Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[5].x, point[5].y); + rlVertex2f(point[4].x, point[4].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[4].x, point[4].y); + + // [8] Left Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[6].x, point[6].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[11].x, point[11].y); + + // [9] Middle Rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[10].x, point[10].y); + rlEnd(); +#endif +} + +// Draw rectangle with rounded edges +void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color) +{ + // Not a rounded rectangle + if (roundness <= 0.0f) + { + DrawRectangleLines((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, color); + return; + } + + if (roundness >= 1.0f) roundness = 1.0f; + + // Calculate corner radius + float radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)ceilf((2*PI/th)/4.0f); + if (segments <= 0) segments = 4; + } + + float stepLength = 90.0f/(float)segments; + + /* + Quick sketch to make sense of all of this, + marks the 8 + 4 (corner centers P8-11) points used + + P0 ------------------ P1 + / \ + / \ + P7 / \ P2 + | *P8 P9* | + | | + | | + P6 \ *P11 P10* / P3 + \ / + \ / + P5 ------------------ P4 + */ + + // The x-coordinates used for the outline + const float x0 = rec.x + radius + 0.5f; + const float x1 = (rec.x + rec.width) - radius - 0.5f; + const float x2 = rec.x + rec.width - 0.5f; + const float x3 = rec.x + 0.5f; + + // The y-coordinates used for the outline + const float y0 = rec.y + 0.5f; + const float y1 = rec.y + radius + 0.5f; + const float y2 = (rec.y + rec.height) - radius - 0.5f; + const float y3 = rec.y + rec.height - 0.5f; + + const Vector2 point[8] = { + {x0, y0}, // P0 + {x1, y0}, // P1 + {x2, y1}, // P2 + {x2, y2}, // P3 + {x1, y3}, // P4 + {x0, y3}, // P5 + {x3, y2}, // P6 + {x3, y1}, // P7 + }; + + const Vector2 centers[4] = { + {x0, y1}, // P16 + {x1, y1}, // P17 + {x1, y2}, // P18 + {x0, y2} // P19 + }; + + const float angles[4] = { 180.0f, 270.0f, 0.0f, 90.0f }; + + rlBegin(RL_LINES); + // Draw all the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*radius, center.y + sinf(DEG2RAD*angle)*radius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*radius, center.y + sinf(DEG2RAD*(angle + stepLength))*radius); + angle += stepLength; + } + } + + // And now the remaining 4 lines + for (int i = 0; i < 8; i += 2) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[i].x, point[i].y); + rlVertex2f(point[i + 1].x, point[i + 1].y); + } + rlEnd(); +} + +// Draw rectangle with rounded edges outline with line thickness +void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float thick, Color color) +{ + // Not a rounded rectangle + if (roundness <= 0.0f) + { + DrawRectangleLinesEx(rec, thick, color); + return; + } + + if (roundness >= 1.0f) roundness = 1.0f; + + float radius = 0.0f; + float roundedOutlineThick = 0.0f; + float outerRadius = 0.0f; + float innerRadius = 0.0f; + if (thick >= 0.0f) + { + // Calculate corner radius + radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; + + outerRadius = radius; + innerRadius = outerRadius - thick; + + // The maximum thickness the outline can have and still be rounded on the interior edge is equal to the corner radius + // Put another way, when `innerRadius <= 0`, the interior of the outline is just a normal rectangle with no rounding + if (innerRadius <= 0.0f) + { + innerRadius = 0.0f; + roundedOutlineThick = outerRadius; + + // Draw the not-rounded portion of the outline + DrawRectangleLinesEx((Rectangle){ rec.x + outerRadius, rec.y + outerRadius, rec.width - outerRadius*2.0f, rec.height - outerRadius*2.0f }, thick - outerRadius, color); + } + else + { + roundedOutlineThick = thick; + } + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/outerRadius, 2) - 1); + segments = (int)ceilf((2*PI/th)/4.0f); + if (segments <= 0) segments = 4; + } + } + else + { + thick *= -1.0f; + + // Calculate corner radius + radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; // Only possible if the rectangle has 0 width or height + + // Expand the rectangle + rec.x -= thick; + rec.y -= thick; + rec.width += thick*2.0f; + rec.height += thick*2.0f; + + innerRadius = radius; + outerRadius = innerRadius + thick; + roundedOutlineThick = thick; + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/innerRadius, 2) - 1); + segments = (int)ceilf((2*PI/th)/4.0f); + if (segments <= 0) segments = 4; + } + } + + float stepLength = 90.0f/(float)segments; + + /* + Quick sketch to make sense of all of this, + marks the 16 + 4(corner centers P16-19) points used + + P0 ================== P1 + // P8 P9 \\ + // \\ + P7 // P15 P10 \\ P2 + || *P16 P17* || + || || + || P14 P11 || + P6 \\ *P19 P18* // P3 + \\ // + \\ P13 P12 // + P5 ================== P4 + */ + + // The x-coordinates used for the outline + const float x0 = rec.x + outerRadius; + const float x1 = (rec.x + rec.width) - outerRadius; + const float x2 = rec.x + rec.width; + const float x3 = rec.x; + const float x4 = rec.x + rec.width - roundedOutlineThick; + const float x5 = rec.x + roundedOutlineThick; + + // The y-coordinates used for the outline + const float y0 = rec.y; + const float y1 = rec.y + outerRadius; + const float y2 = (rec.y + rec.height) - outerRadius; + const float y3 = rec.y + rec.height; + const float y4 = rec.y + roundedOutlineThick; + const float y5 = rec.y + rec.height - roundedOutlineThick; + + const Vector2 point[16] = { + {x0, y0}, // P0 + {x1, y0}, // P1 + {x2, y1}, // P2 + {x2, y2}, // P3 + {x1, y3}, // P4 + {x0, y3}, // P5 + {x3, y2}, // P6 + {x3, y1}, // P7 + {x0, y4}, // P8 + {x1, y4}, // P9 + {x4, y1}, // P10 + {x4, y2}, // P11 + {x1, y5}, // P12 + {x0, y5}, // P13 + {x5, y2}, // P14 + {x5, y1} // P15 + }; + + const Vector2 centers[4] = { + {x0, y1}, // P16 + {x1, y1}, // P17 + {x1, y2}, // P18 + {x0, y2} // P19 + }; + + const float angles[4] = { 180.0f, 270.0f, 0.0f, 90.0f }; + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + // Draw all the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + + angle += stepLength; + } + } + + // Upper rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[0].x, point[0].y); + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[8].x, point[8].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[9].x, point[9].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[1].x, point[1].y); + + // Right rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[2].x, point[2].y); + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[10].x, point[10].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[11].x, point[11].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[3].x, point[3].y); + + // Lower rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[13].x, point[13].y); + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[5].x, point[5].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[4].x, point[4].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[12].x, point[12].y); + + // Left rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[15].x, point[15].y); + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[7].x, point[7].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(point[6].x, point[6].y); + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(point[14].x, point[14].y); + + rlEnd(); + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + + // Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner + for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const Vector2 center = centers[k]; + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + + angle += stepLength; + } + } + + // Upper rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[8].x, point[8].y); + rlVertex2f(point[9].x, point[9].y); + rlVertex2f(point[1].x, point[1].y); + rlVertex2f(point[0].x, point[0].y); + rlVertex2f(point[9].x, point[9].y); + + // Right rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[11].x, point[11].y); + rlVertex2f(point[3].x, point[3].y); + rlVertex2f(point[2].x, point[2].y); + rlVertex2f(point[10].x, point[10].y); + rlVertex2f(point[3].x, point[3].y); + + // Lower rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[13].x, point[13].y); + rlVertex2f(point[5].x, point[5].y); + rlVertex2f(point[4].x, point[4].y); + rlVertex2f(point[12].x, point[12].y); + rlVertex2f(point[13].x, point[13].y); + rlVertex2f(point[4].x, point[4].y); + + // Left rectangle + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[6].x, point[6].y); + rlVertex2f(point[14].x, point[14].y); + rlVertex2f(point[15].x, point[15].y); + rlVertex2f(point[7].x, point[7].y); + rlVertex2f(point[14].x, point[14].y); + rlEnd(); +#endif +} + +// Draw a polygon of n sides +void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color) +{ + if (sides < 3) sides = 3; + float centralAngle = rotation*DEG2RAD; + float angleStep = 360.0f/(float)sides*DEG2RAD; + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + float nextAngle = centralAngle + angleStep; + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(centralAngle)*radius, center.y + sinf(centralAngle)*radius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(nextAngle)*radius, center.y + sinf(nextAngle)*radius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(centralAngle)*radius, center.y + sinf(centralAngle)*radius); + + centralAngle = nextAngle; + } + rlEnd(); + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + cosf(centralAngle + angleStep)*radius, center.y + sinf(centralAngle + angleStep)*radius); + rlVertex2f(center.x + cosf(centralAngle)*radius, center.y + sinf(centralAngle)*radius); + + centralAngle += angleStep; + } + rlEnd(); +#endif +} + +// Draw a polygon outline of n sides +void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color) +{ + if (sides < 3) sides = 3; + float centralAngle = rotation*DEG2RAD; + float angleStep = 360.0f/(float)sides*DEG2RAD; + + rlBegin(RL_LINES); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + cosf(centralAngle)*radius, center.y + sinf(centralAngle)*radius); + rlVertex2f(center.x + cosf(centralAngle + angleStep)*radius, center.y + sinf(centralAngle + angleStep)*radius); + + centralAngle += angleStep; + } + rlEnd(); +} + +void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float thick, Color color) +{ + if (sides < 3) sides = 3; + float centralAngle = rotation*DEG2RAD; + float exteriorAngle = 360.0f/(float)sides*DEG2RAD; + float apothem = radius*cosf(DEG2RAD*180.0f/(float)sides); + + float outerRadius = 0.0f; + float innerRadius = 0.0f; + if (thick >= 0.0f) + { + outerRadius = radius; + innerRadius = fmaxf(0.0f, radius - thick*(radius/apothem)); + } + else + { + thick *= -1.0f; + outerRadius = radius + thick*(radius/apothem); + innerRadius = radius; + } + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + float nextAngle = centralAngle + exteriorAngle; + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(centralAngle)*outerRadius, center.y + sinf(centralAngle)*outerRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(centralAngle)*innerRadius, center.y + sinf(centralAngle)*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(nextAngle)*innerRadius, center.y + sinf(nextAngle)*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(nextAngle)*outerRadius, center.y + sinf(nextAngle)*outerRadius); + + centralAngle = nextAngle; + } + rlEnd(); + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + float nextAngle = centralAngle + exteriorAngle; + + rlVertex2f(center.x + cosf(nextAngle)*outerRadius, center.y + sinf(nextAngle)*outerRadius); + rlVertex2f(center.x + cosf(centralAngle)*outerRadius, center.y + sinf(centralAngle)*outerRadius); + rlVertex2f(center.x + cosf(centralAngle)*innerRadius, center.y + sinf(centralAngle)*innerRadius); + + rlVertex2f(center.x + cosf(centralAngle)*innerRadius, center.y + sinf(centralAngle)*innerRadius); + rlVertex2f(center.x + cosf(nextAngle)*innerRadius, center.y + sinf(nextAngle)*innerRadius); + rlVertex2f(center.x + cosf(nextAngle)*outerRadius, center.y + sinf(nextAngle)*outerRadius); + + centralAngle = nextAngle; + } + rlEnd(); +#endif +} + +// Draw a color-filled circle +void DrawCircle(int centerX, int centerY, float radius, Color color) +{ + DrawCircleV((Vector2){ (float)centerX, (float)centerY }, radius, color); +} + +// Draw a color-filled circle (Vector version) +// NOTE: On OpenGL 3.3 and ES2 using QUADS to avoid drawing order issues +void DrawCircleV(Vector2 center, float radius, Color color) +{ + DrawCircleSector(center, radius, 0, 360, 36, color); +} + +// Draw a gradient-filled circle +void DrawCircleGradient(Vector2 center, float radius, Color inner, Color outer) +{ + rlBegin(RL_TRIANGLES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(inner.r, inner.g, inner.b, inner.a); + rlVertex2f(center.x, center.y); + rlColor4ub(outer.r, outer.g, outer.b, outer.a); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*radius, center.y + sinf(DEG2RAD*(i + 10))*radius); + rlColor4ub(outer.r, outer.g, outer.b, outer.a); + rlVertex2f(center.x + cosf(DEG2RAD*i)*radius, center.y + sinf(DEG2RAD*i)*radius); + } + rlEnd(); +} + +// Draw a piece of a circle +void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color) +{ + if (startAngle == endAngle) return; + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + // Drawing a whole circle, things get weird without limiting the circle to 360 degrees + if (endAngle - startAngle >= 360.0f) endAngle = startAngle + 360.0f; + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)ceilf((endAngle - startAngle)*(2*PI/th)/360.0f); + + if (segments <= 0) segments = minSegments; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + // NOTE: Every QUAD actually represents two segments + for (int i = 0; i < segments/2; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength*2.0f))*radius, center.y + sinf(DEG2RAD*(angle + stepLength*2.0f))*radius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*radius, center.y + sinf(DEG2RAD*(angle + stepLength))*radius); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*radius, center.y + sinf(DEG2RAD*angle)*radius); + + angle += (stepLength*2.0f); + } + + // NOTE: In case number of segments is odd, adding one last piece to the cake + if ((((unsigned int)segments)%2) == 1) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*radius, center.y + sinf(DEG2RAD*(angle + stepLength))*radius); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*radius, center.y + sinf(DEG2RAD*angle)*radius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + } + + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*radius, center.y + sinf(DEG2RAD*(angle + stepLength))*radius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*radius, center.y + sinf(DEG2RAD*angle)*radius); + + angle += stepLength; + } + rlEnd(); +#endif +} + +// Draw a piece of a circle outlines +void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color) +{ + if (startAngle == endAngle) return; + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero issue + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + bool showCapLines = true; + // Drawing a whole circle, things get weird without limiting the circle to 360 degrees + if (endAngle - startAngle >= 360.0f) + { + showCapLines = false; + endAngle = startAngle + 360.0f; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)ceilf((endAngle - startAngle)*(2*PI/th)/360.0f); + + if (segments <= 0) segments = minSegments; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + + rlBegin(RL_LINES); + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*radius, center.y + sinf(DEG2RAD*angle)*radius); + } + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + cosf(DEG2RAD*angle)*radius, center.y + sinf(DEG2RAD*angle)*radius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*radius, center.y + sinf(DEG2RAD*(angle + stepLength))*radius); + + angle += stepLength; + } + + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*radius, center.y + sinf(DEG2RAD*angle)*radius); + } + rlEnd(); +} + +// Draw a piece of a circle outlines with thickness +void DrawCircleSectorLinesEx(Vector2 center, float radius, float startAngle, float endAngle, int segments, float thick, Color color) +{ + if (startAngle == endAngle) return; + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero issue + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + bool showCapLines = true; + // Drawing a whole circle, things get weird without limiting the circle to 360 degrees + if (endAngle - startAngle >= 360.0f) + { + showCapLines = thick >= 0.0f; + endAngle = startAngle + 360.0f; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)ceilf((endAngle - startAngle)*(2*PI/th)/360.0f); + + if (segments <= 0) segments = minSegments; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + + /* + A sketch to help make things clearer + + NOTE: Some considerations are different when `thick` is negative + The vertices used here are still relevant, but would instead be outside of the circle + S0 is always the center + + The circle sector outline is drawn in 3 main pieces, the circle outline, cap 1, and cap 2 + The circle outline is self explanatory + Cap 1 covers the `startAngle` edge and cap 2 covers the `endAngle` edge + S0 is the first shared point between the caps, and also the circle's center + S1 is the second shared point (sometimes not shared) between the caps + S1 is also C0 and C3 in this sketch. In certain cases, S1 goes outside of + the circle and C0 and C3 become different points + C1 is one of cap 1's vertices that is on the inside edge of the circle outline + C2 is like C1, but is also on the `startAngle` edge + C4 is cap 2's vertex that corresponds with C1 + C5 is cap 2's vertex that corresponds with C2, except on the `endAngle` edge + + [][][][][] + [][] [] + [] []C4[]C5 + [] [][] {} {} + [] [] {} {} + [] [] {}C {} <- endAngle + [] [] {}a {} + [] [] {}p {} + [] [] {}2 {} + [] [] {} {} + [] [] {} {} startAngle + [] [] {} S0{}{}{}{}{}{}{}{}C2[][] + [] [] {}{} Cap1 [] [] + [] [] S1{}{}{}{}{}{}{}{}{}{}C1 [] + [] [] [] [] + [] [] [] [] + [] [] Not filled in [] [] + [] [] [] [] + [] [] [] [] + [] [][] [][] [] + [] [][][][][][][] [] + [][] Circle outline [][] + [][][][][][][][][] + + [] = Circle outline edge pixel + {} = Cap outline edge pixel + */ + + // We are not drawing a circle, we are drawing an n-sided polygon + // So, we need to adjust the outline thickness of the "circle" for it to look correct with fewer segments + float apothem = radius*cosf(DEG2RAD*((endAngle - startAngle)/2.0f)/(float)segments); + float radiusThick = thick*(radius/apothem); + + float outerRadius = radius; + float innerRadius = radius - radiusThick; + + if (thick >= 0.0f) + { + if (thick >= innerRadius) + { + DrawCircleSector(center, radius, startAngle, endAngle, segments, color); + return; + } + } + else + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + } + + // Cap 1 vertices + Vector2 c0 = { 0 }; + Vector2 c1 = { 0 }; + Vector2 c2 = { 0 }; + + // Cap 2 vertices + Vector2 c3 = { 0 }; + Vector2 c4 = { 0 }; + Vector2 c5 = { 0 }; + + // The number of angle steps that come before C1 (from `startAngle`, counter clockwise) + int stepsBeforeC1 = 0; + bool s1OutsideOfCircle = false; + // The number of angle steps that come before C0 (from `startAngle`, counter clockwise) + // Only used if S1 is outside of the circle + int stepsBeforeC0 = 0; + + if (showCapLines) + { + if (thick >= 0.0f) + { + c2 = (Vector2){ center.x + cosf(DEG2RAD*startAngle)*innerRadius, center.y + sinf(DEG2RAD*startAngle)*innerRadius }; + c5 = (Vector2){ center.x + cosf(DEG2RAD*endAngle)*innerRadius, center.y + sinf(DEG2RAD*endAngle)*innerRadius }; + + // For C1 and C4, we need to find the point that lies on the circle (n-sided polygon, actually) + // We want C1 and C4 to be `thick` pixels perpendicularly from the `startAngle` and `endAngle` edges + // and to be on the `innerRadius` edge + + float c1Angle = RAD2DEG*asinf(thick/innerRadius); + + // There are more segments before C1 than there are segments being drawn, + // so the whole circle sector must be covered + if (c1Angle/stepLength >= (float)segments) + { + DrawCircleSector(center, radius, startAngle, endAngle, segments, color); + return; + } + + // Do this after the previous check just in case `stepLength` is really small and + // dividing by it produces a very large number + stepsBeforeC1 = (int)(c1Angle/stepLength); + + // The angles of the vertices on the circle outline before and after C1 + float vertexAngleBeforeC1 = stepLength*(float)stepsBeforeC1; + float vertexAngleAfterC1 = stepLength*((float)(stepsBeforeC1 + 1)); + + /* + Here is another sketch + + We know which outline line segment C1 is on (`vertexAngleBeforeC1` and `vertexAngleAfterC1`) + Now we just need to know where on that line segment C1 is + + We can change our frame of reference so that `startAngle` is 0 degrees and `center` is at the origin (0, 0) + This makes the math much simpler because now we can just go straight down by `thick` pixels and + use the horizontal line that passes through that point to determine where C1 is on our line segment + + The line segment is defined by p1 and p2, we need C1, which is on that edge + The 'y' axis of C1 is equal to `thick` (within this modified frame of reference) + + p1 + /| + / | + / | + / | + / | + / | + / | + / | + C1--------- <-- y axis = `thick` + / + / + p2 + */ + + Vector2 p1 = { cosf(DEG2RAD*vertexAngleBeforeC1)*innerRadius, sinf(DEG2RAD*vertexAngleBeforeC1)*innerRadius }; + Vector2 p2 = { cosf(DEG2RAD*vertexAngleAfterC1)*innerRadius, sinf(DEG2RAD*vertexAngleAfterC1)*innerRadius }; + + // Find the `t` of C1 between p1 and p2 ('t' as in `Lerp(start, end, t)`) + // This is used to lerp between the actual vertices (outside of our modified frame of reference) + // before and after C1 + float t = (p1.y - thick)/(p1.y - p2.y); + + Vector2 vertexBeforeCap1Vertex = { center.x + cosf(DEG2RAD*(startAngle + vertexAngleBeforeC1))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + vertexAngleBeforeC1))*innerRadius }; + Vector2 vertexAfterCap1Vertex = { center.x + cosf(DEG2RAD*(startAngle + vertexAngleAfterC1))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + vertexAngleAfterC1))*innerRadius }; + + c1.x = vertexBeforeCap1Vertex.x + (vertexAfterCap1Vertex.x - vertexBeforeCap1Vertex.x)*t; + c1.y = vertexBeforeCap1Vertex.y + (vertexAfterCap1Vertex.y - vertexBeforeCap1Vertex.y)*t; + + Vector2 vertexBeforeCap2Vertex = { center.x + cosf(DEG2RAD*(endAngle - vertexAngleBeforeC1))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - vertexAngleBeforeC1))*innerRadius }; + Vector2 vertexAfterCap2Vertex = { center.x + cosf(DEG2RAD*(endAngle - vertexAngleAfterC1))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - vertexAngleAfterC1))*innerRadius }; + + c4.x = vertexBeforeCap2Vertex.x + (vertexAfterCap2Vertex.x - vertexBeforeCap2Vertex.x)*t; + c4.y = vertexBeforeCap2Vertex.y + (vertexAfterCap2Vertex.y - vertexBeforeCap2Vertex.y)*t; + + /* + Another sketch couldn't hurt + + This is a "zoomed in" view of the center of the circle sector + You can see where S0 is, and where we want S1 to be + `innerAngleBetweenCapEnds` is the angle of the diagonal line ('//') between the cap ends + `S1Length` is the length of that line + + Since the caps are always parallel to `startAngle` and `endAngle`, + we always have a right triangle we can use to determine where S1 is + + [] [] + [] [] + [] C [] <- endAngle + [] a [] + [] p [] + [] 2 [] startAngle + [][][][]S0[][][][][][] + //[] + // [] Cap 1 + // [] + S1 [][][][][][][] + */ + + float innerAngleBetweenCapEnds = ((endAngle - 90.0f) - (startAngle + 90.0f))/2.0f; + float s1Length = thick/cosf(DEG2RAD*innerAngleBetweenCapEnds); + + // As `startAngle` and `endAngle` draw more of a circle, S1 goes further out from the center + // It can go so far that it is outside of the circle, by a lot + // This case needs to be detected and handled + // If S1 is within the circle, nothing special needs to happen + // But, if S1 is outside of the circle, we need to find the two points (C0 and C3) where + // the line segments C0->C1 and C0->C3 intersect the circle outline, + // using the same method we used to find C1 and C4 + if ((innerAngleBetweenCapEnds < 90.0f) && (s1Length <= innerRadius)) + { + // S1 is inside of the circle + + float betweenStartAndEndAngle = (endAngle + startAngle)/2.0f; + c0 = (Vector2){ center.x + cosf(DEG2RAD*betweenStartAndEndAngle)*s1Length, center.y + sinf(DEG2RAD*betweenStartAndEndAngle)*s1Length }; + c3 = c0; + + p1 = (Vector2){ c1.x - center.x, c1.y - center.y }; + p2 = (Vector2){ c0.x - center.x, c0.y - center.y }; + + // Copied from "raymath.h" Vector2Angle() + float dot = p1.x*p2.x + p1.y*p2.y; + float det = p1.x*p2.y - p1.y*p2.x; + float c1ToS1Angle = atan2f(det, dot); + + // If C1 and C4 are on the wrong side of S1, the whole circle sector is covered + if (c1ToS1Angle < 0.0f) + { + DrawCircleSector(center, radius, startAngle, endAngle, segments, color); + return; + } + } + else + { + // S1 is outside of the circle + + if (endAngle - startAngle <= 180.0f) + { + DrawCircleSector(center, radius, startAngle, endAngle, segments, color); + return; + } + + s1OutsideOfCircle = true; + + stepsBeforeC0 = (int)((180.0f + RAD2DEG*asinf(thick/-innerRadius))/stepLength); + + // Reuse the code for finding C1 and C4 to find C0 and C3 + + float vertexAngleBeforeC0 = stepLength*(float)stepsBeforeC0; + float vertexAngleAfterC0 = stepLength*((float)(stepsBeforeC0 + 1)); + + p1 = (Vector2){ cosf(DEG2RAD*vertexAngleBeforeC0)*innerRadius, sinf(DEG2RAD*vertexAngleBeforeC0)*innerRadius }; + p2 = (Vector2){ cosf(DEG2RAD*vertexAngleAfterC0)*innerRadius, sinf(DEG2RAD*vertexAngleAfterC0)*innerRadius }; + + t = (p1.y - thick)/(p1.y - p2.y); + + vertexBeforeCap1Vertex = (Vector2){ center.x + cosf(DEG2RAD*(startAngle + vertexAngleBeforeC0))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + vertexAngleBeforeC0))*innerRadius }; + vertexAfterCap1Vertex = (Vector2){ center.x + cosf(DEG2RAD*(startAngle + vertexAngleAfterC0))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + vertexAngleAfterC0))*innerRadius }; + + c0.x = vertexBeforeCap1Vertex.x + (vertexAfterCap1Vertex.x - vertexBeforeCap1Vertex.x)*t; + c0.y = vertexBeforeCap1Vertex.y + (vertexAfterCap1Vertex.y - vertexBeforeCap1Vertex.y)*t; + + vertexBeforeCap2Vertex = (Vector2){ center.x + cosf(DEG2RAD*(endAngle - vertexAngleBeforeC0))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - vertexAngleBeforeC0))*innerRadius }; + vertexAfterCap2Vertex = (Vector2){ center.x + cosf(DEG2RAD*(endAngle - vertexAngleAfterC0))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - vertexAngleAfterC0))*innerRadius }; + + c3.x = vertexBeforeCap2Vertex.x + (vertexAfterCap2Vertex.x - vertexBeforeCap2Vertex.x)*t; + c3.y = vertexBeforeCap2Vertex.y + (vertexAfterCap2Vertex.y - vertexBeforeCap2Vertex.y)*t; + } + } + else + { + float outerAngleBetweenCapEnds = ((endAngle + 90.0f) - (startAngle - 90.0f))/2.0f; + float s1Length = thick/cosf(DEG2RAD*outerAngleBetweenCapEnds); + float betweenStartAndEndAngle = 180.0f + (endAngle + startAngle)/2.0f; + c0 = (Vector2){ center.x + cosf(DEG2RAD*betweenStartAndEndAngle)*s1Length, center.y + sinf(DEG2RAD*betweenStartAndEndAngle)*s1Length }; + c3 = c0; + + c2 = (Vector2){ center.x + cosf(DEG2RAD*startAngle)*outerRadius, center.y + sinf(DEG2RAD*startAngle)*outerRadius }; + c5 = (Vector2){ center.x + cosf(DEG2RAD*endAngle)*outerRadius, center.y + sinf(DEG2RAD*endAngle)*outerRadius }; + + // Change the frame of reference so that `center` is the origin and `startAngle` is 0 degrees + + Vector2 c0Translated = { c0.x - center.x, c0.y - center.y }; + Vector2 circleVertex1 = { c2.x - center.x, c2.y - center.y }; + Vector2 circleVertex2 = { cosf(DEG2RAD*(startAngle + stepLength))*outerRadius, sinf(DEG2RAD*(startAngle + stepLength))*outerRadius }; + + // Copied from "raymath.h" Vector2Rotate() + float tempX = c0Translated.x; + c0Translated.x = cosf(-DEG2RAD*startAngle)*tempX - sinf(-DEG2RAD*startAngle)*c0Translated.y; + c0Translated.y = sinf(-DEG2RAD*startAngle)*tempX + cosf(-DEG2RAD*startAngle)*c0Translated.y; + + tempX = circleVertex1.x; + circleVertex1.x = cosf(-DEG2RAD*startAngle)*tempX - sinf(-DEG2RAD*startAngle)*circleVertex1.y; + circleVertex1.y = sinf(-DEG2RAD*startAngle)*tempX + cosf(-DEG2RAD*startAngle)*circleVertex1.y; + + tempX = circleVertex2.x; + circleVertex2.x = cosf(-DEG2RAD*startAngle)*tempX - sinf(-DEG2RAD*startAngle)*circleVertex2.y; + circleVertex2.y = sinf(-DEG2RAD*startAngle)*tempX + cosf(-DEG2RAD*startAngle)*circleVertex2.y; + + // Figure out the line that `circleVertex1` and `circleVertex2` are on + float rise = circleVertex1.y - circleVertex2.y; + float run = circleVertex1.x - circleVertex2.x; + // Get where that line intersects the horizontal line that `c0Translated` is on + float c1Rise = c0Translated.y - circleVertex1.y; + float c1Run = (c1Rise/rise)*run; + float c1DistanceFromC0 = (circleVertex1.x + c1Run) - c0Translated.x; + + c1 = (Vector2){ c0.x + cosf(DEG2RAD*startAngle)*c1DistanceFromC0, c0.y + sinf(DEG2RAD*startAngle)*c1DistanceFromC0 }; + c4 = (Vector2){ c0.x + cosf(DEG2RAD*endAngle)*c1DistanceFromC0, c0.y + sinf(DEG2RAD*endAngle)*c1DistanceFromC0 }; + + if (c1DistanceFromC0 < 0.0f) + { + // The caps are intersecting each other + + Vector2 circleVertex3 = { c5.x - center.x, c5.y - center.y }; + Vector2 circleVertex4 = { cosf(DEG2RAD*(endAngle - stepLength))*outerRadius, sinf(DEG2RAD*(endAngle - stepLength))*outerRadius }; + + tempX = circleVertex3.x; + circleVertex3.x = cosf(-DEG2RAD*startAngle)*tempX - sinf(-DEG2RAD*startAngle)*circleVertex3.y; + circleVertex3.y = sinf(-DEG2RAD*startAngle)*tempX + cosf(-DEG2RAD*startAngle)*circleVertex3.y; + + tempX = circleVertex4.x; + circleVertex4.x = cosf(-DEG2RAD*startAngle)*tempX - sinf(-DEG2RAD*startAngle)*circleVertex4.y; + circleVertex4.y = sinf(-DEG2RAD*startAngle)*tempX + cosf(-DEG2RAD*startAngle)*circleVertex4.y; + + // `startAngle` is 0 degrees within this frame of reference, + // so C1 just goes horizontally out from C0 + Vector2 c1Translated = { c0Translated.x + c1DistanceFromC0, c0Translated.y }; + + // Make `circleVertex2` the origin + circleVertex1.x -= circleVertex2.x; + circleVertex1.y -= circleVertex2.y; + circleVertex3.x -= circleVertex2.x; + circleVertex3.y -= circleVertex2.y; + circleVertex4.x -= circleVertex2.x; + circleVertex4.y -= circleVertex2.y; + c1Translated.x -= circleVertex2.x; + c1Translated.y -= circleVertex2.y; + + // Make the line between `circleVertex1` and `circleVertex2` a horizontal line + float theta = atan2f(circleVertex1.y, circleVertex1.x); + + // Copied from "raymath.h" Vector2Rotate() + tempX = circleVertex1.x; + circleVertex1.x = cosf(-theta)*tempX - sinf(-theta)*circleVertex1.y; + circleVertex1.y = sinf(-theta)*tempX + cosf(-theta)*circleVertex1.y; + + tempX = circleVertex3.x; + circleVertex3.x = cosf(-theta)*tempX - sinf(-theta)*circleVertex3.y; + circleVertex3.y = sinf(-theta)*tempX + cosf(-theta)*circleVertex3.y; + + tempX = circleVertex4.x; + circleVertex4.x = cosf(-theta)*tempX - sinf(-theta)*circleVertex4.y; + circleVertex4.y = sinf(-theta)*tempX + cosf(-theta)*circleVertex4.y; + + tempX = c1Translated.x; + c1Translated.x = cosf(-theta)*tempX - sinf(-theta)*c1Translated.y; + c1Translated.y = sinf(-theta)*tempX + cosf(-theta)*c1Translated.y; + + // Find where the line that `circleVertex3` and `circleVertex4` are on would intersect the + // line segment defined by `circleVertex1` and `c1Translated` + rise = circleVertex3.y - circleVertex4.y; + run = circleVertex3.x - circleVertex4.x; + float targetRise = -circleVertex3.y; + float targetX = circleVertex3.x + (targetRise/rise)*run; + + float t = (c1Translated.x - targetX)/(c1Translated.x - circleVertex1.x); + + c1 = (Vector2){ c1.x + (c2.x - c1.x)*t, c1.y + (c2.y - c1.y)*t }; + c4 = c1; + c0 = c1; + c3 = c1; + } + + // Swap vertices to correct the winding order + Vector2 temp = c0; + c0 = c2; + c2 = temp; + + temp = c3; + c3 = c5; + c5 = temp; + } + } + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + rlColor4ub(color.r, color.g, color.b, color.a); + + // Draw the circle outline + for (int i = 0; i < segments; i++) + { + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerRadius); + + angle += stepLength; + } + + // Draw the caps + if (showCapLines) + { + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c0.x, c0.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c1.x, c1.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c2.x, c2.y); + + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c5.x, c5.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c4.x, c4.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c3.x, c3.y); + + // Some extra work may be needed when `thick` is positive + if (thick >= 0.0f) + { + // Fill in the gaps between cap 1 and the circle outline and cap 2 and the circle outline + if (stepsBeforeC1 > 0) + { + // Draw quads using pairs of vertices on the circle outline + angle = 0; + for (int i = 0; i < stepsBeforeC1/2; i++) + { + // Cap1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c1.x, c1.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + (angle + stepLength*2.0f)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + (angle + stepLength*2.0f)))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerRadius); + + // Cap2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c4.x, c4.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - (angle + stepLength*2.0f)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - (angle + stepLength*2.0f)))*innerRadius); + + angle += stepLength*2.0f; + } + + if (stepsBeforeC1%2 == 1) + { + // Cap1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c1.x, c1.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c1.x, c1.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerRadius); + + // Cap2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c4.x, c4.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c4.x, c4.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius); + } + } + + // Fill in the gap between C0, C3 and the circle outline + if (s1OutsideOfCircle) + { + int verticesBetweenC0andC3 = (segments - stepsBeforeC0*2) - 1; + + // No gap to fill + if (verticesBetweenC0andC3 == 0) + { + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c3.x, c3.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c0.x, c0.y); + } + // There's a gap to fill + else + { + // Triangle touching C0 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c0.x, c0.y); + + // Triangle touching C3 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c3.x, c3.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius); + + // Triangles between the previous two + verticesBetweenC0andC3 -= 1; + angle = startAngle + stepLength*(stepsBeforeC0 + 1); + for (int i = 0; i < verticesBetweenC0andC3/2; i++) + { + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength*2.0f))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength*2.0f))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + + angle += stepLength*2.0f; + } + + if (verticesBetweenC0andC3%2 == 1) + { + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + } + } + } + } + } + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + + rlColor4ub(color.r, color.g, color.b, color.a); + + // Draw the circle outline + for (int i = 0; i < segments; i++) + { + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerRadius); + + angle += stepLength; + } + + // Draw the caps + if (showCapLines) + { + // Cap 1 + rlVertex2f(center.x, center.y); + rlVertex2f(c0.x, c0.y); + rlVertex2f(c1.x, c1.y); + + rlVertex2f(center.x, center.y); + rlVertex2f(c1.x, c1.y); + rlVertex2f(c2.x, c2.y); + + // Cap 2 + rlVertex2f(center.x, center.y); + rlVertex2f(c5.x, c5.y); + rlVertex2f(c4.x, c4.y); + + rlVertex2f(center.x, center.y); + rlVertex2f(c4.x, c4.y); + rlVertex2f(c3.x, c3.y); + + // Some extra work may be needed when `thick` is positive + if (thick >= 0.0f) + { + // Fill in the gaps between cap 1 and the circle outline and cap 2 and the circle outline + if (stepsBeforeC1 > 0) + { + angle = 0; + for (int i = 0; i < stepsBeforeC1; i++) + { + // Cap 1 + rlVertex2f(c1.x, c1.y); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerRadius); + + // Cap 2 + rlVertex2f(c4.x, c4.y); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius); + + angle += stepLength; + } + } + + // Fill in the gap between C0, C3 and the circle outline + if (s1OutsideOfCircle) + { + int verticesBetweenC0andC3 = (segments - stepsBeforeC0*2) - 1; + + // No gap to fill + if (verticesBetweenC0andC3 == 0) + { + rlVertex2f(center.x, center.y); + rlVertex2f(c3.x, c3.y); + rlVertex2f(c0.x, c0.y); + } + // There's a gap to fill + else + { + // Triangle touching C0 + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius); + rlVertex2f(c0.x, c0.y); + + // Triangle touching C3 + rlVertex2f(center.x, center.y); + rlVertex2f(c3.x, c3.y); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius); + + // Triangles between the previous two + verticesBetweenC0andC3 -= 1; + angle = startAngle + stepLength*(stepsBeforeC0 + 1); + for (int i = 0; i < verticesBetweenC0andC3; i++) + { + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + + angle += stepLength; + } + } + } + } + } + + rlEnd(); +#endif +} + +// Draw circle outline +void DrawCircleLines(int centerX, int centerY, float radius, Color color) +{ + DrawCircleLinesV((Vector2){ (float)centerX, (float)centerY }, radius, color); +} + +// Draw circle outline (Vector version) +void DrawCircleLinesV(Vector2 center, float radius, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // NOTE: Circle outline is drawn pixel by pixel every degree (0 to 360) + for (int i = 0; i < 360; i += 10) + { + rlVertex2f(center.x + cosf(DEG2RAD*i)*radius, center.y + sinf(DEG2RAD*i)*radius); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*radius, center.y + sinf(DEG2RAD*(i + 10))*radius); + } + rlEnd(); +} + +void DrawCircleLinesEx(Vector2 center, float radius, float thick, Color color) +{ + DrawRing(center, radius - thick, radius, 0.0f, 360.0f, 36, color); +} + +// Draw ellipse +void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color) +{ + DrawEllipseV((Vector2){ (float)centerX, (float)centerY }, radiusH, radiusV, color); +} + +// Draw ellipse (Vector version) +void DrawEllipseV(Vector2 center, float radiusH, float radiusV, Color color) +{ + rlBegin(RL_TRIANGLES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*radiusH, center.y + sinf(DEG2RAD*(i + 10))*radiusV); + rlVertex2f(center.x + cosf(DEG2RAD*i)*radiusH, center.y + sinf(DEG2RAD*i)*radiusV); + } + rlEnd(); +} + +// Draw ellipse outline +void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color) +{ + DrawEllipseLinesV((Vector2){ (float)centerX, (float)centerY }, radiusH, radiusV, color); +} + +// Draw ellipse outline +void DrawEllipseLinesV(Vector2 center, float radiusH, float radiusV, Color color) +{ + rlBegin(RL_LINES); + for (int i = 0; i < 360; i += 10) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*radiusH, center.y + sinf(DEG2RAD*(i + 10))*radiusV); + rlVertex2f(center.x + cosf(DEG2RAD*i)*radiusH, center.y + sinf(DEG2RAD*i)*radiusV); + } + rlEnd(); +} + +// Draw ellipse outline with thickness +void DrawEllipseLinesEx(Vector2 center, float radiusH, float radiusV, float thick, Color color) +{ + float outerRadiusH = radiusH, innerRadiusH = radiusH - thick; + float outerRadiusV = radiusV, innerRadiusV = radiusV - thick; + + if (thick >= 0.0f) { + // Just a filled-in ellipse + if (innerRadiusH <= 0.0f || innerRadiusV <= 0.0f) + { + DrawEllipseV(center, radiusH, radiusV, color); + return; + } + } + else + { + // The outline is growing outside of the ellipse, so swap the inner and outer radius + float tmp = outerRadiusH; + outerRadiusH = innerRadiusH; + innerRadiusH = tmp; + + tmp = outerRadiusV; + outerRadiusV = innerRadiusV; + innerRadiusV = tmp; + } + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < 360; i += 10) + { + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*i)*innerRadiusH, center.y + sinf(DEG2RAD*i)*innerRadiusV); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*innerRadiusH, center.y + sinf(DEG2RAD*(i + 10))*innerRadiusV); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*outerRadiusH, center.y + sinf(DEG2RAD*(i + 10))*outerRadiusV); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*i)*outerRadiusH, center.y + sinf(DEG2RAD*i)*outerRadiusV); + } + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < 360; i += 10) + { + rlVertex2f(center.x + cosf(DEG2RAD*i)*innerRadiusH, center.y + sinf(DEG2RAD*i)*innerRadiusV); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*innerRadiusH, center.y + sinf(DEG2RAD*(i + 10))*innerRadiusV); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*outerRadiusH, center.y + sinf(DEG2RAD*(i + 10))*outerRadiusV); + + rlVertex2f(center.x + cosf(DEG2RAD*i)*innerRadiusH, center.y + sinf(DEG2RAD*i)*innerRadiusV); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*outerRadiusH, center.y + sinf(DEG2RAD*(i + 10))*outerRadiusV); + rlVertex2f(center.x + cosf(DEG2RAD*i)*outerRadiusH, center.y + sinf(DEG2RAD*i)*outerRadiusV); + } + rlEnd(); +#endif +} + +// Draw ring +void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color) +{ + if (startAngle == endAngle) return; + + // Function expects (outerRadius > innerRadius) + if (outerRadius < innerRadius) + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + + if (outerRadius <= 0.0f) outerRadius = 0.1f; + } + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + // Drawing a whole circle, things get weird without limiting the circle to 360 degrees + if (endAngle - startAngle >= 360.0f) endAngle = startAngle + 360.0f; + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/outerRadius, 2) - 1); + segments = (int)ceilf((endAngle - startAngle)*(2*PI/th)/360.0f); + + if (segments <= 0) segments = minSegments; + } + + // Not a ring + if (innerRadius <= 0.0f) + { + DrawCircleSector(center, outerRadius, startAngle, endAngle, segments, color); + return; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerRadius); + + angle += stepLength; + } + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + + angle += stepLength; + } + rlEnd(); +#endif +} + +// Draw ring outline +void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color) +{ + if (startAngle == endAngle) return; + + // Function expects (outerRadius > innerRadius) + if (outerRadius < innerRadius) + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + + if (outerRadius <= 0.0f) outerRadius = 0.1f; + } + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + bool showCapLines = true; + // Drawing a whole circle, things get weird without limiting the circle to 360 degrees + if (endAngle - startAngle >= 360.0f) + { + showCapLines = false; + endAngle = startAngle + 360.0f; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/outerRadius, 2) - 1); + segments = (int)ceilf((endAngle - startAngle)*(2*PI/th)/360.0f); + + if (segments <= 0) segments = minSegments; + } + + if (innerRadius <= 0.0f) + { + DrawCircleSectorLines(center, outerRadius, startAngle, endAngle, segments, color); + return; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + + rlBegin(RL_LINES); + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + } + + for (int i = 0; i < segments; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + + angle += stepLength; + } + + if (showCapLines) + { + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + } + rlEnd(); +} + +// Draw ring outline with line thickness +void DrawRingLinesEx(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, float thick, Color color) +{ + if (startAngle == endAngle) return; + + // Function expects (outerRadius > innerRadius) + if (outerRadius < innerRadius) + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + + if (outerRadius <= 0.0f) outerRadius = 0.1f; + } + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + bool showCapLines = true; + // Drawing a whole circle, things get weird without limiting the circle to 360 degrees + if (endAngle - startAngle >= 360.0f) + { + showCapLines = thick >= 0.0f; + endAngle = startAngle + 360.0f; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/outerRadius, 2) - 1); + segments = (int)ceilf((endAngle - startAngle)*(2*PI/th)/360.0f); + + if (segments <= 0) segments = minSegments; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + + // We are not drawing a circle, we are drawing an n-sided polygon + // So, we need to adjust the outline thickness of the "circle" for it to look correct with fewer segments + float apothem = outerRadius*cosf(DEG2RAD*((endAngle - startAngle)/2.0f)/(float)segments); + float radiusThick = thick*(outerRadius/apothem); + + // These names can be confusing, but they are useful + // Since 2 rings are being drawn, there are 4 radiuses (or radii) + // "Inner" means closer to the center, "outer" means farther from the center + // Sorted from farthest to closest you get: + // 1. outerOuterRadius (farthest) + // 2. innerOuterRadius + // 3. outerInnerRadius + // 4. innerInnerRadius (closest) + float innerOuterRadius = 0.0f; + float outerOuterRadius = 0.0f; + float innerInnerRadius = 0.0f; + float outerInnerRadius = 0.0f; + + if (thick >= 0.0f) + { + innerRadius = fmaxf(0.0f, innerRadius); + + // Just a filled-in ring + if (radiusThick > (outerRadius - innerRadius)/2.0f) + { + DrawRing(center, innerRadius, outerRadius, startAngle, endAngle, segments, color); + return; + } + + innerInnerRadius = innerRadius; + outerInnerRadius = innerInnerRadius + radiusThick; + + outerOuterRadius = outerRadius; + innerOuterRadius = outerOuterRadius - radiusThick; + } + else + { + // Just a circle sector outline + if (innerRadius <= 0.0f) + { + DrawCircleSectorLinesEx(center, outerRadius, startAngle, endAngle, segments, thick, color); + return; + } + + outerInnerRadius = innerRadius; + innerInnerRadius = fmaxf(0.0f, outerInnerRadius + radiusThick); + + innerOuterRadius = outerRadius; + outerOuterRadius = innerOuterRadius - radiusThick; + } + + // For positive `thick` values + int stepsBeforeInner = 0; + int stepsBeforeOuter = 0; + float tInner = 0.0f; + float tOuter = 0.0f; + bool innerAnglesCrossEachOther = false; + + // For negative `thick` values + Vector2 cap1SecondInnerVertex = { 0 }; + Vector2 cap1SecondOuterVertex = { 0 }; + Vector2 cap2SecondInnerVertex = { 0 }; + Vector2 cap2SecondOuterVertex = { 0 }; + bool capsIntersect = false; + Vector2 capIntersectionVertex = { 0 }; + + if (showCapLines) + { + if (thick >= 0.0f) + { + // Get the angle of the arc that has `thick` length along the inner and outer radii + float cap1InnerAngleEnd = RAD2DEG*(thick/outerInnerRadius); + float cap1OuterAngleEnd = RAD2DEG*(thick/innerOuterRadius); + + // Just a filled-in ring + if (endAngle - startAngle < cap1OuterAngleEnd*2.0f) + { + DrawRing(center, innerRadius, outerRadius, startAngle, endAngle, segments, color); + return; + } + + if (endAngle - startAngle < cap1InnerAngleEnd*2.0f) innerAnglesCrossEachOther = true; + + stepsBeforeInner = (int)(cap1InnerAngleEnd/stepLength); + stepsBeforeOuter = (int)(cap1OuterAngleEnd/stepLength); + + // We need to find where `cap1InnerAngleEnd` intersects the edge defined + // by `beforeInnerVertex` and `afterInnerVertex` + // + // We can make this easy by making `center` the origin (0, 0) and + // making `cap1InnerAngleEnd` 0 degrees (a horizontal line) + // + // With that, we know these lines intersect when 'y' equals 0, + // so we just need to solve for 't' (as in `Lerp(start, end, t)`) + Vector2 beforeInnerVertex = { cosf(DEG2RAD*((float)stepsBeforeInner*stepLength - cap1InnerAngleEnd))*outerInnerRadius, sinf(DEG2RAD*((float)stepsBeforeInner*stepLength - cap1InnerAngleEnd))*outerInnerRadius }; + Vector2 afterInnerVertex = { cosf(DEG2RAD*((float)(stepsBeforeInner + 1)*stepLength - cap1InnerAngleEnd))*outerInnerRadius, sinf(DEG2RAD*((float)(stepsBeforeInner + 1)*stepLength - cap1InnerAngleEnd))*outerInnerRadius }; + tInner = beforeInnerVertex.y/(beforeInnerVertex.y - afterInnerVertex.y); + + // The same as above, but for the outer edge + Vector2 beforeOuterVertex = { cosf(DEG2RAD*((float)stepsBeforeOuter*stepLength - cap1OuterAngleEnd))*innerOuterRadius, sinf(DEG2RAD*((float)stepsBeforeOuter*stepLength - cap1OuterAngleEnd))*innerOuterRadius }; + Vector2 afterOuterVertex = { cosf(DEG2RAD*((float)(stepsBeforeOuter + 1)*stepLength - cap1OuterAngleEnd))*innerOuterRadius, sinf(DEG2RAD*((float)(stepsBeforeOuter + 1)*stepLength - cap1OuterAngleEnd))*innerOuterRadius }; + tOuter = beforeOuterVertex.y/(beforeOuterVertex.y - afterOuterVertex.y); + } + else + { + // "Cap 1" is the outline on `startAngle` and "Cap 2" is the outline on `endAngle` + + /* + A sketch to help make all this a little more understandable + (This is an overly simplified representation of cap 1) + + I2[][][][][]O2 <- y = thick + [] [] + [] [] + I0----------O0 <- angle = 0 degrees, y = 0 + [] [] + I1 O1 <- angle = stepLength + + Cap 2 is a mirror copy of cap 1, the inside and outside vertices switch sides + + We're using a frame of reference where `center` is (0, 0) and `startAngle` is 0 degrees + + I0 is `innerInnerRadius` distance from `center` at `starAngle` + I1 is `innerInnerRadius` distance from `center` at `starAngle + stepLength` + I2 goes out from I0 perpendicular to `startAngle` + O0 is the same as I0, except using `outerOuterRadius` instead of `innerInnerRadius` + O1 is the same as I1, except using `outerOuterRadius` instead of `innerInnerRadius` + O2 is the same as I2, except goes out from O0 + + The intersection cases between the caps edges are: + 1. No intersections, easy + 2. The I0->I2 and I2->O2 edges intersect between the caps + 3. The I0->I2 and O0->O2 edges intersect between the caps + + Notice that cap 1 and 2's I2->O2 and O0->O2 edges can't intersect at the same time, + and, if there's any intersection, I0->I2 is one of the edges + */ + + Vector2 cap1O0 = { outerOuterRadius, 0.0f }; + Vector2 cap1O1 = { cosf(DEG2RAD*stepLength)*outerOuterRadius, sinf(DEG2RAD*stepLength)*outerOuterRadius }; + + // Assuming a linear interpolation such as `value = Lerp(start, end, t)` + // We can find O2 by getting its 't' between O1.y and O0.y (which is always greater than 1) + // We can solve for `t` using `t = (start - value)/(start - end)` + // Since we know `end = 0` we can simplify it to `t = (start - value)/start` + float tOuter = (cap1O1.y - thick)/cap1O1.y; + Vector2 cap1O2 = { cap1O1.x + (cap1O0.x - cap1O1.x)*tOuter, thick }; + + //Vector2 cap1I0 = { innerInnerRadius, 0.0f }; // Not used + + float capLongEdgeLength = outerOuterRadius - innerInnerRadius; + Vector2 cap1I2 = { cap1O2.x - capLongEdgeLength, thick }; + + Vector2 cap2O0 = { cosf(DEG2RAD*(endAngle - startAngle))*outerOuterRadius, sinf(DEG2RAD*(endAngle - startAngle))*outerOuterRadius }; + Vector2 cap2O1 = { cosf(DEG2RAD*(endAngle - startAngle - stepLength))*outerOuterRadius, sinf(DEG2RAD*(endAngle - startAngle - stepLength))*outerOuterRadius }; + Vector2 cap2O2 = { cap2O1.x + (cap2O0.x - cap2O1.x)*tOuter, cap2O1.y + (cap2O0.y - cap2O1.y)*tOuter }; + + Vector2 cap2I0 = { cosf(DEG2RAD*(endAngle - startAngle))*innerInnerRadius, sinf(DEG2RAD*(endAngle - startAngle))*innerInnerRadius }; + Vector2 cap2I2 = { cap2O2.x - cosf(DEG2RAD*(endAngle - startAngle))*capLongEdgeLength, cap2O2.y - sinf(DEG2RAD*(endAngle - startAngle))*capLongEdgeLength}; + + // The 't' of the intersection between I2 and O2 (`Lerp(I2, O2, t)`) + float tCapLongEdgeCross = -1.0f; + // Avoid division by zero + if (cap2I2.y - cap2O2.y != 0.0f) + { + // Find where the long edge of cap 2 intersects the long edge of cap 1 + tCapLongEdgeCross = (cap2I2.y - thick)/(cap2I2.y - cap2O2.y); + if ((tCapLongEdgeCross >= 0.0f) && (tCapLongEdgeCross <= 1.0f)) capsIntersect = true; + } + + // Rotate the frame of reference so that cap 1's I0->I2 edge is a vertical line + float rotateBy = -DEG2RAD*stepLength/2.0f; + + // Copied from "raymath.h" Vector2Rotate() + // Though we only use the x axis, so we ignore the y axis + float cosres = cosf(rotateBy); + float sinres = sinf(rotateBy); + + cap1I2.x = cap1I2.x*cosres - cap1I2.y*sinres; + cap1O2.x = cap1O2.x*cosres - cap1O2.y*sinres; + cap2I0.x = cap2I0.x*cosres - cap2I0.y*sinres; + cap2I2.x = cap2I2.x*cosres - cap2I2.y*sinres; + cap2O0.x = cap2O0.x*cosres - cap2O0.y*sinres; + cap2O2.x = cap2O2.x*cosres - cap2O2.y*sinres; + + // The 't' of the intersection between I0 and I2 (`Lerp(I0, I2, t)`) + float tCrossInner = -1.0f; + // Avoid division by zero + if (cap2I0.x - cap2I2.x != 0.0f) tCrossInner = (cap2I0.x - cap1I2.x)/(cap2I0.x - cap2I2.x); + // Make sure `tCrossInner` is 0 when it should be (mitigate floating-point rounding woes) + if (innerInnerRadius <= 0.0f) tCrossInner = 0.0f; + + // The 't' of the intersection between O0 and O2 (`Lerp(O0, O2, t)`) + float tCrossOuter = -1.0f; + // Avoid division by zero + if (cap2O0.x - cap2O2.x != 0.0f) tCrossOuter = (cap2O0.x - cap1O2.x)/(cap2O0.x - cap2O2.x); + + // With our additional information, calculate the vertices we need + // outside of our modified frame of reference + + cap1O0 = (Vector2){ center.x + cosf(DEG2RAD*startAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*startAngle)*outerOuterRadius }; + cap1O1 = (Vector2){ center.x + cosf(DEG2RAD*(startAngle + stepLength))*outerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + stepLength))*outerOuterRadius }; + cap1O2 = (Vector2){ cap1O1.x + (cap1O0.x - cap1O1.x)*tOuter, cap1O1.y + (cap1O0.y - cap1O1.y)*tOuter }; + + cap2O0 = (Vector2){ center.x + cosf(DEG2RAD*endAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*endAngle)*outerOuterRadius }; + cap2O1 = (Vector2){ center.x + cosf(DEG2RAD*(endAngle - stepLength))*outerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - stepLength))*outerOuterRadius }; + cap2O2 = (Vector2){ cap2O1.x + (cap2O0.x - cap2O1.x)*tOuter, cap2O1.y + (cap2O0.y - cap2O1.y)*tOuter }; + + //cap1I0 = (Vector2){ center.x + cosf(DEG2RAD*startAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*startAngle)*innerInnerRadius }; + cap1I2 = (Vector2){ cap1O2.x - cosf(DEG2RAD*startAngle)*capLongEdgeLength, cap1O2.y - sinf(DEG2RAD*startAngle)*capLongEdgeLength }; + + cap2I0 = (Vector2){ center.x + cosf(DEG2RAD*endAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*endAngle)*innerInnerRadius }; + cap2I2 = (Vector2){ cap2O2.x - cosf(DEG2RAD*endAngle)*capLongEdgeLength, cap2O2.y - sinf(DEG2RAD*endAngle)*capLongEdgeLength }; + + if (capsIntersect) + { + capIntersectionVertex = (Vector2){ cap2I2.x + (cap2O2.x - cap2I2.x)*tCapLongEdgeCross, cap2I2.y + (cap2O2.y - cap2I2.y)*tCapLongEdgeCross }; + + cap2I2 = (Vector2){ cap2I0.x + (cap2I2.x - cap2I0.x)*tCrossInner, cap2I0.y + (cap2I2.y - cap2I0.y)*tCrossInner }; + cap1I2 = cap2I2; + } + else if ((tCrossOuter >= 0.0f) && (tCrossOuter <= 1.0f)) + { + cap2O2 = (Vector2){ cap2O0.x + (cap2O2.x - cap2O0.x)*tCrossOuter, cap2O0.y + (cap2O2.y - cap2O0.y)*tCrossOuter }; + cap1O2 = cap2O2; + + cap2I2 = (Vector2){ cap2I0.x + (cap2I2.x - cap2I0.x)*tCrossInner, cap2I0.y + (cap2I2.y - cap2I0.y)*tCrossInner }; + cap1I2 = cap2I2; + } + + cap1SecondInnerVertex = cap1I2; + cap1SecondOuterVertex = cap1O2; + cap2SecondInnerVertex = cap2I2; + cap2SecondOuterVertex = cap2O2; + } + } + + float angle = startAngle; + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < segments; i++) + { + // `innerRadius` outline + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerInnerRadius, center.y + sinf(DEG2RAD*angle)*outerInnerRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerInnerRadius, center.y + sinf(DEG2RAD*angle)*innerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerInnerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerInnerRadius); + + // `outerRadius` outline + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerOuterRadius, center.y + sinf(DEG2RAD*angle)*outerOuterRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerOuterRadius, center.y + sinf(DEG2RAD*angle)*innerOuterRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerOuterRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerOuterRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerOuterRadius); + + angle += stepLength; + } + + if (showCapLines) + { + if (thick >= 0.0f) + { + angle = 0.0f; + + for (int i = 0; i < stepsBeforeOuter; i++) + { + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerOuterRadius); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerOuterRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius); + + angle += stepLength; + } + + // We've already moved `stepsBeforeOuter` steps from each end + int totalStepsLeft = segments - stepsBeforeOuter*2; + int innerStepsLeft = stepsBeforeInner - stepsBeforeOuter; + + // Cap 1 + Vector2 cap1OuterVertexBeforeEnd = { center.x + cosf(DEG2RAD*(startAngle + angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerOuterRadius }; + Vector2 cap1OuterVertexAfterEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius }; + Vector2 cap1InnerVertexBeforeEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + (float)innerStepsLeft*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + (float)innerStepsLeft*stepLength))*outerInnerRadius }; + Vector2 cap1InnerVertexAfterEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius }; + Vector2 cap1InnerVertexEnd = { cap1InnerVertexBeforeEnd.x + (cap1InnerVertexAfterEnd.x - cap1InnerVertexBeforeEnd.x)*tInner, cap1InnerVertexBeforeEnd.y + (cap1InnerVertexAfterEnd.y - cap1InnerVertexBeforeEnd.y)*tInner }; + Vector2 cap1OuterVertexEnd = { cap1OuterVertexBeforeEnd.x + (cap1OuterVertexAfterEnd.x - cap1OuterVertexBeforeEnd.x)*tOuter, cap1OuterVertexBeforeEnd.y + (cap1OuterVertexAfterEnd.y - cap1OuterVertexBeforeEnd.y)*tOuter }; + + // Cap 2 + Vector2 cap2OuterVertexBeforeEnd = { center.x + cosf(DEG2RAD*(endAngle - angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerOuterRadius }; + Vector2 cap2OuterVertexAfterEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius }; + Vector2 cap2InnerVertexBeforeEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - (float)innerStepsLeft*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - (float)innerStepsLeft*stepLength))*outerInnerRadius }; + Vector2 cap2InnerVertexAfterEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius }; + Vector2 cap2InnerVertexEnd = { cap2InnerVertexBeforeEnd.x + (cap2InnerVertexAfterEnd.x - cap2InnerVertexBeforeEnd.x)*tInner, cap2InnerVertexBeforeEnd.y + (cap2InnerVertexAfterEnd.y - cap2InnerVertexBeforeEnd.y)*tInner }; + Vector2 cap2OuterVertexEnd = { cap2OuterVertexBeforeEnd.x + (cap2OuterVertexAfterEnd.x - cap2OuterVertexBeforeEnd.x)*tOuter, cap2OuterVertexBeforeEnd.y + (cap2OuterVertexAfterEnd.y - cap2OuterVertexBeforeEnd.y)*tOuter }; + + int stepsCount = (innerAnglesCrossEachOther)? totalStepsLeft/2 : innerStepsLeft; + + // Iterate over pairs of steps + for (int i = 0; i < stepsCount/2; i++) + { + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength*2.0f))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength*2.0f))*outerInnerRadius); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength*2.0f))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength*2.0f))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + + angle += stepLength*2.0f; + } + + // Handle the last step if there's an odd amount + if (stepsCount%2 == 1) + { + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + + angle += stepLength; + } + + // When the inner angles coming from `startAngle` and `endAngle` cross each other, + // the `*innerVertexEnd` vertices go past each other and cause the geometry to intersect itself + if (innerAnglesCrossEachOther) + { + // We need to find where the line defined by `cap1InnerVertexEnd` and `cap1OuterVertexEnd` intersects + // the line defined by `cap2InnerVertexEnd` and `cap2OuterVertexEnd` + // That point is then used instead to prevent the outline from intersecting itself + + // Make `cap1InnerVertexEnd` the origin and the angle to `cap1OuterVertexEnd` 0 degrees + Vector2 tempCap1OuterVertexEnd = { cap1OuterVertexEnd.x - cap1InnerVertexEnd.x, cap1OuterVertexEnd.y - cap1InnerVertexEnd.y }; + Vector2 tempCap2InnerVertexEnd = { cap2InnerVertexEnd.x - cap1InnerVertexEnd.x, cap2InnerVertexEnd.y - cap1InnerVertexEnd.y }; + Vector2 tempCap2OuterVertexEnd = { cap2OuterVertexEnd.x - cap1InnerVertexEnd.x, cap2OuterVertexEnd.y - cap1InnerVertexEnd.y }; + + float rotateBy = -atan2f(tempCap1OuterVertexEnd.y, tempCap1OuterVertexEnd.x); + // We only need the y coordinates, so only rotate the y coordinates + float start = sinf(rotateBy)*tempCap2InnerVertexEnd.x + cosf(rotateBy)*tempCap2InnerVertexEnd.y; + float end = sinf(rotateBy)*tempCap2OuterVertexEnd.x + cosf(rotateBy)*tempCap2OuterVertexEnd.y; + float tCross = start/(start - end); + + Vector2 intersection = { cap2InnerVertexEnd.x + (cap2OuterVertexEnd.x - cap2InnerVertexEnd.x)*tCross, cap2InnerVertexEnd.y + (cap2OuterVertexEnd.y - cap2InnerVertexEnd.y)*tCross }; + + if (segments%2 == 0) + { + // There are an even number of segments, so there's 1 vertex exactly in the middle + + Vector2 middleInnerVertex = { center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius }; + + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(middleInnerVertex.x, middleInnerVertex.y); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(middleInnerVertex.x, middleInnerVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + } + else + { + // There are an odd number of segments, so there are 2 vertices in the middle + + Vector2 middleInnerVertex1 = { center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius }; + Vector2 middleInnerVertex2 = { center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius }; + + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(middleInnerVertex1.x, middleInnerVertex1.y); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(middleInnerVertex2.x, middleInnerVertex2.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + + // Triangle between the caps + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(middleInnerVertex1.x, middleInnerVertex1.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(middleInnerVertex2.x, middleInnerVertex2.y); + } + } + else + { + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1InnerVertexBeforeEnd.x, cap1InnerVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1InnerVertexEnd.x, cap1InnerVertexEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2InnerVertexEnd.x, cap2InnerVertexEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2InnerVertexBeforeEnd.x, cap2InnerVertexBeforeEnd.y); + } + } + else + { + // Cap 1 + Vector2 cap1FirstInnerVertex = { center.x + cosf(DEG2RAD*startAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*startAngle)*innerInnerRadius }; + Vector2 cap1FirstOuterVertex = { center.x + cosf(DEG2RAD*startAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*startAngle)*outerOuterRadius }; + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1FirstInnerVertex.x, cap1FirstInnerVertex.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1FirstOuterVertex.x, cap1FirstOuterVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1SecondOuterVertex.x, cap1SecondOuterVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1SecondInnerVertex.x, cap1SecondInnerVertex.y); + + // Cap 2 + Vector2 cap2FirstInnerVertex = { center.x + cosf(DEG2RAD*endAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*endAngle)*innerInnerRadius }; + Vector2 cap2FirstOuterVertex = { center.x + cosf(DEG2RAD*endAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*endAngle)*outerOuterRadius }; + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2FirstInnerVertex.x, cap2FirstInnerVertex.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2SecondInnerVertex.x, cap2SecondInnerVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2SecondOuterVertex.x, cap2SecondOuterVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2FirstOuterVertex.x, cap2FirstOuterVertex.y); + + if (capsIntersect) + { + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1SecondInnerVertex.x, cap1SecondInnerVertex.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1SecondInnerVertex.x, cap1SecondInnerVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1SecondOuterVertex.x, cap1SecondOuterVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(capIntersectionVertex.x, capIntersectionVertex.y); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2SecondInnerVertex.x, cap2SecondInnerVertex.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(capIntersectionVertex.x, capIntersectionVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2SecondOuterVertex.x, cap2SecondOuterVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2SecondInnerVertex.x, cap2SecondInnerVertex.y); + } + } + } + rlEnd(); +#else + rlBegin(RL_TRIANGLES); + + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < segments; i++) + { + // `innerRadius` outline + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerInnerRadius, center.y + sinf(DEG2RAD*angle)*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerInnerRadius, center.y + sinf(DEG2RAD*angle)*innerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerInnerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerInnerRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerInnerRadius, center.y + sinf(DEG2RAD*angle)*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerInnerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerInnerRadius); + + // `outerRadius` outline + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerOuterRadius, center.y + sinf(DEG2RAD*angle)*outerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerOuterRadius, center.y + sinf(DEG2RAD*angle)*innerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerOuterRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerOuterRadius, center.y + sinf(DEG2RAD*angle)*outerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerOuterRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerOuterRadius); + + angle += stepLength; + } + + if (showCapLines) + { + if (thick >= 0.0f) + { + angle = 0.0f; + + for (int i = 0; i < stepsBeforeOuter; i++) + { + // Cap 1 + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerOuterRadius); + + // Cap 2 + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius); + + angle += stepLength; + } + + // We've already moved `stepsBeforeOuter` steps from each end + int totalStepsLeft = segments - stepsBeforeOuter*2; + int innerStepsLeft = stepsBeforeInner - stepsBeforeOuter; + + // Cap 1 + Vector2 cap1OuterVertexBeforeEnd = { center.x + cosf(DEG2RAD*(startAngle + angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerOuterRadius }; + Vector2 cap1OuterVertexAfterEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius }; + Vector2 cap1InnerVertexBeforeEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + (float)innerStepsLeft*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + (float)innerStepsLeft*stepLength))*outerInnerRadius }; + Vector2 cap1InnerVertexAfterEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius }; + Vector2 cap1InnerVertexEnd = { cap1InnerVertexBeforeEnd.x + (cap1InnerVertexAfterEnd.x - cap1InnerVertexBeforeEnd.x)*tInner, cap1InnerVertexBeforeEnd.y + (cap1InnerVertexAfterEnd.y - cap1InnerVertexBeforeEnd.y)*tInner }; + Vector2 cap1OuterVertexEnd = { cap1OuterVertexBeforeEnd.x + (cap1OuterVertexAfterEnd.x - cap1OuterVertexBeforeEnd.x)*tOuter, cap1OuterVertexBeforeEnd.y + (cap1OuterVertexAfterEnd.y - cap1OuterVertexBeforeEnd.y)*tOuter }; + + // Cap 2 + Vector2 cap2OuterVertexBeforeEnd = { center.x + cosf(DEG2RAD*(endAngle - angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerOuterRadius }; + Vector2 cap2OuterVertexAfterEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius }; + Vector2 cap2InnerVertexBeforeEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - (float)innerStepsLeft*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - (float)innerStepsLeft*stepLength))*outerInnerRadius }; + Vector2 cap2InnerVertexAfterEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius }; + Vector2 cap2InnerVertexEnd = { cap2InnerVertexBeforeEnd.x + (cap2InnerVertexAfterEnd.x - cap2InnerVertexBeforeEnd.x)*tInner, cap2InnerVertexBeforeEnd.y + (cap2InnerVertexAfterEnd.y - cap2InnerVertexBeforeEnd.y)*tInner }; + Vector2 cap2OuterVertexEnd = { cap2OuterVertexBeforeEnd.x + (cap2OuterVertexAfterEnd.x - cap2OuterVertexBeforeEnd.x)*tOuter, cap2OuterVertexBeforeEnd.y + (cap2OuterVertexAfterEnd.y - cap2OuterVertexBeforeEnd.y)*tOuter }; + + int stepsCount = (innerAnglesCrossEachOther)? totalStepsLeft/2 : innerStepsLeft; + + for (int i = 0; i < stepsCount; i++) + { + // Cap 1 + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius); + + // Cap 2 + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + + angle += stepLength; + } + + // When the inner angles coming from `startAngle` and `endAngle` cross each other, + // the `*innerVertexEnd` vertices go past each other and cause the geometry to intersect itself + if (innerAnglesCrossEachOther) + { + // We need to find where the line defined by `cap1InnerVertexEnd` and `cap1OuterVertexEnd` intersects + // the line defined by `cap2InnerVertexEnd` and `cap2OuterVertexEnd` + // That point is then used instead to prevent the outline from intersecting itself + + // Make `cap1InnerVertexEnd` the origin and the angle to `cap1OuterVertexEnd` 0 degrees + Vector2 tempCap1OuterVertexEnd = { cap1OuterVertexEnd.x - cap1InnerVertexEnd.x, cap1OuterVertexEnd.y - cap1InnerVertexEnd.y }; + Vector2 tempCap2InnerVertexEnd = { cap2InnerVertexEnd.x - cap1InnerVertexEnd.x, cap2InnerVertexEnd.y - cap1InnerVertexEnd.y }; + Vector2 tempCap2OuterVertexEnd = { cap2OuterVertexEnd.x - cap1InnerVertexEnd.x, cap2OuterVertexEnd.y - cap1InnerVertexEnd.y }; + + float rotateBy = -atan2f(tempCap1OuterVertexEnd.y, tempCap1OuterVertexEnd.x); + // We only need the y coordinates, so only rotate the y coordinates + float start = sinf(rotateBy)*tempCap2InnerVertexEnd.x + cosf(rotateBy)*tempCap2InnerVertexEnd.y; + float end = sinf(rotateBy)*tempCap2OuterVertexEnd.x + cosf(rotateBy)*tempCap2OuterVertexEnd.y; + float tCross = start/(start - end); + + Vector2 intersection = { cap2InnerVertexEnd.x + (cap2OuterVertexEnd.x - cap2InnerVertexEnd.x)*tCross, cap2InnerVertexEnd.y + (cap2OuterVertexEnd.y - cap2InnerVertexEnd.y)*tCross }; + + if (segments%2 == 0) + { + // There are an even number of segments, so there's 1 vertex exactly in the middle + + Vector2 middleInnerVertex = { center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius }; + + // Cap 1 + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + rlVertex2f(middleInnerVertex.x, middleInnerVertex.y); + + // Cap 2 + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(middleInnerVertex.x, middleInnerVertex.y); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + } + else + { + // There are an odd number of segments, so there are 2 vertices in the middle + + Vector2 middleInnerVertex1 = { center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius }; + Vector2 middleInnerVertex2 = { center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius }; + + // Cap 1 + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + rlVertex2f(middleInnerVertex1.x, middleInnerVertex1.y); + + // Cap 2 + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(middleInnerVertex2.x, middleInnerVertex2.y); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + + // Triangle between the caps + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(middleInnerVertex1.x, middleInnerVertex1.y); + + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(middleInnerVertex1.x, middleInnerVertex1.y); + rlVertex2f(middleInnerVertex2.x, middleInnerVertex2.y); + } + } + else + { + // Cap 1 + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + rlVertex2f(cap1InnerVertexBeforeEnd.x, cap1InnerVertexBeforeEnd.y); + rlVertex2f(cap1InnerVertexEnd.x, cap1InnerVertexEnd.y); + + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + rlVertex2f(cap1InnerVertexEnd.x, cap1InnerVertexEnd.y); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + + // Cap 2 + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + rlVertex2f(cap2InnerVertexEnd.x, cap2InnerVertexEnd.y); + + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + rlVertex2f(cap2InnerVertexEnd.x, cap2InnerVertexEnd.y); + rlVertex2f(cap2InnerVertexBeforeEnd.x, cap2InnerVertexBeforeEnd.y); + } + } + else + { + // Cap 1 + Vector2 cap1FirstInnerVertex = { center.x + cosf(DEG2RAD*startAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*startAngle)*innerInnerRadius }; + Vector2 cap1FirstOuterVertex = { center.x + cosf(DEG2RAD*startAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*startAngle)*outerOuterRadius }; + + rlVertex2f(cap1FirstInnerVertex.x, cap1FirstInnerVertex.y); + rlVertex2f(cap1FirstOuterVertex.x, cap1FirstOuterVertex.y); + rlVertex2f(cap1SecondOuterVertex.x, cap1SecondOuterVertex.y); + + rlVertex2f(cap1FirstInnerVertex.x, cap1FirstInnerVertex.y); + rlVertex2f(cap1SecondOuterVertex.x, cap1SecondOuterVertex.y); + rlVertex2f(cap1SecondInnerVertex.x, cap1SecondInnerVertex.y); + + // Cap 2 + Vector2 cap2FirstInnerVertex = { center.x + cosf(DEG2RAD*endAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*endAngle)*innerInnerRadius }; + Vector2 cap2FirstOuterVertex = { center.x + cosf(DEG2RAD*endAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*endAngle)*outerOuterRadius }; + + rlVertex2f(cap2FirstInnerVertex.x, cap2FirstInnerVertex.y); + rlVertex2f(cap2SecondInnerVertex.x, cap2SecondInnerVertex.y); + rlVertex2f(cap2SecondOuterVertex.x, cap2SecondOuterVertex.y); + + rlVertex2f(cap2FirstInnerVertex.x, cap2FirstInnerVertex.y); + rlVertex2f(cap2SecondOuterVertex.x, cap2SecondOuterVertex.y); + rlVertex2f(cap2FirstOuterVertex.x, cap2FirstOuterVertex.y); + + if (capsIntersect) + { + // Cap 1 + rlVertex2f(cap1SecondInnerVertex.x, cap1SecondInnerVertex.y); + rlVertex2f(cap1SecondOuterVertex.x, cap1SecondOuterVertex.y); + rlVertex2f(capIntersectionVertex.x, capIntersectionVertex.y); + + // Cap 2 + rlVertex2f(cap2SecondInnerVertex.x, cap2SecondInnerVertex.y); + rlVertex2f(capIntersectionVertex.x, capIntersectionVertex.y); + rlVertex2f(cap2SecondOuterVertex.x, cap2SecondOuterVertex.y); + } + } + } + + rlEnd(); +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Splines functions +//---------------------------------------------------------------------------------- + +// Draw spline: linear, minimum 2 points +void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color) +{ + if (pointCount < 2) return; + +#if SUPPORT_SPLINE_MITERS + Vector2 prevNormal = (Vector2){-(points[1].y - points[0].y), (points[1].x - points[0].x)}; + float prevLength = sqrtf(prevNormal.x*prevNormal.x + prevNormal.y*prevNormal.y); + + if (prevLength > 0.0f) + { + prevNormal.x /= prevLength; + prevNormal.y /= prevLength; + } + else + { + prevNormal.x = 0.0f; + prevNormal.y = 0.0f; + } + + Vector2 prevRadius = { 0.5f*thick*prevNormal.x, 0.5f*thick*prevNormal.y }; + + for (int i = 0; i < pointCount - 1; i++) + { + Vector2 normal = { 0 }; + + if (i < pointCount - 2) + { + normal = (Vector2){-(points[i + 2].y - points[i + 1].y), (points[i + 2].x - points[i + 1].x)}; + float normalLength = sqrtf(normal.x*normal.x + normal.y*normal.y); + + if (normalLength > 0.0f) + { + normal.x /= normalLength; + normal.y /= normalLength; + } + else + { + normal.x = 0.0f; + normal.y = 0.0f; + } + } + else + { + normal = prevNormal; + } + + Vector2 radius = { prevNormal.x + normal.x, prevNormal.y + normal.y }; + float radiusLength = sqrtf(radius.x*radius.x + radius.y*radius.y); + + if (radiusLength > 0.0f) + { + radius.x /= radiusLength; + radius.y /= radiusLength; + } + else + { + radius.x = 0.0f; + radius.y = 0.0f; + } + + float cosTheta = radius.x*normal.x + radius.y*normal.y; + + if (cosTheta != 0.0f) + { + radius.x *= (thick*0.5f/cosTheta); + radius.y *= (thick*0.5f/cosTheta); + } + else + { + radius.x = 0.0f; + radius.y = 0.0f; + } + + Vector2 strip[4] = { + { points[i].x - prevRadius.x, points[i].y - prevRadius.y }, + { points[i].x + prevRadius.x, points[i].y + prevRadius.y }, + { points[i + 1].x - radius.x, points[i + 1].y - radius.y }, + { points[i + 1].x + radius.x, points[i + 1].y + radius.y } + }; + + DrawTriangleStrip(strip, 4, color); + + prevRadius = radius; + prevNormal = normal; + } + +#else // !SUPPORT_SPLINE_MITERS + + Vector2 delta = { 0 }; + float length = 0.0f; + float scale = 0.0f; + + for (int i = 0; i < pointCount - 1; i++) + { + delta = (Vector2){ points[i + 1].x - points[i].x, points[i + 1].y - points[i].y }; + length = sqrtf(delta.x*delta.x + delta.y*delta.y); + + if (length > 0) scale = thick/(2*length); + + Vector2 radius = { -scale*delta.y, scale*delta.x }; + Vector2 strip[4] = { + { points[i].x - radius.x, points[i].y - radius.y }, + { points[i].x + radius.x, points[i].y + radius.y }, + { points[i + 1].x - radius.x, points[i + 1].y - radius.y }, + { points[i + 1].x + radius.x, points[i + 1].y + radius.y } + }; + + DrawTriangleStrip(strip, 4, color); + } +#endif + +#if SUPPORT_SPLINE_SEGMENT_CAPS + // TODO: Add spline segment rounded caps at the begin/end of the spline? +#endif +} + +// Draw spline: B-Spline, minimum 4 points +void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color) +{ + if (pointCount < 4) return; + + float a[4] = { 0 }; + float b[4] = { 0 }; + float dy = 0.0f; + float dx = 0.0f; + float size = 0.0f; + + Vector2 currentPoint = { 0 }; + Vector2 nextPoint = { 0 }; + Vector2 vertices[2*SPLINE_SEGMENT_DIVISIONS + 2] = { 0 }; + + for (int i = 0; i < (pointCount - 3); i++) + { + float t = 0.0f; + Vector2 p1 = points[i], p2 = points[i + 1], p3 = points[i + 2], p4 = points[i + 3]; + + a[0] = (-p1.x + 3.0f*p2.x - 3.0f*p3.x + p4.x)/6.0f; + a[1] = (3.0f*p1.x - 6.0f*p2.x + 3.0f*p3.x)/6.0f; + a[2] = (-3.0f*p1.x + 3.0f*p3.x)/6.0f; + a[3] = (p1.x + 4.0f*p2.x + p3.x)/6.0f; + + b[0] = (-p1.y + 3.0f*p2.y - 3.0f*p3.y + p4.y)/6.0f; + b[1] = (3.0f*p1.y - 6.0f*p2.y + 3.0f*p3.y)/6.0f; + b[2] = (-3.0f*p1.y + 3.0f*p3.y)/6.0f; + b[3] = (p1.y + 4.0f*p2.y + p3.y)/6.0f; + + currentPoint.x = a[3]; + currentPoint.y = b[3]; + + if (i == 0) DrawCircleV(currentPoint, thick/2.0f, color); // Draw init line circle-cap + + if (i > 0) + { + vertices[0].x = currentPoint.x + dy*size; + vertices[0].y = currentPoint.y - dx*size; + vertices[1].x = currentPoint.x - dy*size; + vertices[1].y = currentPoint.y + dx*size; + } + + for (int j = 1; j <= SPLINE_SEGMENT_DIVISIONS; j++) + { + t = ((float)j)/((float)SPLINE_SEGMENT_DIVISIONS); + + nextPoint.x = a[3] + t*(a[2] + t*(a[1] + t*a[0])); + nextPoint.y = b[3] + t*(b[2] + t*(b[1] + t*b[0])); + + dy = nextPoint.y - currentPoint.y; + dx = nextPoint.x - currentPoint.x; + size = 0.5f*thick/sqrtf(dx*dx+dy*dy); + + if ((i == 0) && (j == 1)) + { + vertices[0].x = currentPoint.x + dy*size; + vertices[0].y = currentPoint.y - dx*size; + vertices[1].x = currentPoint.x - dy*size; + vertices[1].y = currentPoint.y + dx*size; + } + + vertices[2*j + 1].x = nextPoint.x - dy*size; + vertices[2*j + 1].y = nextPoint.y + dx*size; + vertices[2*j].x = nextPoint.x + dy*size; + vertices[2*j].y = nextPoint.y - dx*size; + + currentPoint = nextPoint; + } + + DrawTriangleStrip(vertices, 2*SPLINE_SEGMENT_DIVISIONS + 2, color); + } + + // Cap circle drawing at the end of every segment + DrawCircleV(currentPoint, thick/2.0f, color); +} + +// Draw spline: Catmull-Rom, minimum 4 points +void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color) +{ + if (pointCount < 4) return; + + float dy = 0.0f; + float dx = 0.0f; + float size = 0.0f; + + Vector2 currentPoint = points[1]; + Vector2 nextPoint = { 0 }; + Vector2 vertices[2*SPLINE_SEGMENT_DIVISIONS + 2] = { 0 }; + + DrawCircleV(currentPoint, thick/2.0f, color); // Draw init line circle-cap + + for (int i = 0; i < (pointCount - 3); i++) + { + float t = 0.0f; + Vector2 p1 = points[i], p2 = points[i + 1], p3 = points[i + 2], p4 = points[i + 3]; + + if (i > 0) + { + vertices[0].x = currentPoint.x + dy*size; + vertices[0].y = currentPoint.y - dx*size; + vertices[1].x = currentPoint.x - dy*size; + vertices[1].y = currentPoint.y + dx*size; + } + + for (int j = 1; j <= SPLINE_SEGMENT_DIVISIONS; j++) + { + t = ((float)j)/((float)SPLINE_SEGMENT_DIVISIONS); + + float q0 = (-1.0f*t*t*t) + (2.0f*t*t) + (-1.0f*t); + float q1 = (3.0f*t*t*t) + (-5.0f*t*t) + 2.0f; + float q2 = (-3.0f*t*t*t) + (4.0f*t*t) + t; + float q3 = t*t*t - t*t; + + nextPoint.x = 0.5f*((p1.x*q0) + (p2.x*q1) + (p3.x*q2) + (p4.x*q3)); + nextPoint.y = 0.5f*((p1.y*q0) + (p2.y*q1) + (p3.y*q2) + (p4.y*q3)); + + dy = nextPoint.y - currentPoint.y; + dx = nextPoint.x - currentPoint.x; + size = (0.5f*thick)/sqrtf(dx*dx + dy*dy); + + if ((i == 0) && (j == 1)) + { + vertices[0].x = currentPoint.x + dy*size; + vertices[0].y = currentPoint.y - dx*size; + vertices[1].x = currentPoint.x - dy*size; + vertices[1].y = currentPoint.y + dx*size; + } + + vertices[2*j + 1].x = nextPoint.x - dy*size; + vertices[2*j + 1].y = nextPoint.y + dx*size; + vertices[2*j].x = nextPoint.x + dy*size; + vertices[2*j].y = nextPoint.y - dx*size; + + currentPoint = nextPoint; + } + + DrawTriangleStrip(vertices, 2*SPLINE_SEGMENT_DIVISIONS + 2, color); + } + + // Cap circle drawing at the end of every segment + DrawCircleV(currentPoint, thick/2.0f, color); +} + +// Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] +void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color) +{ + if (pointCount >= 3) + { + for (int i = 0; i < pointCount - 2; i += 2) DrawSplineSegmentBezierQuadratic(points[i], points[i + 1], points[i + 2], thick, color); + + // Cap circle drawing at the end of every segment + //for (int i = 2; i < pointCount - 2; i += 2) DrawCircleV(points[i], thick/2.0f, color); + } +} + +// Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] +void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color) +{ + if (pointCount >= 4) + { + for (int i = 0; i < pointCount - 3; i += 3) DrawSplineSegmentBezierCubic(points[i], points[i + 1], points[i + 2], points[i + 3], thick, color); + + // Cap circle drawing at the end of every segment + //for (int i = 3; i < pointCount - 3; i += 3) DrawCircleV(points[i], thick/2.0f, color); + } +} + +// Draw spline segment: Linear, 2 points +void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color) +{ + // NOTE: For the linear spline no subdivisions are used, only a single quad + + Vector2 delta = { p2.x - p1.x, p2.y - p1.y }; + float length = sqrtf(delta.x*delta.x + delta.y*delta.y); + + if ((length > 0) && (thick > 0)) + { + float scale = thick/(2*length); + + Vector2 radius = { -scale*delta.y, scale*delta.x }; + Vector2 strip[4] = { + { p1.x - radius.x, p1.y - radius.y }, + { p1.x + radius.x, p1.y + radius.y }, + { p2.x - radius.x, p2.y - radius.y }, + { p2.x + radius.x, p2.y + radius.y } + }; + + DrawTriangleStrip(strip, 4, color); + } +} + +// Draw spline segment: B-Spline, 4 points +void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color) +{ + const float step = 1.0f/SPLINE_SEGMENT_DIVISIONS; + + Vector2 currentPoint = { 0 }; + Vector2 nextPoint = { 0 }; + float t = 0.0f; + + Vector2 points[2*SPLINE_SEGMENT_DIVISIONS + 2] = { 0 }; + + float a[4] = { 0 }; + float b[4] = { 0 }; + + a[0] = (-p1.x + 3*p2.x - 3*p3.x + p4.x)/6.0f; + a[1] = (3*p1.x - 6*p2.x + 3*p3.x)/6.0f; + a[2] = (-3*p1.x + 3*p3.x)/6.0f; + a[3] = (p1.x + 4*p2.x + p3.x)/6.0f; + + b[0] = (-p1.y + 3*p2.y - 3*p3.y + p4.y)/6.0f; + b[1] = (3*p1.y - 6*p2.y + 3*p3.y)/6.0f; + b[2] = (-3*p1.y + 3*p3.y)/6.0f; + b[3] = (p1.y + 4*p2.y + p3.y)/6.0f; + + currentPoint.x = a[3]; + currentPoint.y = b[3]; + + for (int i = 0; i <= SPLINE_SEGMENT_DIVISIONS; i++) + { + t = step*(float)i; + + nextPoint.x = a[3] + t*(a[2] + t*(a[1] + t*a[0])); + nextPoint.y = b[3] + t*(b[2] + t*(b[1] + t*b[0])); + + float dy = nextPoint.y - currentPoint.y; + float dx = nextPoint.x - currentPoint.x; + float size = (0.5f*thick)/sqrtf(dx*dx + dy*dy); + + if (i == 1) + { + points[0].x = currentPoint.x + dy*size; + points[0].y = currentPoint.y - dx*size; + points[1].x = currentPoint.x - dy*size; + points[1].y = currentPoint.y + dx*size; + } + + points[2*i + 1].x = nextPoint.x - dy*size; + points[2*i + 1].y = nextPoint.y + dx*size; + points[2*i].x = nextPoint.x + dy*size; + points[2*i].y = nextPoint.y - dx*size; + + currentPoint = nextPoint; + } + + DrawTriangleStrip(points, 2*SPLINE_SEGMENT_DIVISIONS+2, color); +} + +// Draw spline segment: Catmull-Rom, 4 points +void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color) +{ + const float step = 1.0f/SPLINE_SEGMENT_DIVISIONS; + + Vector2 currentPoint = p1; + Vector2 nextPoint = { 0 }; + float t = 0.0f; + + Vector2 points[2*SPLINE_SEGMENT_DIVISIONS + 2] = { 0 }; + + for (int i = 0; i <= SPLINE_SEGMENT_DIVISIONS; i++) + { + t = step*(float)i; + + float q0 = (-1*t*t*t) + (2*t*t) + (-1*t); + float q1 = (3*t*t*t) + (-5*t*t) + 2; + float q2 = (-3*t*t*t) + (4*t*t) + t; + float q3 = t*t*t - t*t; + + nextPoint.x = 0.5f*((p1.x*q0) + (p2.x*q1) + (p3.x*q2) + (p4.x*q3)); + nextPoint.y = 0.5f*((p1.y*q0) + (p2.y*q1) + (p3.y*q2) + (p4.y*q3)); + + float dy = nextPoint.y - currentPoint.y; + float dx = nextPoint.x - currentPoint.x; + float size = (0.5f*thick)/sqrtf(dx*dx + dy*dy); + + if (i == 1) + { + points[0].x = currentPoint.x + dy*size; + points[0].y = currentPoint.y - dx*size; + points[1].x = currentPoint.x - dy*size; + points[1].y = currentPoint.y + dx*size; + } + + points[2*i + 1].x = nextPoint.x - dy*size; + points[2*i + 1].y = nextPoint.y + dx*size; + points[2*i].x = nextPoint.x + dy*size; + points[2*i].y = nextPoint.y - dx*size; + + currentPoint = nextPoint; + } + + DrawTriangleStrip(points, 2*SPLINE_SEGMENT_DIVISIONS + 2, color); +} + +// Draw spline segment: Quadratic Bezier, 2 points, 1 control point +void DrawSplineSegmentBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float thick, Color color) +{ + const float step = 1.0f/SPLINE_SEGMENT_DIVISIONS; + + Vector2 previous = p1; + Vector2 current = { 0 }; + float t = 0.0f; + + Vector2 points[2*SPLINE_SEGMENT_DIVISIONS + 2] = { 0 }; + + for (int i = 1; i <= SPLINE_SEGMENT_DIVISIONS; i++) + { + t = step*(float)i; + + float a = powf(1.0f - t, 2); + float b = 2.0f*(1.0f - t)*t; + float c = powf(t, 2); + + // NOTE: The easing functions aren't suitable here because they don't take a control point + current.y = a*p1.y + b*c2.y + c*p3.y; + current.x = a*p1.x + b*c2.x + c*p3.x; + + float dy = current.y - previous.y; + float dx = current.x - previous.x; + float size = 0.5f*thick/sqrtf(dx*dx+dy*dy); + + if (i == 1) + { + points[0].x = previous.x + dy*size; + points[0].y = previous.y - dx*size; + points[1].x = previous.x - dy*size; + points[1].y = previous.y + dx*size; + } + + points[2*i + 1].x = current.x - dy*size; + points[2*i + 1].y = current.y + dx*size; + points[2*i].x = current.x + dy*size; + points[2*i].y = current.y - dx*size; + + previous = current; + } + + DrawTriangleStrip(points, 2*SPLINE_SEGMENT_DIVISIONS + 2, color); +} + +// Draw spline segment: Cubic Bezier, 2 points, 2 control points +void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float thick, Color color) +{ + const float step = 1.0f/SPLINE_SEGMENT_DIVISIONS; + + Vector2 previous = p1; + Vector2 current = { 0 }; + float t = 0.0f; + + Vector2 points[2*SPLINE_SEGMENT_DIVISIONS + 2] = { 0 }; + + for (int i = 1; i <= SPLINE_SEGMENT_DIVISIONS; i++) + { + t = step*(float)i; + + float a = powf(1.0f - t, 3); + float b = 3.0f*powf(1.0f - t, 2)*t; + float c = 3.0f*(1.0f - t)*powf(t, 2); + float d = powf(t, 3); + + current.y = a*p1.y + b*c2.y + c*c3.y + d*p4.y; + current.x = a*p1.x + b*c2.x + c*c3.x + d*p4.x; + + float dy = current.y - previous.y; + float dx = current.x - previous.x; + float size = 0.5f*thick/sqrtf(dx*dx+dy*dy); + + if (i == 1) + { + points[0].x = previous.x + dy*size; + points[0].y = previous.y - dx*size; + points[1].x = previous.x - dy*size; + points[1].y = previous.y + dx*size; + } + + points[2*i + 1].x = current.x - dy*size; + points[2*i + 1].y = current.y + dx*size; + points[2*i].x = current.x + dy*size; + points[2*i].y = current.y - dx*size; + + previous = current; + } + + DrawTriangleStrip(points, 2*SPLINE_SEGMENT_DIVISIONS + 2, color); +} + +// Get spline point for a given t [0.0f .. 1.0f], Linear +Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t) +{ + Vector2 point = { 0 }; + + point.x = startPos.x*(1.0f - t) + endPos.x*t; + point.y = startPos.y*(1.0f - t) + endPos.y*t; + + return point; +} + +// Get spline point for a given t [0.0f .. 1.0f], B-Spline +Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t) +{ + Vector2 point = { 0 }; + + float a[4] = { 0 }; + float b[4] = { 0 }; + + a[0] = (-p1.x + 3*p2.x - 3*p3.x + p4.x)/6.0f; + a[1] = (3*p1.x - 6*p2.x + 3*p3.x)/6.0f; + a[2] = (-3*p1.x + 3*p3.x)/6.0f; + a[3] = (p1.x + 4*p2.x + p3.x)/6.0f; + + b[0] = (-p1.y + 3*p2.y - 3*p3.y + p4.y)/6.0f; + b[1] = (3*p1.y - 6*p2.y + 3*p3.y)/6.0f; + b[2] = (-3*p1.y + 3*p3.y)/6.0f; + b[3] = (p1.y + 4*p2.y + p3.y)/6.0f; + + point.x = a[3] + t*(a[2] + t*(a[1] + t*a[0])); + point.y = b[3] + t*(b[2] + t*(b[1] + t*b[0])); + + return point; +} + +// Get spline point for a given t [0.0f .. 1.0f], Catmull-Rom +Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t) +{ + Vector2 point = { 0 }; + + float q0 = (-1*t*t*t) + (2*t*t) + (-1*t); + float q1 = (3*t*t*t) + (-5*t*t) + 2; + float q2 = (-3*t*t*t) + (4*t*t) + t; + float q3 = t*t*t - t*t; + + point.x = 0.5f*((p1.x*q0) + (p2.x*q1) + (p3.x*q2) + (p4.x*q3)); + point.y = 0.5f*((p1.y*q0) + (p2.y*q1) + (p3.y*q2) + (p4.y*q3)); + + return point; +} + +// Get spline point for a given t [0.0f .. 1.0f], Quadratic Bezier +Vector2 GetSplinePointBezierQuadratic(Vector2 startPos, Vector2 controlPos, Vector2 endPos, float t) +{ + Vector2 point = { 0 }; + + float a = powf(1.0f - t, 2); + float b = 2.0f*(1.0f - t)*t; + float c = powf(t, 2); + + point.y = a*startPos.y + b*controlPos.y + c*endPos.y; + point.x = a*startPos.x + b*controlPos.x + c*endPos.x; + + return point; +} + +// Get spline point for a given t [0.0f .. 1.0f], Cubic Bezier +Vector2 GetSplinePointBezierCubic(Vector2 startPos, Vector2 startControlPos, Vector2 endControlPos, Vector2 endPos, float t) +{ + Vector2 point = { 0 }; + + float a = powf(1.0f - t, 3); + float b = 3.0f*powf(1.0f - t, 2)*t; + float c = 3.0f*(1.0f - t)*powf(t, 2); + float d = powf(t, 3); + + point.y = a*startPos.y + b*startControlPos.y + c*endControlPos.y + d*endPos.y; + point.x = a*startPos.x + b*startControlPos.x + c*endControlPos.x + d*endPos.x; + + return point; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Collision Detection functions +//---------------------------------------------------------------------------------- + +// Check if point is inside rectangle +bool CheckCollisionPointRec(Vector2 point, Rectangle rec) +{ + bool collision = false; + + if ((point.x >= rec.x) && (point.x < (rec.x + rec.width)) && (point.y >= rec.y) && (point.y < (rec.y + rec.height))) collision = true; + + return collision; +} + +// Check if point is inside circle +bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius) +{ + bool collision = false; + + float distanceSquared = (point.x - center.x)*(point.x - center.x) + (point.y - center.y)*(point.y - center.y); + + if (distanceSquared <= radius*radius) collision = true; + + return collision; +} + +// Check if point is inside a triangle defined by three points (p1, p2, p3) +bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3) +{ + bool collision = false; + + float alpha = ((p2.y - p3.y)*(point.x - p3.x) + (p3.x - p2.x)*(point.y - p3.y)) / + ((p2.y - p3.y)*(p1.x - p3.x) + (p3.x - p2.x)*(p1.y - p3.y)); + + float beta = ((p3.y - p1.y)*(point.x - p3.x) + (p1.x - p3.x)*(point.y - p3.y)) / + ((p2.y - p3.y)*(p1.x - p3.x) + (p3.x - p2.x)*(p1.y - p3.y)); + + float gamma = 1.0f - alpha - beta; + + if ((alpha > 0) && (beta > 0) && (gamma > 0)) collision = true; + + return collision; +} + +// Check if point is within a polygon described by array of vertices +// NOTE: Based on http://jeffreythompson.org/collision-detection/poly-point.php +bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount) +{ + bool collision = false; + + if (pointCount > 2) + { + for (int i = 0, j = pointCount - 1; i < pointCount; j = i++) + { + if ((points[i].y > point.y) != (points[j].y > point.y) && + (point.x < (points[j].x - points[i].x)*(point.y - points[i].y)/(points[j].y - points[i].y) + points[i].x)) + { + collision = !collision; + } + } + } + + return collision; +} + +// Check collision between two rectangles +bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2) +{ + bool collision = false; + + if ((rec1.x < (rec2.x + rec2.width) && (rec1.x + rec1.width) > rec2.x) && + (rec1.y < (rec2.y + rec2.height) && (rec1.y + rec1.height) > rec2.y)) collision = true; + + return collision; +} + +// Check collision between two circles +bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2) +{ + bool collision = false; + + float dx = center2.x - center1.x; // X distance between centers + float dy = center2.y - center1.y; // Y distance between centers + + float distanceSquared = dx*dx + dy*dy; // Distance between centers squared + float radiusSum = radius1 + radius2; + + collision = (distanceSquared <= (radiusSum*radiusSum)); + + return collision; +} + +// Check collision between circle and rectangle +// NOTE: Reviewed version to take into account corner limit case +bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) +{ + bool collision = false; + + float recCenterX = rec.x + rec.width/2.0f; + float recCenterY = rec.y + rec.height/2.0f; + + float dx = fabsf(center.x - recCenterX); + float dy = fabsf(center.y - recCenterY); + + if ((dx <= (rec.width/2.0f + radius)) && (dy <= (rec.height/2.0f + radius))) + { + if (dx <= (rec.width/2.0f)) collision = true; + else if (dy <= (rec.height/2.0f)) collision = true; + else + { + float cornerDistanceSq = (dx - rec.width/2.0f)*(dx - rec.width/2.0f) + + (dy - rec.height/2.0f)*(dy - rec.height/2.0f); + + collision = (cornerDistanceSq <= (radius*radius)); + } + } + + return collision; +} + +// Check the collision between two lines defined by two points each, returns collision point by reference +// REF: https://en.wikipedia.org/wiki/Line–line_intersection#Given_two_points_on_each_line_segment +bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint) +{ + bool collision = false; + + float rx = endPos1.x - startPos1.x; + float ry = endPos1.y - startPos1.y; + float sx = endPos2.x - startPos2.x; + float sy = endPos2.y - startPos2.y; + + float div = rx*sy - ry*sx; + + if (fabsf(div) >= FLT_EPSILON) + { + float s12x = startPos2.x - startPos1.x; + float s12y = startPos2.y - startPos1.y; + + float t = (s12x*sy - s12y*sx)/div; + float u = (s12x*ry - s12y*rx)/div; + + if ((0.0f <= t) && (t <= 1.0f) && (0.0f <= u) && (u <= 1.0f)) + { + if (collisionPoint) + { + collisionPoint->x = startPos1.x + t*rx; + collisionPoint->y = startPos1.y + t*ry; + } + + collision = true; + } + } + + return collision; +} + +// Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] +bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold) +{ + bool collision = false; + + float dxc = point.x - p1.x; + float dyc = point.y - p1.y; + float dxl = p2.x - p1.x; + float dyl = p2.y - p1.y; + float cross = dxc*dyl - dyc*dxl; + + if (fabsf(cross) < (threshold*fmaxf(fabsf(dxl), fabsf(dyl)))) + { + if (fabsf(dxl) >= fabsf(dyl)) collision = (dxl > 0)? ((p1.x <= point.x) && (point.x <= p2.x)) : ((p2.x <= point.x) && (point.x <= p1.x)); + else collision = (dyl > 0)? ((p1.y <= point.y) && (point.y <= p2.y)) : ((p2.y <= point.y) && (point.y <= p1.y)); + } + + return collision; +} + +// Check if circle collides with a line created between two points [p1] and [p2] +bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2) +{ + bool collision = false; + + float dx = p1.x - p2.x; + float dy = p1.y - p2.y; + + if ((fabsf(dx) + fabsf(dy)) <= FLT_EPSILON) + { + collision = CheckCollisionCircles(p1, 0, center, radius); + } + else + { + float lengthSQ = ((dx*dx) + (dy*dy)); + float dotProduct = (((center.x - p1.x)*(p2.x - p1.x)) + ((center.y - p1.y)*(p2.y - p1.y)))/(lengthSQ); + + if (dotProduct > 1.0f) dotProduct = 1.0f; + else if (dotProduct < 0.0f) dotProduct = 0.0f; + + float dx2 = (p1.x - (dotProduct*(dx))) - center.x; + float dy2 = (p1.y - (dotProduct*(dy))) - center.y; + float distanceSQ = ((dx2*dx2) + (dy2*dy2)); + + if (distanceSQ <= radius*radius) collision = true; + } + + return collision; +} + +// Get collision rectangle for two rectangles collision +Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2) +{ + Rectangle overlap = { 0 }; + + float left = (rec1.x > rec2.x)? rec1.x : rec2.x; + float right1 = rec1.x + rec1.width; + float right2 = rec2.x + rec2.width; + float right = (right1 < right2)? right1 : right2; + float top = (rec1.y > rec2.y)? rec1.y : rec2.y; + float bottom1 = rec1.y + rec1.height; + float bottom2 = rec2.y + rec2.height; + float bottom = (bottom1 < bottom2)? bottom1 : bottom2; + + if ((left < right) && (top < bottom)) + { + overlap.x = left; + overlap.y = top; + overlap.width = right - left; + overlap.height = bottom - top; + } + + return overlap; +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Cubic easing in-out +// NOTE: Used by DrawLineBezier() only +static float EaseCubicInOut(float t, float b, float c, float d) +{ + float result = 0.0f; + + if ((t /= 0.5f*d) < 1) result = 0.5f*c*t*t*t + b; + else + { + t -= 2; + result = 0.5f*c*(t*t*t + 2.0f) + b; + } + + return result; +} + +#endif // SUPPORT_MODULE_RSHAPES diff --git a/src/libraries/raylib/raylib/src/rtext.c b/src/libraries/raylib/raylib/src/rtext.c new file mode 100644 index 0000000..ae802c1 --- /dev/null +++ b/src/libraries/raylib/raylib/src/rtext.c @@ -0,0 +1,3039 @@ +/********************************************************************************************** +* +* rtext - Basic functions to load fonts and draw text +* +* CONFIGURATION: +* #define SUPPORT_MODULE_RTEXT 1 +* rtext module is included in the build +* +* #define SUPPORT_FILEFORMAT_FNT 1 +* #define SUPPORT_FILEFORMAT_TTF 1 +* #define SUPPORT_FILEFORMAT_BDF 0 +* Selected desired fileformats to be supported for loading. Some of those formats are +* supported by default, to remove support, #define as 0 in this module or your build system +* +* #define MAX_TEXT_BUFFER_LENGTH 1024 +* Text functions using static buffer max size +* +* #define MAX_TEXTSPLIT_COUNT 128 +* TextSplit() function static substrings pointers array (pointing to static buffer) +* +* #define FONT_ATLAS_CORNER_REC_SIZE 3 +* On font atlas image generation [GenImageFontAtlas()], add a NxN pixels white rectangle +* at the bottom-right corner of the atlas. It can be useful to for shapes drawing, to allow +* drawing text and shapes with a single draw call [SetShapesTexture()] +* +* DEPENDENCIES: +* stb_truetype - Load TTF file and rasterize characters data +* stb_rect_pack - Rectangles packing algorithms, required for font atlas generation +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +#include "config.h" // Defines module configuration flags + +#if SUPPORT_MODULE_RTEXT + +#include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 -> Only DrawTextPro() + +#include // Required for: malloc(), free() +#include // Required for: vsprintf(), snprintf() +#include // Required for: strcmp(), strstr(), strncpy(), sscanf() [Used in LoadBMFont()] +#include // Required for: va_list, va_start(), vsprintf(), va_end() [Used in TextFormat()] +#include // Required for: toupper(), tolower() [Used in TextToUpper(), TextToLower()] + +#if SUPPORT_FILEFORMAT_TTF || SUPPORT_FILEFORMAT_BDF + #if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-function" + #endif + + #define STB_RECT_PACK_IMPLEMENTATION + #include "external/stb_rect_pack.h" // Required for: ttf/bdf font rectangles packaging + + #include // Required for: ttf/bdf font rectangles packaging + + #if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic pop + #endif +#endif + +#if SUPPORT_FILEFORMAT_TTF + #if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-function" + #endif + + #define STBTT_malloc(x,u) ((void)(u),RL_MALLOC(x)) + #define STBTT_free(x,u) ((void)(u),RL_FREE(x)) + + #define STBTT_STATIC + #define STB_TRUETYPE_IMPLEMENTATION + #include "external/stb_truetype.h" // Required for: ttf font data reading + + #if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic pop + #endif +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef MAX_TEXT_BUFFER_LENGTH + #define MAX_TEXT_BUFFER_LENGTH 1024 // Size of internal static buffers used on some functions: + // TextFormat(), TextSubtext(), TextToUpper(), TextToLower(), TextToPascal(), TextSplit() +#endif +#ifndef MAX_TEXTSPLIT_COUNT + #define MAX_TEXTSPLIT_COUNT 128 // Maximum number of substrings to split: TextSplit() +#endif + +#ifndef FONT_ATLAS_CORNER_REC_SIZE + // On font atlas image generation [GenImageFontAtlas()], add a 3x3 pixels white rectangle + // at the bottom-right corner of the atlas. It can be useful for shapes drawing, to allow + // drawing text and shapes with a single draw call [SetShapesTexture()] + #define FONT_ATLAS_CORNER_REC_SIZE 3 // Size of white rectangle drawn on font atlas on font loading +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Global variables +//---------------------------------------------------------------------------------- +// Default font provided by raylib +// NOTE: Default font is loaded on InitWindow() and disposed on CloseWindow() [module: core] +static Font defaultFont = { 0 }; + +// Text vertical line spacing in pixels (between lines) +static int textLineSpacing = 2; + +//---------------------------------------------------------------------------------- +// Other Modules Functions Declaration (required by text) +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +#if SUPPORT_FILEFORMAT_FNT +static Font LoadBMFont(const char *fileName); // Load a BMFont file (AngelCode font file) +#endif +#if SUPPORT_FILEFORMAT_BDF +static GlyphInfo *LoadFontDataBDF(const unsigned char *fileData, int dataSize, const int *codepoints, int codepointCount, int *outFontSize); +#endif + +extern void LoadFontDefault(void); +extern void UnloadFontDefault(void); + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +// Load raylib default font +extern void LoadFontDefault(void) +{ + #define BIT_CHECK(a,b) ((a) & (1u << (b))) + + // Check to see if the font for an image has already been allocated, + // and if no need to upload, then return + if (defaultFont.glyphs != NULL) return; + + // NOTE: Using UTF-8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement + // REF: http://www.utf8-chartable.de/unicode-utf8-table.pl + + defaultFont.glyphCount = 224; // Number of glyphs included in our default font + defaultFont.glyphPadding = 0; // Characters padding + + // Default font is directly defined here (data generated from a sprite font image) + // This way, reconstructing Font without creating large global variables + // This data is automatically allocated to Stack and automatically deallocated at the end of this function + unsigned int defaultFontData[512] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200020, 0x0001b000, 0x00000000, 0x00000000, 0x8ef92520, 0x00020a00, 0x7dbe8000, 0x1f7df45f, + 0x4a2bf2a0, 0x0852091e, 0x41224000, 0x10041450, 0x2e292020, 0x08220812, 0x41222000, 0x10041450, 0x10f92020, 0x3efa084c, 0x7d22103c, 0x107df7de, + 0xe8a12020, 0x08220832, 0x05220800, 0x10450410, 0xa4a3f000, 0x08520832, 0x05220400, 0x10450410, 0xe2f92020, 0x0002085e, 0x7d3e0281, 0x107df41f, + 0x00200000, 0x8001b000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xc0000fbe, 0xfbf7e00f, 0x5fbf7e7d, 0x0050bee8, 0x440808a2, 0x0a142fe8, 0x50810285, 0x0050a048, + 0x49e428a2, 0x0a142828, 0x40810284, 0x0048a048, 0x10020fbe, 0x09f7ebaf, 0xd89f3e84, 0x0047a04f, 0x09e48822, 0x0a142aa1, 0x50810284, 0x0048a048, + 0x04082822, 0x0a142fa0, 0x50810285, 0x0050a248, 0x00008fbe, 0xfbf42021, 0x5f817e7d, 0x07d09ce8, 0x00008000, 0x00000fe0, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000c0180, + 0xdfbf4282, 0x0bfbf7ef, 0x42850505, 0x004804bf, 0x50a142c6, 0x08401428, 0x42852505, 0x00a808a0, 0x50a146aa, 0x08401428, 0x42852505, 0x00081090, + 0x5fa14a92, 0x0843f7e8, 0x7e792505, 0x00082088, 0x40a15282, 0x08420128, 0x40852489, 0x00084084, 0x40a16282, 0x0842022a, 0x40852451, 0x00088082, + 0xc0bf4282, 0xf843f42f, 0x7e85fc21, 0x3e0900bf, 0x00000000, 0x00000004, 0x00000000, 0x000c0180, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04000402, 0x41482000, 0x00000000, 0x00000800, + 0x04000404, 0x4100203c, 0x00000000, 0x00000800, 0xf7df7df0, 0x514bef85, 0xbefbefbe, 0x04513bef, 0x14414500, 0x494a2885, 0xa28a28aa, 0x04510820, + 0xf44145f0, 0x474a289d, 0xa28a28aa, 0x04510be0, 0x14414510, 0x494a2884, 0xa28a28aa, 0x02910a00, 0xf7df7df0, 0xd14a2f85, 0xbefbe8aa, 0x011f7be0, + 0x00000000, 0x00400804, 0x20080000, 0x00000000, 0x00000000, 0x00600f84, 0x20080000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xac000000, 0x00000f01, 0x00000000, 0x00000000, 0x24000000, 0x00000f01, 0x00000000, 0x06000000, 0x24000000, 0x00000f01, 0x00000000, 0x09108000, + 0x24fa28a2, 0x00000f01, 0x00000000, 0x013e0000, 0x2242252a, 0x00000f52, 0x00000000, 0x038a8000, 0x2422222a, 0x00000f29, 0x00000000, 0x010a8000, + 0x2412252a, 0x00000f01, 0x00000000, 0x010a8000, 0x24fbe8be, 0x00000f01, 0x00000000, 0x0ebe8000, 0xac020000, 0x00000f01, 0x00000000, 0x00048000, + 0x0003e000, 0x00000f00, 0x00000000, 0x00008000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000038, 0x8443b80e, 0x00203a03, + 0x02bea080, 0xf0000020, 0xc452208a, 0x04202b02, 0xf8029122, 0x07f0003b, 0xe44b388e, 0x02203a02, 0x081e8a1c, 0x0411e92a, 0xf4420be0, 0x01248202, + 0xe8140414, 0x05d104ba, 0xe7c3b880, 0x00893a0a, 0x283c0e1c, 0x04500902, 0xc4400080, 0x00448002, 0xe8208422, 0x04500002, 0x80400000, 0x05200002, + 0x083e8e00, 0x04100002, 0x804003e0, 0x07000042, 0xf8008400, 0x07f00003, 0x80400000, 0x04000022, 0x00000000, 0x00000000, 0x80400000, 0x04000002, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00800702, 0x1848a0c2, 0x84010000, 0x02920921, 0x01042642, 0x00005121, 0x42023f7f, 0x00291002, + 0xefc01422, 0x7efdfbf7, 0xefdfa109, 0x03bbbbf7, 0x28440f12, 0x42850a14, 0x20408109, 0x01111010, 0x28440408, 0x42850a14, 0x2040817f, 0x01111010, + 0xefc78204, 0x7efdfbf7, 0xe7cf8109, 0x011111f3, 0x2850a932, 0x42850a14, 0x2040a109, 0x01111010, 0x2850b840, 0x42850a14, 0xefdfbf79, 0x03bbbbf7, + 0x001fa020, 0x00000000, 0x00001000, 0x00000000, 0x00002070, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08022800, 0x00012283, 0x02430802, 0x01010001, 0x8404147c, 0x20000144, 0x80048404, 0x00823f08, 0xdfbf4284, 0x7e03f7ef, 0x142850a1, 0x0000210a, + 0x50a14684, 0x528a1428, 0x142850a1, 0x03efa17a, 0x50a14a9e, 0x52521428, 0x142850a1, 0x02081f4a, 0x50a15284, 0x4a221428, 0xf42850a1, 0x03efa14b, + 0x50a16284, 0x4a521428, 0x042850a1, 0x0228a17a, 0xdfbf427c, 0x7e8bf7ef, 0xf7efdfbf, 0x03efbd0b, 0x00000000, 0x04000000, 0x00000000, 0x00000008, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200508, 0x00840400, 0x11458122, 0x00014210, + 0x00514294, 0x51420800, 0x20a22a94, 0x0050a508, 0x00200000, 0x00000000, 0x00050000, 0x08000000, 0xfefbefbe, 0xfbefbefb, 0xfbeb9114, 0x00fbefbe, + 0x20820820, 0x8a28a20a, 0x8a289114, 0x3e8a28a2, 0xfefbefbe, 0xfbefbe0b, 0x8a289114, 0x008a28a2, 0x228a28a2, 0x08208208, 0x8a289114, 0x088a28a2, + 0xfefbefbe, 0xfbefbefb, 0xfa2f9114, 0x00fbefbe, 0x00000000, 0x00000040, 0x00000000, 0x00000000, 0x00000000, 0x00000020, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00210100, 0x00000004, 0x00000000, 0x00000000, 0x14508200, 0x00001402, 0x00000000, 0x00000000, + 0x00000010, 0x00000020, 0x00000000, 0x00000000, 0xa28a28be, 0x00002228, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, + 0xa28a28aa, 0x000022a8, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, 0xbefbefbe, 0x00003e2f, 0x00000000, 0x00000000, + 0x00000004, 0x00002028, 0x00000000, 0x00000000, 0x80000000, 0x00003e0f, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; + + int charsHeight = 10; + int charsDivisor = 1; // Every char is separated from the consecutive by a 1 pixel divisor, horizontally and vertically + + int charsWidth[224] = { 3, 1, 4, 6, 5, 7, 6, 2, 3, 3, 5, 5, 2, 4, 1, 7, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 3, 4, 3, 6, + 7, 6, 6, 6, 6, 6, 6, 6, 6, 3, 5, 6, 5, 7, 6, 6, 6, 6, 6, 6, 7, 6, 7, 7, 6, 6, 6, 2, 7, 2, 3, 5, + 2, 5, 5, 5, 5, 5, 4, 5, 5, 1, 2, 5, 2, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 3, 1, 3, 4, 4, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 5, 5, 5, 7, 1, 5, 3, 7, 3, 5, 4, 1, 7, 4, 3, 5, 3, 3, 2, 5, 6, 1, 2, 2, 3, 5, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 3, 3, 3, 3, 7, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 4, 6, + 5, 5, 5, 5, 5, 5, 9, 5, 5, 5, 5, 5, 2, 2, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5 }; + + // Re-construct image from defaultFontData and generate OpenGL texture + //---------------------------------------------------------------------- + Image imFont = { + .data = RL_CALLOC(128*128, 2), // 2 bytes per pixel (gray + alpha) + .width = 128, + .height = 128, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA + }; + + // Fill image.data with defaultFontData (convert from bit to pixel!) + for (int i = 0, counter = 0; i < imFont.width*imFont.height; i += 32) + { + for (int j = 31; j >= 0; j--) + { + if (BIT_CHECK(defaultFontData[counter], j)) + { + // NOTE: Unreferencing data as short, so, + // considering data as little-endian (alpha + gray) + ((unsigned short *)imFont.data)[i + j] = 0xffff; + } + else + { + ((unsigned char *)imFont.data)[(i + j)*sizeof(short)] = 0xff; + ((unsigned char *)imFont.data)[(i + j)*sizeof(short) + 1] = 0x00; + } + } + + counter++; + } + + defaultFont.texture = LoadTextureFromImage(imFont); + + // Check again if font glyph data has been already loaded + // to avoid reallocating the glyphs and rects + if (defaultFont.glyphs != NULL) + { + UnloadImage(imFont); + return; + } + + // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, glyphCount + //------------------------------------------------------------------------------ + // Allocate space for our characters info data + // NOTE: This memory must be freed at end! --> Done by CloseWindow() + defaultFont.glyphs = (GlyphInfo *)RL_CALLOC(defaultFont.glyphCount, sizeof(GlyphInfo)); + defaultFont.recs = (Rectangle *)RL_CALLOC(defaultFont.glyphCount, sizeof(Rectangle)); + + int currentLine = 0; + int currentPosX = charsDivisor; + int testPosX = charsDivisor; + + for (int i = 0; i < defaultFont.glyphCount; i++) + { + defaultFont.glyphs[i].value = 32 + i; // First char is 32 + + defaultFont.recs[i].x = (float)currentPosX; + defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); + defaultFont.recs[i].width = (float)charsWidth[i]; + defaultFont.recs[i].height = (float)charsHeight; + + testPosX += (int)(defaultFont.recs[i].width + (float)charsDivisor); + + if (testPosX >= imFont.width) + { + currentLine++; + currentPosX = 2*charsDivisor + charsWidth[i]; + testPosX = currentPosX; + + defaultFont.recs[i].x = (float)charsDivisor; + defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); + } + else currentPosX = testPosX; + + // NOTE: On default font character offsets and xAdvance are not required + defaultFont.glyphs[i].offsetX = 0; + defaultFont.glyphs[i].offsetY = 0; + defaultFont.glyphs[i].advanceX = 0; + + // Fill character image data from fontClear data + defaultFont.glyphs[i].image = ImageFromImage(imFont, defaultFont.recs[i]); + } + + UnloadImage(imFont); + + defaultFont.baseSize = (int)defaultFont.recs[0].height; + + TRACELOG(LOG_INFO, "FONT: Default font loaded successfully (%i glyphs)", defaultFont.glyphCount); +} + +// Unload raylib default font +extern void UnloadFontDefault(void) +{ + for (int i = 0; i < defaultFont.glyphCount; i++) UnloadImage(defaultFont.glyphs[i].image); + UnloadTexture(defaultFont.texture); + RL_FREE(defaultFont.glyphs); + RL_FREE(defaultFont.recs); + defaultFont.glyphCount = 0; + defaultFont.glyphs = NULL; + defaultFont.recs = NULL; +} + +// Get the default font +Font GetFontDefault() +{ + return defaultFont; +} + +// Load Font from file into GPU memory (VRAM) +Font LoadFont(const char *fileName) +{ + // Default values for ttf font generation +#ifndef FONT_TTF_DEFAULT_SIZE + #define FONT_TTF_DEFAULT_SIZE 32 // TTF font generation default char size (char-height) +#endif +#ifndef FONT_TTF_DEFAULT_NUMCHARS + #define FONT_TTF_DEFAULT_NUMCHARS 95 // TTF font generation default charset: 95 glyphs (ASCII 32..126) +#endif +#ifndef FONT_TTF_DEFAULT_FIRST_CHAR + #define FONT_TTF_DEFAULT_FIRST_CHAR 32 // TTF font generation default first char for image sprite font (32-Space) +#endif +#ifndef FONT_TTF_DEFAULT_CHARS_PADDING + #define FONT_TTF_DEFAULT_CHARS_PADDING 4 // TTF font generation default glyphs padding +#endif + + Font font = { 0 }; + +#if SUPPORT_FILEFORMAT_TTF + if (IsFileExtension(fileName, ".ttf") || IsFileExtension(fileName, ".otf")) font = LoadFontEx(fileName, FONT_TTF_DEFAULT_SIZE, NULL, FONT_TTF_DEFAULT_NUMCHARS); + else +#endif +#if SUPPORT_FILEFORMAT_FNT + if (IsFileExtension(fileName, ".fnt")) font = LoadBMFont(fileName); + else +#endif +#if SUPPORT_FILEFORMAT_BDF + if (IsFileExtension(fileName, ".bdf")) font = LoadFontEx(fileName, FONT_TTF_DEFAULT_SIZE, NULL, FONT_TTF_DEFAULT_NUMCHARS); + else +#endif + { + Image image = LoadImage(fileName); + if (image.data != NULL) font = LoadFontFromImage(image, MAGENTA, FONT_TTF_DEFAULT_FIRST_CHAR); + else font = GetFontDefault(); + UnloadImage(image); + } + + if (font.texture.id == 0) TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName); + else + { + SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default, set point filter (the best performance) + TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", font.baseSize, font.glyphCount); + } + + return font; +} + +// Load font from file with defined codepoints and generation size +// NOTE: NULL for codepoints and 0 for codepointCount to load the default character set (32..126), +// font size is provided in pixels height +Font LoadFontEx(const char *fileName, int fontSize, const int *codepoints, int codepointCount) +{ + Font font = { 0 }; + + // Loading file to memory + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData != NULL) + { + // Loading font from memory data + font = LoadFontFromMemory(GetFileExtension(fileName), fileData, dataSize, fontSize, codepoints, codepointCount); + + UnloadFileData(fileData); + } + + return font; +} + +// Load an Image font file (XNA style) +Font LoadFontFromImage(Image image, Color key, int firstChar) +{ +#ifndef MAX_GLYPHS_FROM_IMAGE + #define MAX_GLYPHS_FROM_IMAGE 256 // Maximum number of glyphs supported on image scan +#endif + + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r) && (col1.g == col2.g) && (col1.b == col2.b) && (col1.a == col2.a)) + + Font font = GetFontDefault(); + + int charSpacing = 0; + int lineSpacing = 0; + + // Allocate a temporal arrays for glyphs data measures, + // once the actual number of glyphs is obtained, copy data to a sized array + int tempCharValues[MAX_GLYPHS_FROM_IMAGE] = { 0 }; + Rectangle tempCharRecs[MAX_GLYPHS_FROM_IMAGE] = { 0 }; + + Color *pixels = LoadImageColors(image); + + // Parse image data to get charSpacing and lineSpacing + int x = 0; + int y = 0; + for (y = 0; y < image.height; y++) + { + for (x = 0; x < image.width; x++) + { + if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break; + } + + if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break; + } + + // Security check + if ((x == 0) || (y == 0)) + { + UnloadImageColors(pixels); + return font; + } + + charSpacing = x; + lineSpacing = y; + + int charHeight = 0; + int j = 0; + + while (((lineSpacing + j) < image.height) && + !COLOR_EQUAL(pixels[(lineSpacing + j)*image.width + charSpacing], key)) j++; + + charHeight = j; + + // Check array values to get characters: value, x, y, w, h + int index = 0; + int lineToRead = 0; + int xPosToRead = charSpacing; + + // Parse image data to get rectangle sizes + while ((lineSpacing + lineToRead*(charHeight + lineSpacing)) < image.height) + { + while ((xPosToRead < image.width) && + !COLOR_EQUAL((pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead]), key)) + { + tempCharValues[index] = firstChar + index; + + tempCharRecs[index].x = (float)xPosToRead; + tempCharRecs[index].y = (float)(lineSpacing + lineToRead*(charHeight + lineSpacing)); + tempCharRecs[index].height = (float)charHeight; + + int charWidth = 0; + + while (((xPosToRead + charWidth) < image.width) && + !COLOR_EQUAL(pixels[(lineSpacing + (charHeight + lineSpacing)*lineToRead)*image.width + xPosToRead + charWidth], key)) charWidth++; + + tempCharRecs[index].width = (float)charWidth; + + index++; + + xPosToRead += (charWidth + charSpacing); + } + + lineToRead++; + xPosToRead = charSpacing; + } + + // NOTE: Key color borders need to be removed from image to avoid weird + // artifacts on texture scaling when using TEXTURE_FILTER_BILINEAR or TEXTURE_FILTER_TRILINEAR + for (int i = 0; i < image.height*image.width; i++) if (COLOR_EQUAL(pixels[i], key)) pixels[i] = BLANK; + + // Create a new image with the processed color data (key color replaced by BLANK) + Image fontClear = { + .data = pixels, + .width = image.width, + .height = image.height, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + }; + + // Set font with all data parsed from image + font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture + font.glyphCount = index; + font.glyphPadding = 0; + + // Populate tempCharValues and tempCharsRecs with glyphs data + // Move temp data to sized charValues and charRecs arrays + font.glyphs = (GlyphInfo *)RL_MALLOC(font.glyphCount*sizeof(GlyphInfo)); + font.recs = (Rectangle *)RL_MALLOC(font.glyphCount*sizeof(Rectangle)); + + for (int i = 0; i < font.glyphCount; i++) + { + font.glyphs[i].value = tempCharValues[i]; + + // Get character rectangle in the font atlas texture + font.recs[i] = tempCharRecs[i]; + + // NOTE: On image based fonts (XNA style), character offsets and xAdvance are not required (set to 0) + font.glyphs[i].offsetX = 0; + font.glyphs[i].offsetY = 0; + font.glyphs[i].advanceX = 0; + + // Fill character image data from fontClear data + font.glyphs[i].image = ImageFromImage(fontClear, tempCharRecs[i]); + } + + UnloadImage(fontClear); // Unload processed image once converted to texture + + font.baseSize = (int)font.recs[0].height; + + return font; +} + +// Load font from memory buffer, fileType refers to extension: i.e. ".ttf" +Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, const int *codepoints, int codepointCount) +{ + Font font = { 0 }; + + char fileExtLower[16] = { 0 }; + snprintf(fileExtLower, 16, "%s", TextToLower(fileType)); + + font.baseSize = fontSize; + font.glyphPadding = 0; + +#if SUPPORT_FILEFORMAT_TTF + if (TextIsEqual(fileExtLower, ".ttf") || + TextIsEqual(fileExtLower, ".otf")) + { + font.glyphs = LoadFontData(fileData, dataSize, font.baseSize, codepoints, (codepointCount > 0)? codepointCount : 95, FONT_DEFAULT, &font.glyphCount); + } + else +#endif +#if SUPPORT_FILEFORMAT_BDF + if (TextIsEqual(fileExtLower, ".bdf")) + { + font.glyphs = LoadFontDataBDF(fileData, dataSize, codepoints, (codepointCount > 0)? codepointCount : 95, &font.baseSize); + font.glyphCount = (codepointCount > 0)? codepointCount : 95; + } + else +#endif + { + font.glyphs = NULL; + } + +#if SUPPORT_FILEFORMAT_TTF || SUPPORT_FILEFORMAT_BDF + if (font.glyphs != NULL) + { + font.glyphPadding = FONT_TTF_DEFAULT_CHARS_PADDING; + + Image atlas = GenImageFontAtlas(font.glyphs, &font.recs, font.glyphCount, font.baseSize, font.glyphPadding, 0); + font.texture = LoadTextureFromImage(atlas); + + // Update glyphs[i].image to use alpha, required to be used on ImageDrawText() + for (int i = 0; i < font.glyphCount; i++) + { + UnloadImage(font.glyphs[i].image); + font.glyphs[i].image = ImageFromImage(atlas, font.recs[i]); + } + + UnloadImage(atlas); + + TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", font.baseSize, font.glyphCount); + } + else + { + TRACELOG(LOG_WARNING, "FONT: Font is not supported by LoadFontEx/LoadFontFromMemory or no glyphs found, reverted to default font"); + font = GetFontDefault(); + } +#else + TRACELOG(LOG_WARNING, "FONT: Font is not supported by LoadFontEx/LoadFontFromMemory or no glyphs found, reverted to default font"); + font = GetFontDefault(); +#endif + + return font; +} + +// Check if font is valid (font data loaded) +// WARNING: GPU texture not checked +bool IsFontValid(Font font) +{ + return ((font.baseSize > 0) && // Validate font size + (font.glyphCount > 0) && // Validate font contains some glyph + (font.recs != NULL) && // Validate font recs defining glyphs on texture atlas + (font.glyphs != NULL)); // Validate glyph data is loaded + + // NOTE: Further validations could be done to verify if recs and glyphs contain valid data (glyphs values, metrics...) +} + +// Load font data for further use +// NOTE: Requires TTF font memory data and can generate SDF data +GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, const int *codepoints, int codepointCount, int type, int *glyphCount) +{ + // NOTE: Using some SDF generation default values, + // trades off precision with ability to handle *smaller* sizes +#ifndef FONT_SDF_CHAR_PADDING + #define FONT_SDF_CHAR_PADDING 4 // SDF font generation char padding +#endif +#ifndef FONT_SDF_ON_EDGE_VALUE + #define FONT_SDF_ON_EDGE_VALUE 128 // SDF font generation on edge value +#endif +#ifndef FONT_SDF_PIXEL_DIST_SCALE + #define FONT_SDF_PIXEL_DIST_SCALE 64.0f // SDF font generation pixel distance scale +#endif +#ifndef FONT_BITMAP_ALPHA_THRESHOLD + #define FONT_BITMAP_ALPHA_THRESHOLD 80 // Bitmap (B&W) font generation alpha threshold +#endif + + GlyphInfo *glyphs = NULL; + int glyphCounter = 0; + +#if SUPPORT_FILEFORMAT_TTF + // Load font data (including pixel data) from TTF memory file + // NOTE: Loaded information should be enough to generate font image atlas, using any packaging method + if (fileData != NULL) + { + bool genFontChars = false; + stbtt_fontinfo fontInfo = { 0 }; + // TODO: Should a shallow copy be created to avoid "dealing" with a const user array? + int *requiredCodepoints = (int *)codepoints; + + if (stbtt_InitFont(&fontInfo, (unsigned char *)fileData, 0)) // Initialize font for data reading + { + // Calculate font scale factor + float scaleFactor = stbtt_ScaleForPixelHeight(&fontInfo, (float)fontSize); + + // Calculate font basic metrics + // NOTE: ascent is equivalent to font baseline + int ascent = 0; + int descent = 0; + int lineGap = 0; + stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); + + // In case no chars count provided, default to 95 + codepointCount = (codepointCount > 0)? codepointCount : 95; + + // Fill fontChars in case not provided externally + // NOTE: By default filling glyphCount consecutively, starting at 32 (Space) + if (requiredCodepoints == NULL) + { + requiredCodepoints = (int *)RL_MALLOC(codepointCount*sizeof(int)); + for (int i = 0; i < codepointCount; i++) requiredCodepoints[i] = i + 32; + genFontChars = true; + } + + // Check available glyphs on provided font before loading them + for (int i = 0, index; i < codepointCount; i++) + { + index = stbtt_FindGlyphIndex(&fontInfo, requiredCodepoints[i]); + if (index > 0) glyphCounter++; + } + + // WARNING: Allocating space for maximum number of codepoints + glyphs = (GlyphInfo *)RL_CALLOC(glyphCounter, sizeof(GlyphInfo)); + glyphCounter = 0; // Reset to reuse + + int k = 0; + for (int i = 0; i < codepointCount; i++) + { + int cpWidth = 0, cpHeight = 0; // Codepoint width and height (on generation) + int cp = requiredCodepoints[i]; // Codepoint value to get info for + + // Render a unicode codepoint to a bitmap + // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap + // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be + // stbtt_MakeCodepointBitmap() -- renders into a provided bitmap + + // Check if glyph is available in the font + // WARNING: if (index == 0), glyph not found, it could fallback to default .notdef glyph (if defined in font) + int index = stbtt_FindGlyphIndex(&fontInfo, cp); + + if (index > 0) + { + // NOTE: Only storing glyphs for codepoints found in the font + glyphs[k].value = cp; + + switch (type) + { + case FONT_DEFAULT: + case FONT_BITMAP: + { + glyphs[k].image.data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, cp, + &cpWidth, &cpHeight, &glyphs[k].offsetX, &glyphs[k].offsetY); + } break; + case FONT_SDF: + { + if (cp != 32) + { + glyphs[k].image.data = stbtt_GetCodepointSDF(&fontInfo, scaleFactor, cp, + FONT_SDF_CHAR_PADDING, FONT_SDF_ON_EDGE_VALUE, FONT_SDF_PIXEL_DIST_SCALE, + &cpWidth, &cpHeight, &glyphs[k].offsetX, &glyphs[k].offsetY); + } + } break; + //case FONT_MSDF: + default: break; + } + + if (glyphs[k].image.data != NULL) // Glyph data has been found in the font + { + stbtt_GetCodepointHMetrics(&fontInfo, cp, &glyphs[k].advanceX, NULL); + glyphs[k].advanceX = (int)((float)glyphs[k].advanceX*scaleFactor); + + // WARNING: If requested SDF font, sdf-glyph height is definitely bigger than fontSize due to FONT_SDF_CHAR_PADDING + if ((type != FONT_SDF) && (cpHeight > fontSize)) TRACELOG(LOG_WARNING, "FONT: [0x%04x] Glyph height is bigger than requested font size: %i > %i", cp, cpHeight, (int)fontSize); + + // Load glyph image + glyphs[k].image.width = cpWidth; + glyphs[k].image.height = cpHeight; + glyphs[k].image.mipmaps = 1; + glyphs[k].image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + + glyphs[k].offsetY += (int)((float)ascent*scaleFactor); + } + //else TRACELOG(LOG_WARNING, "FONT: Glyph [0x%08x] has no image data available", cp); // Only reported for 0x20 and 0x3000 + + // Create an empty image for Space character (0x20), useful for sprite font generation + // NOTE: Another space to consider: 0x3000 (CJK - Ideographic Space) + if ((cp == 0x20) || (cp == 0x3000)) + { + stbtt_GetCodepointHMetrics(&fontInfo, cp, &glyphs[k].advanceX, NULL); + glyphs[k].advanceX = (int)((float)glyphs[k].advanceX*scaleFactor); + + Image imSpace = { + .data = NULL, + .width = glyphs[k].advanceX, + .height = fontSize, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE + }; + + // Only allocate space image if required + if (glyphs[k].advanceX > 0) imSpace.data = RL_CALLOC(glyphs[k].advanceX*fontSize, 1); + else glyphs[k].advanceX = 0; + + glyphs[k].image = imSpace; + } + + if (type == FONT_BITMAP) + { + // Aliased bitmap (black & white) font generation, avoiding anti-aliasing + // NOTE: For optimum results, bitmap font should be generated at base pixel size + for (int p = 0; p < cpWidth*cpHeight; p++) + { + if (((unsigned char *)glyphs[k].image.data)[p] < FONT_BITMAP_ALPHA_THRESHOLD) + ((unsigned char *)glyphs[k].image.data)[p] = 0; + else ((unsigned char *)glyphs[k].image.data)[p] = 255; + } + } + + k++; + glyphCounter++; + } + else + { + // WARNING: Glyph not found on font, optionally use a fallback glyph + } + } + + if (glyphCounter < codepointCount) TRACELOG(LOG_WARNING, "FONT: Requested codepoints glyphs found: [%i/%i]", k, codepointCount); + } + else TRACELOG(LOG_WARNING, "FONT: Failed to process TTF font data"); + + if (genFontChars) RL_FREE(requiredCodepoints); + } +#endif + + *glyphCount = glyphCounter; + return glyphs; +} + +// Generate image font atlas using chars info +// NOTE: Packing method: 0-Default, 1-Skyline +#if SUPPORT_FILEFORMAT_TTF || SUPPORT_FILEFORMAT_BDF +Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod) +{ + Image atlas = { 0 }; + + if (glyphs == NULL) + { + TRACELOG(LOG_WARNING, "FONT: Provided glyphs info not valid, returning empty image atlas"); + return atlas; + } + + *glyphRecs = NULL; + + // In case no chars count provided, suppose default of 95 + glyphCount = (glyphCount > 0)? glyphCount : 95; + + // NOTE: Rectangles memory is loaded here! + Rectangle *recs = (Rectangle *)RL_MALLOC(glyphCount*sizeof(Rectangle)); + + // Calculate image size based on total glyph width and glyph row count + int totalWidth = 0; + int maxGlyphWidth = 0; + + for (int i = 0; i < glyphCount; i++) + { + if (glyphs[i].image.width > maxGlyphWidth) maxGlyphWidth = glyphs[i].image.width; + totalWidth += glyphs[i].image.width + 2*padding; + } + + int paddedFontSize = fontSize + 2*padding; + + // Estimate image atlas size from available data + // NOTE: Multiplying total expected area by 1.2f scale factor but in case + // some glyphs do not fit, the atlas height is scaled x2 to fit them + float totalArea = totalWidth*paddedFontSize*1.2f; + float imageMinSize = sqrtf(totalArea); + int imageSize = (int)powf(2, ceilf(logf(imageMinSize)/logf(2))); + + if (totalArea < ((imageSize*imageSize)/2)) + { + atlas.width = imageSize; // Atlas bitmap width + atlas.height = imageSize/2; // Atlas bitmap height + } + else + { + atlas.width = imageSize; // Atlas bitmap width + atlas.height = imageSize; // Atlas bitmap height + } + + int atlasDataSize = atlas.width*atlas.height; // Save total size for bounds checking + atlas.data = (unsigned char *)RL_CALLOC(atlasDataSize, 1); // Create a bitmap to store characters (8 bpp) + atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + atlas.mipmaps = 1; + + // DEBUG: View padding in the generated image setting a gray background... + //for (int i = 0; i < atlas.width*atlas.height; i++) ((unsigned char *)atlas.data)[i] = 100; + + if (packMethod == 0) // Use basic packing algorithm + { + int offsetX = padding; + int offsetY = padding; + + // NOTE: Using simple packaging, one char after another + for (int i = 0; i < glyphCount; i++) + { + // Check remaining space for glyph + if (offsetX >= (atlas.width - glyphs[i].image.width - 2*padding)) + { + offsetX = padding; + + // NOTE: Be careful on offsetY for SDF fonts, by default SDF + // use an internal padding of 4 pixels, it means char rectangle + // height is bigger than fontSize, it could be up to (fontSize + 8) + offsetY += (fontSize + 2*padding); + + if (offsetY > (atlas.height - fontSize - padding)) + { + TRACELOG(LOG_WARNING, "FONT: Updating atlas size to fit all characters"); + + // Update atlas size to fit all characters + int updatedAtlasHeight = atlas.height*2; + int updatedAtlasDataSize = atlas.width*updatedAtlasHeight; + unsigned char *updatedAtlasData = (unsigned char *)RL_CALLOC(updatedAtlasDataSize, 1); + + memcpy(updatedAtlasData, atlas.data, atlasDataSize); + RL_FREE(atlas.data); + atlas.data = updatedAtlasData; + atlas.height = updatedAtlasHeight; + atlasDataSize = updatedAtlasDataSize; + } + } + + // Copy pixel data from glyph image to atlas + for (int y = 0; y < glyphs[i].image.height; y++) + { + for (int x = 0; x < glyphs[i].image.width; x++) + { + int destX = offsetX + x; + int destY = offsetY + y; + + // Security: check both lower and upper bounds + if ((destX >= 0) && (destX < atlas.width) && (destY >= 0) && (destY < atlas.height)) + { + ((unsigned char *)atlas.data)[destY*atlas.width + destX] = + ((unsigned char *)glyphs[i].image.data)[y*glyphs[i].image.width + x]; + } + } + } + + // Fill chars rectangles in atlas info + recs[i].x = (float)offsetX; + recs[i].y = (float)offsetY; + recs[i].width = (float)glyphs[i].image.width; + recs[i].height = (float)glyphs[i].image.height; + + // Move atlas position X for next character drawing + offsetX += (glyphs[i].image.width + 2*padding); + } + } + else if (packMethod == 1) // Use Skyline rect packing algorithm (stb_pack_rect) + { + stbrp_context *context = (stbrp_context *)RL_MALLOC(sizeof(*context)); + stbrp_node *nodes = (stbrp_node *)RL_MALLOC(glyphCount*sizeof(*nodes)); + + stbrp_init_target(context, atlas.width, atlas.height, nodes, glyphCount); + stbrp_rect *rects = (stbrp_rect *)RL_MALLOC(glyphCount*sizeof(stbrp_rect)); + + // Fill rectangles for packaging + for (int i = 0; i < glyphCount; i++) + { + rects[i].id = i; + rects[i].w = glyphs[i].image.width + 2*padding; + rects[i].h = glyphs[i].image.height + 2*padding; + } + + // Package rectangles into atlas + stbrp_pack_rects(context, rects, glyphCount); + + for (int i = 0; i < glyphCount; i++) + { + // It returns char rectangles in atlas + recs[i].x = rects[i].x + (float)padding; + recs[i].y = rects[i].y + (float)padding; + recs[i].width = (float)glyphs[i].image.width; + recs[i].height = (float)glyphs[i].image.height; + + if (rects[i].was_packed) + { + // Copy pixel data from fc.data to atlas + for (int y = 0; y < glyphs[i].image.height; y++) + { + for (int x = 0; x < glyphs[i].image.width; x++) + { + int destX = rects[i].x + padding + x; + int destY = rects[i].y + padding + y; + + // Security fix: check both lower and upper bounds + if (destX >= 0 && destX < atlas.width && destY >= 0 && destY < atlas.height) + { + ((unsigned char *)atlas.data)[destY*atlas.width + destX] = + ((unsigned char *)glyphs[i].image.data)[y*glyphs[i].image.width + x]; + } + } + } + } + else TRACELOG(LOG_WARNING, "FONT: Failed to package glyph (0x%02x)", glyphs[i].value); + } + + RL_FREE(rects); + RL_FREE(nodes); + RL_FREE(context); + } + + // Add a 3x3 white rectangle at the bottom-right corner of the generated atlas, + // useful to use as the white texture to draw shapes with raylib + // Security: ensure the atlas is large enough to hold a 3x3 rectangle + if ((FONT_ATLAS_CORNER_REC_SIZE > 0) && (atlas.width >= 3) && (atlas.height >= 3)) + { + for (int i = 0, k = atlas.width*atlas.height - 1; i < FONT_ATLAS_CORNER_REC_SIZE; i++) + { + ((unsigned char *)atlas.data)[k - 0] = 255; + ((unsigned char *)atlas.data)[k - 1] = 255; + ((unsigned char *)atlas.data)[k - 2] = 255; + k -= atlas.width; + } + } + + // Convert image data from GRAYSCALE to GRAY_ALPHA + unsigned char *dataGrayAlpha = (unsigned char *)RL_MALLOC(atlas.width*atlas.height*sizeof(unsigned char)*2); // Two channels + + for (int i = 0, k = 0; i < atlas.width*atlas.height; i++, k += 2) + { + dataGrayAlpha[k] = 255; + dataGrayAlpha[k + 1] = ((unsigned char *)atlas.data)[i]; + } + + RL_FREE(atlas.data); + atlas.data = dataGrayAlpha; + atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + + *glyphRecs = recs; + + return atlas; +} +#endif + +// Unload font glyphs info data (RAM) +void UnloadFontData(GlyphInfo *glyphs, int glyphCount) +{ + if (glyphs != NULL) + { + for (int i = 0; i < glyphCount; i++) UnloadImage(glyphs[i].image); + + RL_FREE(glyphs); + } +} + +// Unload Font from GPU memory (VRAM) +void UnloadFont(Font font) +{ + // NOTE: Make sure font is not default font (fallback) + if (font.texture.id != GetFontDefault().texture.id) + { + UnloadFontData(font.glyphs, font.glyphCount); + UnloadTexture(font.texture); + RL_FREE(font.recs); + + TRACELOG(LOG_DEBUG, "FONT: Unloaded font data from RAM and VRAM"); + } +} + +// Export font as code file, returns true on success +bool ExportFontAsCode(Font font, const char *fileName) +{ + bool result = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + // Get file name from path + char fileNamePascal[256] = { 0 }; + snprintf(fileNamePascal, 256, "%s", TextToPascal(GetFileNameWithoutExt(fileName))); + + // Get font atlas image and size, required to estimate code file size + // NOTE: This mechanism is highly coupled to raylib + Image image = LoadImageFromTexture(font.texture); + if (image.format != PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) TRACELOG(LOG_WARNING, "Font export as code: Font image format is not GRAY+ALPHA!"); + int imageDataSize = GetPixelDataSize(image.width, image.height, image.format); + + // Image data is usually GRAYSCALE + ALPHA and can be reduced to GRAYSCALE + //ImageFormat(&image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); + + // Estimate text code size + // - Image data is stored as "0x%02x", so it requires at least 4 char per byte, let's use 6 + // - font.recs[] data is stored as "{ %1.0f, %1.0f, %1.0f , %1.0f }", let's reserve 64 per rec + // - font.glyphs[] data is stored as "{ %i, %i, %i, %i, { 0 }},\n", let's reserve 64 per glyph + // - Comments and additional code, let's reserve 32KB + int txtDataSize = imageDataSize*6 + font.glyphCount*64 + font.glyphCount*64 + 32768; + char *txtData = (char *)RL_CALLOC(txtDataSize, sizeof(char)); + + int byteCount = 0; + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// FontAsCode exporter v1.0 - Font data exported as an array of bytes //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2026 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// ---------------------------------------------------------------------------------- //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// TODO: Fill the information and license of the exported font here: //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// Font name: .... //\n"); + byteCount += sprintf(txtData + byteCount, "// Font creator: .... //\n"); + byteCount += sprintf(txtData + byteCount, "// Font LICENSE: .... //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + +#define SUPPORT_COMPRESSED_FONT_ATLAS +#if defined(SUPPORT_COMPRESSED_FONT_ATLAS) + // WARNING: Data is compressed using raylib CompressData() DEFLATE, + // it requires to be decompressed with raylib DecompressData(), that requires + // compiling raylib with SUPPORT_COMPRESSION_API config flag enabled + + // Compress font image data + int compDataSize = 0; + unsigned char *compData = CompressData((const unsigned char *)image.data, imageDataSize, &compDataSize); + + // Save font image data (compressed) + byteCount += sprintf(txtData + byteCount, "#define COMPRESSED_DATA_SIZE_FONT_%s %i\n\n", TextToUpper(fileNamePascal), compDataSize); + byteCount += sprintf(txtData + byteCount, "// Font image pixels data compressed (DEFLATE)\n"); + byteCount += sprintf(txtData + byteCount, "// NOTE: Original pixel data simplified to GRAYSCALE\n"); + byteCount += sprintf(txtData + byteCount, "static unsigned char fontData_%s[COMPRESSED_DATA_SIZE_FONT_%s] = { ", fileNamePascal, TextToUpper(fileNamePascal)); + for (int i = 0; i < compDataSize - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%02x,\n " : "0x%02x, "), compData[i]); + byteCount += sprintf(txtData + byteCount, "0x%02x };\n\n", compData[compDataSize - 1]); + RL_FREE(compData); +#else + // Save font image data (uncompressed) + byteCount += sprintf(txtData + byteCount, "// Font image pixels data\n"); + byteCount += sprintf(txtData + byteCount, "// NOTE: 2 bytes per pixel, GRAY + ALPHA channels\n"); + byteCount += sprintf(txtData + byteCount, "static unsigned char fontImageData_%s[%i] = { ", fileNamePascal, imageDataSize); + for (int i = 0; i < imageDataSize - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%02x,\n " : "0x%02x, "), ((unsigned char *)imFont.data)[i]); + byteCount += sprintf(txtData + byteCount, "0x%02x };\n\n", ((unsigned char *)imFont.data)[imageDataSize - 1]); +#endif + + // Save font recs data + byteCount += sprintf(txtData + byteCount, "// Font characters rectangles data\n"); + byteCount += sprintf(txtData + byteCount, "static Rectangle fontRecs_%s[%i] = {\n", fileNamePascal, font.glyphCount); + for (int i = 0; i < font.glyphCount; i++) + { + byteCount += sprintf(txtData + byteCount, " { %1.0f, %1.0f, %1.0f , %1.0f },\n", font.recs[i].x, font.recs[i].y, font.recs[i].width, font.recs[i].height); + } + byteCount += sprintf(txtData + byteCount, "};\n\n"); + + // Save font glyphs data + // NOTE: Glyphs image data not saved (grayscale pixels), it could be generated from image and recs + byteCount += sprintf(txtData + byteCount, "// Font glyphs info data\n"); + byteCount += sprintf(txtData + byteCount, "// NOTE: No glyphs.image data provided\n"); + byteCount += sprintf(txtData + byteCount, "static GlyphInfo fontGlyphs_%s[%i] = {\n", fileNamePascal, font.glyphCount); + for (int i = 0; i < font.glyphCount; i++) + { + byteCount += sprintf(txtData + byteCount, " { %i, %i, %i, %i, { 0 }},\n", font.glyphs[i].value, font.glyphs[i].offsetX, font.glyphs[i].offsetY, font.glyphs[i].advanceX); + } + byteCount += sprintf(txtData + byteCount, "};\n\n"); + + // Custom font loading function + byteCount += sprintf(txtData + byteCount, "// Font loading function: %s\n", fileNamePascal); + byteCount += sprintf(txtData + byteCount, "static Font LoadFont_%s(void)\n{\n", fileNamePascal); + byteCount += sprintf(txtData + byteCount, " Font font = { 0 };\n\n"); + byteCount += sprintf(txtData + byteCount, " font.baseSize = %i;\n", font.baseSize); + byteCount += sprintf(txtData + byteCount, " font.glyphCount = %i;\n", font.glyphCount); + byteCount += sprintf(txtData + byteCount, " font.glyphPadding = %i;\n\n", font.glyphPadding); + byteCount += sprintf(txtData + byteCount, " // Custom font loading\n"); +#if defined(SUPPORT_COMPRESSED_FONT_ATLAS) + byteCount += sprintf(txtData + byteCount, " // NOTE: Compressed font image data (DEFLATE), it requires DecompressData() function\n"); + byteCount += sprintf(txtData + byteCount, " int fontDataSize_%s = 0;\n", fileNamePascal); + byteCount += sprintf(txtData + byteCount, " unsigned char *data = DecompressData(fontData_%s, COMPRESSED_DATA_SIZE_FONT_%s, &fontDataSize_%s);\n", fileNamePascal, TextToUpper(fileNamePascal), fileNamePascal); + byteCount += sprintf(txtData + byteCount, " Image imFont = { data, %i, %i, 1, %i };\n\n", image.width, image.height, image.format); +#else + byteCount += sprintf(txtData + byteCount, " Image imFont = { fontImageData_%s, %i, %i, 1, %i };\n\n", styleName, image.width, image.height, image.format); +#endif + byteCount += sprintf(txtData + byteCount, " // Load texture from image\n"); + byteCount += sprintf(txtData + byteCount, " font.texture = LoadTextureFromImage(imFont);\n"); +#if defined(SUPPORT_COMPRESSED_FONT_ATLAS) + byteCount += sprintf(txtData + byteCount, " UnloadImage(imFont); // Uncompressed data can be unloaded from memory\n\n"); +#endif + // There are two possible mechanisms to assign font.recs and font.glyphs data, + // that data is already available as global arrays, two options to assign that data: + // - 1. Data copy. This option consumes more memory and Font MUST be unloaded by user, requiring additional code + // - 2. Data assignment. This option consumes less memory and Font MUST NOT be unloaded by user because data is on protected DATA segment +//#define SUPPORT_FONT_DATA_COPY +#if defined(SUPPORT_FONT_DATA_COPY) + byteCount += sprintf(txtData + byteCount, " // Copy glyph recs data from global fontRecs\n"); + byteCount += sprintf(txtData + byteCount, " // NOTE: Required to avoid issues if trying to free font\n"); + byteCount += sprintf(txtData + byteCount, " font.recs = (Rectangle *)malloc(font.glyphCount*sizeof(Rectangle));\n"); + byteCount += sprintf(txtData + byteCount, " memcpy(font.recs, fontRecs_%s, font.glyphCount*sizeof(Rectangle));\n\n", fileNamePascal); + + byteCount += sprintf(txtData + byteCount, " // Copy font glyph info data from global fontChars\n"); + byteCount += sprintf(txtData + byteCount, " // NOTE: Required to avoid issues if trying to free font\n"); + byteCount += sprintf(txtData + byteCount, " font.glyphs = (GlyphInfo *)malloc(font.glyphCount*sizeof(GlyphInfo));\n"); + byteCount += sprintf(txtData + byteCount, " memcpy(font.glyphs, fontGlyphs_%s, font.glyphCount*sizeof(GlyphInfo));\n\n", fileNamePascal); +#else + byteCount += sprintf(txtData + byteCount, " // Assign glyph recs and info data directly\n"); + byteCount += sprintf(txtData + byteCount, " // WARNING: This font data must not be unloaded\n"); + byteCount += sprintf(txtData + byteCount, " font.recs = fontRecs_%s;\n", fileNamePascal); + byteCount += sprintf(txtData + byteCount, " font.glyphs = fontGlyphs_%s;\n\n", fileNamePascal); +#endif + byteCount += sprintf(txtData + byteCount, " return font;\n"); + byteCount += sprintf(txtData + byteCount, "}\n"); + + UnloadImage(image); + + // NOTE: Text data size exported is determined by '\0' (NULL) character + result = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + if (result != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Font as code exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export font as code", fileName); + + return result; +} + +// Draw current FPS +// NOTE: Uses default font +void DrawFPS(int posX, int posY) +{ + Color color = LIME; // Good FPS + int fps = GetFPS(); + + if ((fps < 30) && (fps >= 15)) color = ORANGE; // Warning FPS + else if (fps < 15) color = RED; // Low FPS + + DrawText(TextFormat("%2i FPS", fps), posX, posY, 20, color); +} + +// Draw text (using default font) +// NOTE: fontSize work like in any drawing program but if fontSize is lower than font-base-size, then font-base-size is used +// NOTE: chars spacing is proportional to fontSize +void DrawText(const char *text, int posX, int posY, int fontSize, Color color) +{ + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) + { + Vector2 position = { (float)posX, (float)posY }; + + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + DrawTextEx(GetFontDefault(), text, position, (float)fontSize, (float)spacing, color); + } +} + +// Draw text using Font +// NOTE: chars spacing is NOT proportional to fontSize +void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) +{ + if (font.texture.id == 0) font = GetFontDefault(); // Security check in case of not valid font + + int size = TextLength(text); // Total size in bytes of the text, scanned by codepoints in loop + + float textOffsetY = 0; // Offset between lines (on linebreak '\n') + float textOffsetX = 0.0f; // Offset X to next character to draw + + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + + for (int i = 0; i < size;) + { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetCodepointNext(&text[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); + + if (codepoint == '\n') + { + // NOTE: Line spacing is a global variable, use SetTextLineSpacing() to setup + textOffsetY += (fontSize + textLineSpacing); + textOffsetX = 0.0f; + } + else + { + if ((codepoint != ' ') && (codepoint != '\t')) + { + DrawTextCodepoint(font, codepoint, (Vector2){ position.x + textOffsetX, position.y + textOffsetY }, fontSize, tint); + } + + if (font.glyphs[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing); + else textOffsetX += ((float)font.glyphs[index].advanceX*scaleFactor + spacing); + } + + i += codepointByteCount; // Move text bytes counter to next codepoint + } +} + +// Draw text using Font and pro parameters (rotation) +void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint) +{ + rlPushMatrix(); + + rlTranslatef(position.x, position.y, 0.0f); + rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + rlTranslatef(-origin.x, -origin.y, 0.0f); + + DrawTextEx(font, text, (Vector2){ 0.0f, 0.0f }, fontSize, spacing, tint); + + rlPopMatrix(); +} + +// Draw one character (codepoint) +void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint) +{ + // Character index position in sprite font + // NOTE: In case a codepoint is not available in the font, index returned points to '?' + int index = GetGlyphIndex(font, codepoint); + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + + // Character destination rectangle on screen + // NOTE: Considering glyph padding on drawing + Rectangle dstRec = { position.x + font.glyphs[index].offsetX*scaleFactor - (float)font.glyphPadding*scaleFactor, + position.y + font.glyphs[index].offsetY*scaleFactor - (float)font.glyphPadding*scaleFactor, + (font.recs[index].width + 2.0f*font.glyphPadding)*scaleFactor, + (font.recs[index].height + 2.0f*font.glyphPadding)*scaleFactor }; + + // Character source rectangle from font texture atlas + // NOTE: Considering glyphs padding when drawing, it could be required for outline/glow shader effects + Rectangle srcRec = { font.recs[index].x - (float)font.glyphPadding, font.recs[index].y - (float)font.glyphPadding, + font.recs[index].width + 2.0f*font.glyphPadding, font.recs[index].height + 2.0f*font.glyphPadding }; + + // Draw the character texture on the screen + DrawTexturePro(font.texture, srcRec, dstRec, (Vector2){ 0, 0 }, 0.0f, tint); +} + +// Draw multiple characters (codepoints) +void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint) +{ + float textOffsetY = 0; // Offset between lines (on linebreak '\n') + float textOffsetX = 0.0f; // Offset X to next character to draw + + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + + for (int i = 0; i < codepointCount; i++) + { + int index = GetGlyphIndex(font, codepoints[i]); + + if (codepoints[i] == '\n') + { + // NOTE: Line spacing is a global variable, use SetTextLineSpacing() to setup + textOffsetY += (fontSize + textLineSpacing); + textOffsetX = 0.0f; + } + else + { + if ((codepoints[i] != ' ') && (codepoints[i] != '\t')) + { + DrawTextCodepoint(font, codepoints[i], (Vector2){ position.x + textOffsetX, position.y + textOffsetY }, fontSize, tint); + } + + if (font.glyphs[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing); + else textOffsetX += ((float)font.glyphs[index].advanceX*scaleFactor + spacing); + } + } +} + +// Set vertical line spacing when drawing with line-breaks +void SetTextLineSpacing(int spacing) +{ + textLineSpacing = spacing; +} + +// Measure string width for default font +int MeasureText(const char *text, int fontSize) +{ + Vector2 textSize = { 0.0f, 0.0f }; + + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) + { + int defaultFontSize = 10; // Default Font glyphs height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + textSize = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing); + } + + return (int)textSize.x; +} + +// Measure string size for Font +Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing) +{ + Vector2 textSize = { 0 }; + + if ((font.texture.id == 0) || (text == NULL) || (text[0] == '\0')) return textSize; // Security check + + int size = TextLength(text); // Get size in bytes of text + int tempByteCounter = 0; // Used to count longer text line num chars + int byteCounter = 0; + + float textWidth = 0.0f; + float tempTextWidth = 0.0f; // Used to count longer text line width + + float textHeight = fontSize; + float scaleFactor = fontSize/(float)font.baseSize; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + for (int i = 0; i < size;) + { + byteCounter++; + + int codepointByteCount = 0; + letter = GetCodepointNext(&text[i], &codepointByteCount); + index = GetGlyphIndex(font, letter); + + i += codepointByteCount; + + if (letter != '\n') + { + if (font.glyphs[index].advanceX > 0) textWidth += font.glyphs[index].advanceX; + else textWidth += (font.recs[index].width + font.glyphs[index].offsetX); + } + else + { + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + byteCounter = 0; + textWidth = 0; + + // NOTE: Line spacing is a global variable, use SetTextLineSpacing() to setup + textHeight += (fontSize + textLineSpacing); + } + + if (tempByteCounter < byteCounter) tempByteCounter = byteCounter; + } + + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + + textSize.x = tempTextWidth*scaleFactor + (float)((tempByteCounter - 1)*spacing); + textSize.y = textHeight; + + return textSize; +} + +// Measure string size for an existing array of codepoints for Font +Vector2 MeasureTextCodepoints(Font font, const int *codepoints, int length, float fontSize, float spacing) +{ + Vector2 textSize = { 0 }; + + if ((font.texture.id == 0) || (codepoints == NULL) || (length == 0)) return textSize; // Security check + + float textWidth = 0.0f; + // Used to count longer text line width + float tempTextWidth = 0.0f; + + // Used to count longer text line num chars + int tempGlyphCounter = 0; + int glyphCounter = 0; + + float textHeight = fontSize; + float scaleFactor = fontSize/(float)font.baseSize; + + // Current character + int letter = 0; + // Index position in sprite font + int index = 0; + + for (int i = 0; i < length; i++) + { + letter = codepoints[i]; + index = GetGlyphIndex(font, letter); + + if (letter != '\n') + { + glyphCounter++; + + if (font.glyphs[index].advanceX > 0) textWidth += font.glyphs[index].advanceX; + else textWidth += (font.recs[index].width + font.glyphs[index].offsetX); + } + else + { + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + + textWidth = 0; + glyphCounter = 0; + + // NOTE: Line spacing is a global variable, use SetTextLineSpacing() to setup + textHeight += (fontSize + textLineSpacing); + } + + if (tempGlyphCounter < glyphCounter) tempGlyphCounter = glyphCounter; + } + + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + + textSize.x = tempTextWidth*scaleFactor + (float)((tempGlyphCounter - 1)*spacing); + textSize.y = textHeight; + + return textSize; +} + +// Get index position for a unicode character on font +// NOTE: If codepoint is not found in the font it fallbacks to '?' +int GetGlyphIndex(Font font, int codepoint) +{ + int index = 0; + if (!IsFontValid(font)) return index; + +#define SUPPORT_UNORDERED_CHARSET +#if defined(SUPPORT_UNORDERED_CHARSET) + int fallbackIndex = 0; // Get index of fallback glyph '?' + + // Look for character index in the unordered charset + for (int i = 0; i < font.glyphCount; i++) + { + if (font.glyphs[i].value == 63) fallbackIndex = i; + + if (font.glyphs[i].value == codepoint) + { + index = i; + break; + } + } + + if ((index == 0) && (font.glyphs[0].value != codepoint)) index = fallbackIndex; +#else + index = codepoint - 32; +#endif + + return index; +} + +// Get glyph font info data for a codepoint (unicode character) +// NOTE: If codepoint is not found in the font it fallbacks to '?' +GlyphInfo GetGlyphInfo(Font font, int codepoint) +{ + GlyphInfo info = { 0 }; + + info = font.glyphs[GetGlyphIndex(font, codepoint)]; + + return info; +} + +// Get glyph rectangle in font atlas for a codepoint (unicode character) +// NOTE: If codepoint is not found in the font it fallbacks to '?' +Rectangle GetGlyphAtlasRec(Font font, int codepoint) +{ + Rectangle rec = { 0 }; + + rec = font.recs[GetGlyphIndex(font, codepoint)]; + + return rec; +} + +//---------------------------------------------------------------------------------- +// Text strings management functions +//---------------------------------------------------------------------------------- +// Load text as separate lines ('\n') +// NOTE: Returned lines end with null terminator '\0' +char **LoadTextLines(const char *text, int *count) +{ + char **lines = NULL; + int lineCount = 0; + + if (text != NULL) + { + int textLength = TextLength(text); + lineCount = 1; + + // First text scan pass to get required line count + for (int i = 0; i < textLength; i++) + { + if (text[i] == '\n') lineCount++; + } + + lines = (char **)RL_CALLOC(lineCount, sizeof(char *)); + for (int i = 0, l = 0, lineLen = 0; i <= textLength; i++) + { + if ((text[i] == '\n') || (text[i] == '\0')) + { + lines[l] = (char *)RL_CALLOC(lineLen + 1, 1); + memcpy(lines[l], &text[i - lineLen], lineLen); + lineLen = 0; + l++; + } + else lineLen++; + } + } + + *count = lineCount; + return lines; +} + +// Unload text lines +void UnloadTextLines(char **lines, int lineCount) +{ + for (int i = 0; i < lineCount; i++) RL_FREE(lines[i]); + RL_FREE(lines); +} + +// Get text length in bytes, check for \0 character +// NOTE: Alternative: use strlen(text) +unsigned int TextLength(const char *text) +{ + unsigned int length = 0; + + if (text != NULL) + { + while (text[length] != '\0') length++; + } + + return length; +} + +// Formatting of text with variables to 'embed' +// WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times +const char *TextFormat(const char *text, ...) +{ +#ifndef MAX_TEXTFORMAT_BUFFERS + #define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting +#endif + + // Create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations + static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 }; + static int index = 0; + + char *currentBuffer = buffers[index]; + memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using + + if (text != NULL) + { + va_list args; + va_start(args, text); + int requiredByteCount = vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); + va_end(args); + + // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occurred + if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) + { + // Inserting "..." at the end of the string to mark as truncated + char *truncBuffer = buffers[index] + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0" + snprintf(truncBuffer, 4, "..."); + } + + index += 1; // Move to next buffer for next function call + if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; + } + + return currentBuffer; +} + +// Get integer value from text +// NOTE: This function replaces atoi() [stdlib.h] +int TextToInteger(const char *text) +{ + int value = 0; + int sign = 1; + + if (text != NULL) + { + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1; + text++; + } + + for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10 + (int)(text[i] - '0'); + } + + return value*sign; +} + +// Get float value from text +// NOTE: This function replaces atof() [stdlib.h] +// WARNING: Only '.' character is understood as decimal point +float TextToFloat(const char *text) +{ + float value = 0.0f; + float sign = 1.0f; + + if (text != NULL) + { + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1.0f; + text++; + } + + int i = 0; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); + + if (text[i++] == '.') + { + float divisor = 10.0f; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) + { + value += ((float)(text[i] - '0'))/divisor; + divisor = divisor*10.0f; + } + } + } + + return value*sign; +} + +// Copy one string to another, returns bytes copied +// NOTE: Alternative implementation to strcpy(dst, src) from C standard library +int TextCopy(char *dst, const char *src) +{ + int bytes = 0; + + if ((src != NULL) && (dst != NULL)) + { + while (*src != '\0') + { + *dst = *src; + dst++; + src++; + + bytes++; + } + + *dst = '\0'; + } + + return bytes; +} + +// Check if two text strings are equal +// REQUIRES: strcmp() +bool TextIsEqual(const char *text1, const char *text2) +{ + bool result = false; + + if ((text1 != NULL) && (text2 != NULL)) + { + if (strcmp(text1, text2) == 0) result = true; + } + + return result; +} + +// Get a piece of a text string +const char *TextSubtext(const char *text, int position, int length) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + if ((text != NULL) && (position >= 0) && (length > 0)) + { + int textLength = TextLength(text); + + if (position < textLength) + { + int maxLength = textLength - position; + if (length > maxLength) length = maxLength; + if (length >= MAX_TEXT_BUFFER_LENGTH) length = MAX_TEXT_BUFFER_LENGTH - 1; + + // NOTE: Alternative: memcpy(buffer, text + position, length) + + for (int c = 0; c < length; c++) buffer[c] = text[position + c]; + + buffer[length] = '\0'; + } + } + + return buffer; +} + +// Remove text spaces, concat words +const char *TextRemoveSpaces(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + if (text != NULL) + { + // Avoid copying the ' ' characters + for (int i = 0, j = 0; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[i] != '\0'); i++) + { + if (text[i] != ' ') { buffer[j] = text[i]; j++; } + } + } + + return buffer; +} + +// Get text between two strings +char *GetTextBetween(const char *text, const char *begin, const char *end) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + int beginIndex = TextFindIndex(text, begin); + + if (beginIndex > -1) + { + int beginLen = TextLength(begin); + int endIndex = TextFindIndex(text + beginIndex + beginLen, end); + + if (endIndex > -1) + { + endIndex += (beginIndex + beginLen); + int len = (endIndex - beginIndex - beginLen); + if (len < (MAX_TEXT_BUFFER_LENGTH - 1)) memcpy(buffer, text + beginIndex + beginLen, len); + else snprintf(buffer, MAX_TEXT_BUFFER_LENGTH, "%s", text + beginIndex + beginLen); + } + } + + return buffer; +} + +// Replace text string +// REQUIRES: strstr(), memcpy() +// NOTE: Limited text replace functionality, using static string +char *TextReplace(const char *text, const char *search, const char *replacement) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + if ((text != NULL) && (search != NULL) && (search[0] != '\0')) + { + if (replacement == NULL) replacement = ""; + + char *insertPoint = NULL; // Next insert point + char *tempPtr = NULL; // Temp pointer + int textLen = 0; // Text string length + int searchLen = 0; // Search string length of (the string to remove) + int replaceLen = 0; // Replacement length (the string to replace by) + int lastReplacePos = 0; // Distance between next search and end of last replace + int count = 0; // Number of replacements + + textLen = TextLength(text); + searchLen = TextLength(search); + replaceLen = TextLength(replacement); + + // Count the number of replacements needed + insertPoint = (char *)text; + for (count = 0; (tempPtr = strstr(insertPoint, search)); count++) insertPoint = tempPtr + searchLen; + + if ((textLen + count*(replaceLen - searchLen)) < (MAX_TEXT_BUFFER_LENGTH - 1)) + { + // TODO: Allow copying data replaced up to maximum buffer size and stop + + tempPtr = buffer; // Point to result start + + // First time through the loop, all the variable are set correctly from here on, + // - 'temp' points to the end of the result string + // - 'insertPoint' points to the next occurrence of replace in text + // - 'text' points to the remainder of text after "end of replace" + while (count > 0) + { + insertPoint = (char *)strstr(text, search); + lastReplacePos = (int)(insertPoint - text); + + memcpy(tempPtr, text, lastReplacePos); + tempPtr += lastReplacePos; + + if (replaceLen > 0) + { + memcpy(tempPtr, replacement, replaceLen); + tempPtr += replaceLen; + } + + text += (lastReplacePos + searchLen); // Move to next "end of replace" + count--; + } + + // Copy remaind text part after replacement to result (pointed by moving temp) + // NOTE: Text pointer internal copy has been updated along the process + memcpy(tempPtr, text, TextLength(text)); + } + else TRACELOG(LOG_WARNING, "Text with replacement is longer than internal buffer, use TextReplaceAlloc()"); + } + + return buffer; +} + +// Replace text string +// REQUIRES: strstr(), memcpy() +// WARNING: Allocated memory must be manually freed +char *TextReplaceAlloc(const char *text, const char *search, const char *replacement) +{ + char *result = NULL; + + if ((text != NULL) && (search != NULL) && (search[0] != '\0')) + { + if (replacement == NULL) replacement = ""; + + char *insertPoint = NULL; // Next insert point + char *temp = NULL; // Temp pointer + int textLen = 0; // Text string length + int searchLen = 0; // Search string length of (the string to remove) + int replaceLen = 0; // Replacement length (the string to replace by) + int lastReplacePos = 0; // Distance between next search and end of last replace + int count = 0; // Number of replacements + + textLen = TextLength(text); + searchLen = TextLength(search); + replaceLen = TextLength(replacement); + + // Count the number of replacements needed + insertPoint = (char *)text; + for (count = 0; (temp = strstr(insertPoint, search)); count++) insertPoint = temp + searchLen; + + // Allocate returning string and point temp to it + int tempLen = textLen + (replaceLen - searchLen)*count + 1; + temp = result = (char *)RL_CALLOC(tempLen, sizeof(char)); + + if (result != NULL) // Memory was allocated + { + // First time through the loop, all the variable are set correctly from here on, + // - 'temp' points to the end of the result string + // - 'insertPoint' points to the next occurrence of replace in text + // - 'text' points to the remainder of text after "end of replace" + while (count > 0) + { + insertPoint = (char *)strstr(text, search); + lastReplacePos = (int)(insertPoint - text); + + memcpy(temp, text, lastReplacePos); + temp += lastReplacePos; + + if (replaceLen > 0) + { + memcpy(temp, replacement, replaceLen); + temp += replaceLen; + } + + text += (lastReplacePos + searchLen); // Move to next "end of replace" + + count--; + } + + // Copy remaind text part after replacement to result (pointed by moving temp) + // NOTE: Text pointer internal copy has been updated along the process + memcpy(temp, text, TextLength(text)); + } + } + + return result; +} + +// Replace text between two specific strings +// REQUIRES: strncpy() +// NOTE: If (replacement == NULL) removes "begin"[ ]"end" text +char *TextReplaceBetween(const char *text, const char *begin, const char *end, const char *replacement) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + if ((text != NULL) && (begin != NULL) && (end != NULL)) + { + int beginIndex = TextFindIndex(text, begin); + + if (beginIndex > -1) + { + int beginLen = TextLength(begin); + int endIndex = TextFindIndex(text + beginIndex + beginLen, end); + + if (endIndex > -1) + { + endIndex += (beginIndex + beginLen); + + int textLen = TextLength(text); + int replaceLen = (replacement == NULL)? 0 : TextLength(replacement); + //int toreplaceLen = endIndex - beginIndex - beginLen; + + if ((beginIndex + beginLen + replaceLen + (textLen - endIndex)) < (MAX_TEXT_BUFFER_LENGTH - 1)) + { + strncpy(buffer, text, beginIndex + beginLen); // Copy first text part + if (replacement != NULL) strncpy(buffer + beginIndex + beginLen, replacement, replaceLen); // Copy replacement (if provided) + strncpy(buffer + beginIndex + beginLen + replaceLen, text + endIndex, textLen - endIndex); // Copy end text part + } + else TRACELOG(LOG_WARNING, "TEXT: Text with replaced string is longer than internal buffer (MAX_TEXT_BUFFER_LENGTH)"); + } + } + } + + return buffer; +} + +// Replace text between two specific strings +// REQUIRES: strncpy() +// NOTE: If (replacement == NULL) remove "begin"[ ]"end" text +// WARNING: Returned string must be freed by user +char *TextReplaceBetweenAlloc(const char *text, const char *begin, const char *end, const char *replacement) +{ + char *result = NULL; + + if ((text != NULL) && (begin != NULL) && (end != NULL)) + { + int beginIndex = TextFindIndex(text, begin); + + if (beginIndex > -1) + { + int beginLen = TextLength(begin); + int endIndex = TextFindIndex(text + beginIndex + beginLen, end); + + if (endIndex > -1) + { + endIndex += (beginIndex + beginLen); + + int textLen = TextLength(text); + int replaceLen = (replacement == NULL)? 0 : TextLength(replacement); + int toreplaceLen = endIndex - beginIndex - beginLen; + result = (char *)RL_CALLOC(textLen + replaceLen - toreplaceLen + 1, sizeof(char)); + + strncpy(result, text, beginIndex + beginLen); // Copy first text part + if (replacement != NULL) strncpy(result + beginIndex + beginLen, replacement, replaceLen); // Copy replacement (if provided) + strncpy(result + beginIndex + beginLen + replaceLen, text + endIndex, textLen - endIndex); // Copy end text part + } + } + } + + return result; +} + +// Insert text in a specific position, moves all text forward +// WARNING: Allocated memory must be manually freed +char *TextInsert(const char *text, const char *insert, int position) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + int textLen = TextLength(text); + + if ((text != NULL) && (insert != NULL) && (position >= 0)) + { + if (position > textLen) position = textLen; // End of text string + int insertLen = TextLength(insert); + + if ((textLen + insertLen) < (MAX_TEXT_BUFFER_LENGTH - 1)) + { + // TODO: Allow copying data inserted up to maximum buffer size and stop + + for (int i = 0; i < position; i++) buffer[i] = text[i]; + for (int i = 0; i < insertLen; i++) buffer[i+position] = insert[i]; + for (int i = position; i < textLen; i++) buffer[i+insertLen] = text[i]; + + buffer[textLen + insertLen] = '\0'; // Add EOL + } + else TRACELOG(LOG_WARNING, "Text with inserted string is longer than internal buffer, use TextInserExt()"); + } + + return buffer; +} + +// Insert text in a specific position, moves all text forward +// WARNING: Allocated memory must be manually freed +char *TextInsertAlloc(const char *text, const char *insert, int position) +{ + char *result = NULL; + int textLen = TextLength(text); + + if ((text != NULL) && (insert != NULL) && (position >= 0)) + { + if (position > textLen) position = textLen; // End of text string + int insertLen = TextLength(insert); + + result = (char *)RL_MALLOC(textLen + insertLen + 1); + + for (int i = 0; i < position; i++) result[i] = text[i]; + for (int i = 0; i < insertLen; i++) result[i+position] = insert[i]; + for (int i = position; i < textLen; i++) result[i+insertLen] = text[i]; + + result[textLen + insertLen] = '\0'; // Add EOL + } + + return result; +} + +// Join text strings with delimiter +// REQUIRES: memset(), memcpy() +char *TextJoin(char **textList, int count, const char *delimiter) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + char *textPtr = buffer; + + int totalLength = 0; + int delimiterLen = TextLength(delimiter); + + for (int i = 0; i < count; i++) + { + int textLength = TextLength(textList[i]); + + // Make sure joined text could fit inside MAX_TEXT_BUFFER_LENGTH + if ((totalLength + textLength + delimiterLen) < MAX_TEXT_BUFFER_LENGTH) + { + memcpy(textPtr, textList[i], textLength); + totalLength += textLength; + textPtr += textLength; + + if ((delimiterLen > 0) && (i < (count - 1))) + { + memcpy(textPtr, delimiter, delimiterLen); + totalLength += delimiterLen; + textPtr += delimiterLen; + } + } + } + + return buffer; +} + +// Split string into multiple strings +// REQUIRES: memset() +char **TextSplit(const char *text, char delimiter, int *count) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by MAX_TEXTSPLIT_COUNT + // 2. Maximum size of text to split is MAX_TEXT_BUFFER_LENGTH + + static char *buffers[MAX_TEXTSPLIT_COUNT] = { NULL }; // Pointers to buffer[] text data + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; // Text data with '\0' separators + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + buffers[0] = buffer; + int counter = 0; + + if (text != NULL) + { + counter = 1; + + // Count how many substrings ar found on text and set pointers to every one + // NOTE: Last buffer byte is reserved to terminate the last substring + for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH - 1; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if (buffer[i] == delimiter) + { + buffer[i] = '\0'; // Set an end of string at this point + buffers[counter] = buffer + i + 1; + counter++; + + if (counter == MAX_TEXTSPLIT_COUNT) break; + } + } + } + + *count = counter; + return buffers; +} + +// Append text at specific position and move cursor +// WARNING: It's up to the user to make sure appended text does not overflow the buffer! +void TextAppend(char *text, const char *append, int *position) +{ + if ((text != NULL) && (append != NULL)) + { + TextCopy(text + *position, append); + *position += TextLength(append); + } +} + +// Find first text occurrence within a string +// REQUIRES: strstr() +int TextFindIndex(const char *text, const char *search) +{ + int position = -1; + + if (text != NULL) + { + char *ptr = (char *)strstr(text, search); + + if (ptr != NULL) position = (int)(ptr - text); + } + + return position; +} + +// Get upper case version of provided string +// WARNING: Limited functionality, only basic characters set +// TODO: Support UTF-8 diacritics to upper-case, check codepoints +char *TextToUpper(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + if (text != NULL) + { + for (int i = 0; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[i] != '\0'); i++) + { + if ((text[i] >= 'a') && (text[i] <= 'z')) buffer[i] = text[i] - 32; + else buffer[i] = text[i]; + } + } + + return buffer; +} + +// Get lower case version of provided string +// WARNING: Limited functionality, only basic characters set +char *TextToLower(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + if (text != NULL) + { + for (int i = 0; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[i] != '\0'); i++) + { + if ((text[i] >= 'A') && (text[i] <= 'Z')) buffer[i] = text[i] + 32; + else buffer[i] = text[i]; + } + } + + return buffer; +} + +// Get Pascal case notation version of provided string +// WARNING: Limited functionality, only basic characters set +char *TextToPascal(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + if (text != NULL) + { + // Upper case first character + if ((text[0] >= 'a') && (text[0] <= 'z')) buffer[0] = text[0] - 32; + else buffer[0] = text[0]; + + // Check for next separator to upper case another character + for (int i = 1, j = 1; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[j] != '\0'); i++, j++) + { + if (text[j] != '_') buffer[i] = text[j]; + else + { + while (text[j] == '_') j++; // Skip one or more separators + if (text[j] == '\0') break; // Text ends on a separator, nothing left to copy + + if ((text[j] >= 'a') && (text[j] <= 'z')) buffer[i] = text[j] - 32; + else buffer[i] = text[j]; // Character can not be upper-cased, copy it as is + } + } + } + + return buffer; +} + +// Get snake case notation version of provided string +// WARNING: Limited functionality, only basic characters set +char *TextToSnake(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + if (text != NULL) + { + // Check for next separator to upper case another character + for (int i = 0, j = 0; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[j] != '\0'); j++) + { + if (text[j] == ' ') + { + if ((i > 0) && (buffer[i - 1] != '_')) + { + buffer[i] = '_'; + i++; + } + } + else if ((text[j] >= 'A') && (text[j] <= 'Z')) + { + if ((i > 0) && (buffer[i - 1] != '_')) + { + char prev = text[j - 1]; + char next = text[j + 1]; + + // Considering multiple cap leters to be on single word (HTTPRequest --> http_request) + if (((prev >= 'a') && (prev <= 'z')) || + (((prev >= 'A') && (prev <= 'Z')) && ((next >= 'a') && (next <= 'z')))) + { + if (i < MAX_TEXT_BUFFER_LENGTH - 2) + { + buffer[i] = '_'; + i++; + } + } + } + + buffer[i] = text[j] + 32; + i++; + } + else + { + buffer[i] = text[j]; + i++; + } + } + } + + return buffer; +} + +// Get Camel case notation version of provided string +// WARNING: Limited functionality, only basic characters set +char *TextToCamel(const char *text) +{ + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); + + if (text != NULL) + { + // Lower case first character + if ((text[0] >= 'A') && (text[0] <= 'Z')) buffer[0] = text[0] + 32; + else buffer[0] = text[0]; + + // Check for next separator to upper case another character + for (int i = 1, j = 1; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[j] != '\0'); i++, j++) + { + if (text[j] != '_') buffer[i] = text[j]; + else + { + while (text[j] == '_') j++; // Skip one or more separators + if (text[j] == '\0') break; // Text ends on a separator, nothing left to copy + + if ((text[j] >= 'a') && (text[j] <= 'z')) buffer[i] = text[j] - 32; + else buffer[i] = text[j]; // Character can not be upper-cased, copy it as is + } + } + } + + return buffer; +} + +// Encode text codepoint into UTF-8 text +// REQUIRES: memcpy() +// WARNING: Allocated memory must be manually freed +char *LoadUTF8(const int *codepoints, int length) +{ + char *text = NULL; + + if ((codepoints != NULL) && (length > 0)) + { + // Allocate enough memory to fit all possible codepoints + // NOTE: 5 bytes for every codepoint should be enough + text = (char *)RL_CALLOC(length*5, 1); + const char *utf8 = NULL; + int size = 0; + + for (int i = 0, bytes = 0; i < length; i++) + { + utf8 = CodepointToUTF8(codepoints[i], &bytes); + memcpy(text + size, utf8, bytes); + size += bytes; + } + + // Create second buffer and copy data manually to it + char *temp = (char *)RL_CALLOC(size + 1, 1); + memcpy(temp, text, size); + RL_FREE(text); + text = temp; + } + + return text; +} + +// Unload UTF-8 text encoded from codepoints array +void UnloadUTF8(char *text) +{ + RL_FREE(text); +} + +// Load all codepoints from a UTF-8 text string, codepoints count returned by parameter +int *LoadCodepoints(const char *text, int *count) +{ + int *codepoints = NULL; + int codepointCount = 0; + + if (text != NULL) + { + int textLength = TextLength(text); + + // Allocate a big enough buffer to store as many codepoints as text bytes + codepoints = (int *)RL_CALLOC(textLength, sizeof(int)); + + int codepointSize = 0; + for (int i = 0; i < textLength; codepointCount++) + { + codepoints[codepointCount] = GetCodepointNext(text + i, &codepointSize); + i += codepointSize; + } + + // Create second buffer and copy data manually to it + int *temp = (int *)RL_CALLOC(codepointCount, sizeof(int)); + for (int i = 0; i < codepointCount; i++) temp[i] = codepoints[i]; + RL_FREE(codepoints); + codepoints = temp; + } + + *count = codepointCount; + return codepoints; +} + +// Unload codepoints data from memory +void UnloadCodepoints(int *codepoints) +{ + RL_FREE(codepoints); +} + +// Get total number of characters(codepoints) in a UTF-8 encoded text, until '\0' is found +// NOTE: If an invalid UTF-8 sequence is encountered a '?'(0x3f) codepoint is counted instead +int GetCodepointCount(const char *text) +{ + unsigned int length = 0; + const char *ptr = text; + + if (ptr != NULL) + { + while (*ptr != '\0') + { + int next = 0; + GetCodepointNext(ptr, &next); + ptr += next; + length++; + } + } + + return length; +} + +// Encode codepoint into utf8 text (char array length returned as parameter) +// NOTE: It uses a static array to store UTF-8 bytes +const char *CodepointToUTF8(int codepoint, int *utf8Size) +{ + static char utf8[6] = { 0 }; + memset(utf8, 0, 6); // Clear static array + int size = 0; // Byte size of codepoint + + if (codepoint <= 0x7f) + { + utf8[0] = (char)codepoint; + size = 1; + } + else if (codepoint <= 0x7ff) + { + utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); + utf8[1] = (char)((codepoint & 0x3f) | 0x80); + size = 2; + } + else if (codepoint <= 0xffff) + { + utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); + utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[2] = (char)((codepoint & 0x3f) | 0x80); + size = 3; + } + else if (codepoint <= 0x10ffff) + { + utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); + utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); + utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[3] = (char)((codepoint & 0x3f) | 0x80); + size = 4; + } + + *utf8Size = size; + + return utf8; +} + +// Get next codepoint in a UTF-8 encoded text, scanning until '\0' is found +// When an invalid UTF-8 byte is encountered, exit as soon as possible and a '?'(0x3f) codepoint is returned +// Total number of bytes processed are returned as a parameter +// NOTE: The standard says U+FFFD should be returned in case of errors +// but that character is not supported by the default font in raylib +int GetCodepoint(const char *text, int *codepointSize) +{ +/* + UTF-8 specs from https://www.ietf.org/rfc/rfc3629.txt + + Char. number range | UTF-8 octet sequence + (hexadecimal) | (binary) + --------------------+--------------------------------------------- + 0000 0000-0000 007F | 0xxxxxxx + 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx +*/ + + int codepoint = 0x3f; // Codepoint (defaults to '?') + *codepointSize = 1; + if (text == NULL) return codepoint; + + // NOTE: On decoding errors, return as soon as possible + int octet = (unsigned char)(text[0]); // The first UTF8 octet + + if (octet <= 0x7f) + { + // Only one octet (ASCII range x00-7F) + codepoint = text[0]; + } + else if ((octet & 0xe0) == 0xc0) + { + // Two octets + + // [0]xC2-DF [1]UTF8-tail(x80-BF) + unsigned char octet1 = text[1]; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *codepointSize = 2; return codepoint; } // Unexpected sequence + + if ((octet >= 0xc2) && (octet <= 0xdf)) + { + codepoint = ((octet & 0x1f) << 6) | (octet1 & 0x3f); + *codepointSize = 2; + } + } + else if ((octet & 0xf0) == 0xe0) + { + // Three octets + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *codepointSize = 2; return codepoint; } // Unexpected sequence + + octet2 = text[2]; + + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *codepointSize = 3; return codepoint; } // Unexpected sequence + + // [0]xE0 [1]xA0-BF [2]UTF8-tail(x80-BF) + // [0]xE1-EC [1]UTF8-tail [2]UTF8-tail(x80-BF) + // [0]xED [1]x80-9F [2]UTF8-tail(x80-BF) + // [0]xEE-EF [1]UTF8-tail [2]UTF8-tail(x80-BF) + + if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) || + ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f)))) { *codepointSize = 2; return codepoint; } + + if ((octet >= 0xe0) && (octet <= 0xef)) + { + codepoint = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f); + *codepointSize = 3; + } + } + else if ((octet & 0xf8) == 0xf0) + { + // Four octets + if (octet > 0xf4) return codepoint; + + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; + unsigned char octet3 = '\0'; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *codepointSize = 2; return codepoint; } // Unexpected sequence + + octet2 = text[2]; + + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *codepointSize = 3; return codepoint; } // Unexpected sequence + + octet3 = text[3]; + + if ((octet3 == '\0') || ((octet3 >> 6) != 2)) { *codepointSize = 4; return codepoint; } // Unexpected sequence + + // [0]xF0 [1]x90-BF [2]UTF8-tail [3]UTF8-tail + // [0]xF1-F3 [1]UTF8-tail [2]UTF8-tail [3]UTF8-tail + // [0]xF4 [1]x80-8F [2]UTF8-tail [3]UTF8-tail + + if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) || + ((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f)))) { *codepointSize = 2; return codepoint; } // Unexpected sequence + + if (octet >= 0xf0) + { + codepoint = ((octet & 0x7) << 18) | ((octet1 & 0x3f) << 12) | ((octet2 & 0x3f) << 6) | (octet3 & 0x3f); + *codepointSize = 4; + } + } + + if (codepoint > 0x10ffff) codepoint = 0x3f; // Codepoints after U+10ffff are invalid + + return codepoint; +} + +// Get next codepoint in a byte sequence and bytes processed +int GetCodepointNext(const char *text, int *codepointSize) +{ + const char *ptr = text; + int codepoint = 0x3f; // Codepoint (defaults to '?') + *codepointSize = 1; + if (text == NULL) return codepoint; + + // Get current codepoint and bytes processed + if (0xf0 == (0xf8 & ptr[0])) + { + // 4 byte UTF-8 codepoint + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } // 10xxxxxx checks + codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); + *codepointSize = 4; + } + else if (0xe0 == (0xf0 & ptr[0])) + { + // 3 byte UTF-8 codepoint */ + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } // 10xxxxxx checks + codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); + *codepointSize = 3; + } + else if (0xc0 == (0xe0 & ptr[0])) + { + // 2 byte UTF-8 codepoint + if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } // 10xxxxxx checks + codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); + *codepointSize = 2; + } + else if (0x00 == (0x80 & ptr[0])) + { + // 1 byte UTF-8 codepoint + codepoint = ptr[0]; + *codepointSize = 1; + } + + return codepoint; +} + +// Get previous codepoint in a byte sequence and bytes processed +int GetCodepointPrevious(const char *text, int *codepointSize) +{ + const char *ptr = text; + int codepoint = 0x3f; // Codepoint (defaults to '?') + *codepointSize = 1; + if (text == NULL) return codepoint; + + // Move to previous codepoint + do ptr--; + while (((0x80 & ptr[0]) != 0) && ((0xc0 & ptr[0]) == 0x80)); + + int cpSize = 0; + codepoint = GetCodepointNext(ptr, &cpSize); + if (codepoint != 0) *codepointSize = cpSize; + + return codepoint; +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +#if SUPPORT_FILEFORMAT_FNT || SUPPORT_FILEFORMAT_BDF +// Read a line from memory +// REQUIRES: memcpy() +// NOTE: Returns the number of bytes read +static int GetLine(const char *origin, char *buffer, int maxLength) +{ + int count = 0; + for (; count < maxLength - 1; count++) if (origin[count] == '\n') break; + memcpy(buffer, origin, count); + buffer[count] = '\0'; + return count; +} +#endif + +#if SUPPORT_FILEFORMAT_FNT +// Load a BMFont file (AngelCode font file) +// REQUIRES: strstr(), sscanf(), strrchr(), memcpy() +static Font LoadBMFont(const char *fileName) +{ + #define MAX_BUFFER_SIZE 256 + #define MAX_FONT_IMAGE_PAGES 8 + + Font font = { 0 }; + + char buffer[MAX_BUFFER_SIZE] = { 0 }; + char *searchPoint = NULL; + + int fontSize = 0; + int glyphCount = 0; + + int imWidth = 0; + int imHeight = 0; + int pageCount = 1; + char imFileName[MAX_FONT_IMAGE_PAGES][129] = { 0 }; + + int base = 0; // Useless data + int readBytes = 0; // Data bytes read + int readVars = 0; // Variables filled by sscanf() + + char *fileText = LoadFileText(fileName); + + if (fileText == NULL) return font; + + char *fileTextPtr = fileText; + + // NOTE: Skip first line, it contains no useful information + readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + fileTextPtr += (readBytes + 1); + + // Read line data + readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + searchPoint = strstr(buffer, "lineHeight"); + readVars = sscanf(searchPoint, "lineHeight=%i base=%i scaleW=%i scaleH=%i pages=%i", &fontSize, &base, &imWidth, &imHeight, &pageCount); + fileTextPtr += (readBytes + 1); + + if (readVars < 4) { UnloadFileText(fileText); return font; } // Some data not available, file malformed + + if (pageCount > MAX_FONT_IMAGE_PAGES) + { + TRACELOG(LOG_WARNING, "FONT: [%s] Font defines more pages than supported: %i/%i", fileName, pageCount, MAX_FONT_IMAGE_PAGES); + pageCount = MAX_FONT_IMAGE_PAGES; + } + + for (int i = 0; i < pageCount; i++) + { + readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + searchPoint = strstr(buffer, "file"); + readVars = sscanf(searchPoint, "file=\"%128[^\"]\"", imFileName[i]); + fileTextPtr += (readBytes + 1); + + if (readVars < 1) { UnloadFileText(fileText); return font; } // No fileName read + } + + readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + searchPoint = strstr(buffer, "count"); + readVars = sscanf(searchPoint, "count=%i", &glyphCount); + fileTextPtr += (readBytes + 1); + + if (readVars < 1) { UnloadFileText(fileText); return font; } // No glyphCount read + + // Load all required images for further compose + Image *imFonts = (Image *)RL_CALLOC(pageCount, sizeof(Image)); // Font atlases, multiple images + + for (int i = 0; i < pageCount; i++) + { + imFonts[i] = LoadImage(TextFormat("%s/%s", GetDirectoryPath(fileName), imFileName[i])); + + if (imFonts[i].format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel + Image imFontAlpha = { + .data = RL_CALLOC(imFonts[i].width*imFonts[i].height, 2), + .width = imFonts[i].width, + .height = imFonts[i].height, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA + }; + + for (int p = 0, pi = 0; p < (imFonts[i].width*imFonts[i].height*2); p += 2, pi++) + { + ((unsigned char *)(imFontAlpha.data))[p] = 0xff; + ((unsigned char *)(imFontAlpha.data))[p + 1] = ((unsigned char *)imFonts[i].data)[pi]; + } + + UnloadImage(imFonts[i]); + imFonts[i] = imFontAlpha; + } + } + + Image fullFont = imFonts[0]; + for (int i = 1; i < pageCount; i++) UnloadImage(imFonts[i]); + + // If multiple atlas, then merge atlas + // NOTE: WARNING: This process could be really slow! + if (pageCount > 1) + { + // Resize font atlas to draw additional images + ImageResizeCanvas(&fullFont, imWidth, imHeight*pageCount, 0, 0, BLACK); + + for (int i = 1; i < pageCount; i++) + { + Rectangle srcRec = { 0.0f, 0.0f, (float)imWidth, (float)imHeight }; + Rectangle dstRec = { 0.0f, (float)imHeight*(float)i, (float)imWidth, (float)imHeight }; + ImageDrawImagePro(&fullFont, imFonts[i], srcRec, dstRec, (Vector2){ 0 }, 0.0f, WHITE); + } + } + + RL_FREE(imFonts); + + font.texture = LoadTextureFromImage(fullFont); + + // Fill font characters info data + font.baseSize = fontSize; + font.glyphCount = glyphCount; + font.glyphPadding = 0; + font.glyphs = (GlyphInfo *)RL_MALLOC(glyphCount*sizeof(GlyphInfo)); + font.recs = (Rectangle *)RL_MALLOC(glyphCount*sizeof(Rectangle)); + + int charId = 0; + int charX = 0; + int charY = 0; + int charWidth = 0; + int charHeight = 0; + int charOffsetX = 0; + int charOffsetY = 0; + int charAdvanceX = 0; + int pageID = 0; + + for (int i = 0; i < glyphCount; i++) + { + readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + readVars = sscanf(buffer, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i page=%i", + &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX, &pageID); + fileTextPtr += (readBytes + 1); + + if (readVars == 9) // Make sure all char data has been properly read + { + // Get character rectangle in the font atlas texture + font.recs[i] = (Rectangle){ (float)charX, (float)charY + (float)imHeight*pageID, (float)charWidth, (float)charHeight }; + + // Save data properly in sprite font + font.glyphs[i].value = charId; + font.glyphs[i].offsetX = charOffsetX; + font.glyphs[i].offsetY = charOffsetY; + font.glyphs[i].advanceX = charAdvanceX; + + // Fill character image data from full font data + font.glyphs[i].image = ImageFromImage(fullFont, font.recs[i]); + } + else + { + font.glyphs[i].image = GenImageColor((int)font.recs[i].width, (int)font.recs[i].height, BLACK); + TRACELOG(LOG_WARNING, "FONT: [%s] Some characters data not correctly provided", fileName); + } + } + + UnloadImage(fullFont); + UnloadFileText(fileText); + + if (font.texture.id == 0) + { + UnloadFont(font); + font = GetFontDefault(); + TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load texture, reverted to default font", fileName); + } + else TRACELOG(LOG_INFO, "FONT: [%s] Font loaded successfully (%i glyphs)", fileName, font.glyphCount); + + return font; +} +#endif + +#if SUPPORT_FILEFORMAT_BDF +// Convert hexadecimal to decimal (single digit) +static unsigned char HexToInt(char hex) +{ + if ((hex >= '0') && (hex <= '9')) return hex - '0'; + else if ((hex >= 'a') && (hex <= 'f')) return hex - 'a' + 10; + else if ((hex >= 'A') && (hex <= 'F')) return hex - 'A' + 10; + else return 0; +} + +// Load font data for further use +// NOTE: Requires BDF font memory data +static GlyphInfo *LoadFontDataBDF(const unsigned char *fileData, int dataSize, const int *codepoints, int codepointCount, int *outFontSize) +{ + #define MAX_BUFFER_SIZE 256 + + char buffer[MAX_BUFFER_SIZE] = { 0 }; + + GlyphInfo *glyphs = NULL; + bool internalCodepoints = false; + + int totalReadBytes = 0; // Data bytes read (total) + int readBytes = 0; // Data bytes read (line) + int readVars = 0; // Variables filled by sscanf() + + const char *fileText = (const char *)fileData; + const char *fileTextPtr = fileText; + + bool fontMalformed = false; // Is the font malformed + bool fontStarted = false; // Has font started (STARTFONT) + int fontBBw = 0; // Font base character bounding box width + int fontBBh = 0; // Font base character bounding box height + int fontBBxoff0 = 0; // Font base character bounding box X0 offset + int fontBByoff0 = 0; // Font base character bounding box Y0 offset + int fontAscent = 0; // Font ascent + + bool charStarted = false; // Has character started (STARTCHAR) + bool charBitmapStarted = false; // Has bitmap data started (BITMAP) + int charBitmapNextRow = 0; // Y position for the next row of bitmap data + int charEncoding = -1; // The unicode value of the character (-1 if not set) + int charBBw = 0; // Character bounding box width + int charBBh = 0; // Character bounding box height + int charBBxoff0 = 0; // Character bounding box X0 offset + int charBByoff0 = 0; // Character bounding box Y0 offset + int charDWidthX = 0; // Character advance X + int charDWidthY = 0; // Character advance Y (unused) + + int *requiredCodepoints = (int *)RL_MALLOC(codepointCount*sizeof(int)); + + if (fileData == NULL) return glyphs; + + // In case no chars count provided, default to 95 + codepointCount = (codepointCount > 0)? codepointCount : 95; + + if (codepoints == NULL) + { + // Fill internal codepoints array in case not provided externally + // NOTE: By default, filling glyph count consecutively, starting at 32 (Space) + for (int i = 0; i < codepointCount; i++) requiredCodepoints[i] = i + 32; + internalCodepoints = true; + } + else + { + for (int i = 0; i < codepointCount; i++) requiredCodepoints[i] = codepoints[i]; + } + + glyphs = (GlyphInfo *)RL_CALLOC(codepointCount, sizeof(GlyphInfo)); + + while (totalReadBytes <= dataSize) + { + readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + totalReadBytes += (readBytes + 1); + fileTextPtr += (readBytes + 1); + + // Line: COMMENT + if (strstr(buffer, "COMMENT") != NULL) continue; // Ignore line + + if (charStarted) + { + // Line: ENDCHAR + if (strstr(buffer, "ENDCHAR") != NULL) + { + charStarted = false; + continue; + } + + if (charBitmapStarted) + { + if (glyphs != NULL) + { + int pixelY = charBitmapNextRow++; + + if (pixelY >= glyphs->image.height) break; + + for (int x = 0; x < readBytes; x++) + { + unsigned char byte = HexToInt(buffer[x]); + + for (int bitX = 0; bitX < 4; bitX++) + { + int pixelX = ((x*4) + bitX); + + if (pixelX >= glyphs->image.width) break; + + if ((byte & (8 >> bitX)) > 0) ((unsigned char *)glyphs->image.data)[(pixelY*glyphs->image.width) + pixelX] = 255; + } + } + } + continue; + } + + // Line: ENCODING + if (strstr(buffer, "ENCODING") != NULL) + { + readVars = sscanf(buffer, "ENCODING %i", &charEncoding); + continue; + } + + // Line: BBX + if (strstr(buffer, "BBX") != NULL) + { + readVars = sscanf(buffer, "BBX %i %i %i %i", &charBBw, &charBBh, &charBBxoff0, &charBByoff0); + continue; + } + + // Line: DWIDTH + if (strstr(buffer, "DWIDTH") != NULL) + { + readVars = sscanf(buffer, "DWIDTH %i %i", &charDWidthX, &charDWidthY); + continue; + } + + // Line: BITMAP + if (strstr(buffer, "BITMAP") != NULL) + { + // Search for glyph index in codepoints + glyphs = NULL; + + for (int index = 0; index < codepointCount; index++) + { + if (requiredCodepoints[index] == charEncoding) + { + glyphs = &glyphs[index]; + break; + } + } + + // Init glyph info + if (glyphs != NULL) + { + glyphs->value = charEncoding; + glyphs->offsetX = charBBxoff0 + fontBByoff0; + glyphs->offsetY = fontBBh - (charBBh + charBByoff0 + fontBByoff0 + fontAscent); + glyphs->advanceX = charDWidthX; + + glyphs->image.data = RL_CALLOC(charBBw*charBBh, 1); + glyphs->image.width = charBBw; + glyphs->image.height = charBBh; + glyphs->image.mipmaps = 1; + glyphs->image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + } + + charBitmapStarted = true; + charBitmapNextRow = 0; + + continue; + } + } + else if (fontStarted) + { + // Line: ENDFONT + if (strstr(buffer, "ENDFONT") != NULL) + { + fontStarted = false; + break; + } + + // Line: SIZE + if (strstr(buffer, "SIZE") != NULL) + { + if (outFontSize != NULL) readVars = sscanf(buffer, "SIZE %i", outFontSize); + continue; + } + + // PIXEL_SIZE + if (strstr(buffer, "PIXEL_SIZE") != NULL) + { + if (outFontSize != NULL) readVars = sscanf(buffer, "PIXEL_SIZE %i", outFontSize); + continue; + } + + // FONTBOUNDINGBOX + if (strstr(buffer, "FONTBOUNDINGBOX") != NULL) + { + readVars = sscanf(buffer, "FONTBOUNDINGBOX %i %i %i %i", &fontBBw, &fontBBh, &fontBBxoff0, &fontBByoff0); + continue; + } + + // FONT_ASCENT + if (strstr(buffer, "FONT_ASCENT") != NULL) + { + readVars = sscanf(buffer, "FONT_ASCENT %i", &fontAscent); + continue; + } + + // STARTCHAR + if (strstr(buffer, "STARTCHAR") != NULL) + { + charStarted = true; + charEncoding = -1; + glyphs = NULL; + charBBw = 0; + charBBh = 0; + charBBxoff0 = 0; + charBByoff0 = 0; + charDWidthX = 0; + charDWidthY = 0; + glyphs = NULL; + charBitmapStarted = false; + charBitmapNextRow = 0; + continue; + } + } + else + { + // STARTFONT + if (strstr(buffer, "STARTFONT") != NULL) + { + if (fontStarted) + { + fontMalformed = true; + break; + } + else + { + fontStarted = true; + continue; + } + } + } + } + + RL_FREE(requiredCodepoints); + + if (fontMalformed) + { + RL_FREE(glyphs); + glyphs = NULL; + } + + return glyphs; +} +#endif // SUPPORT_FILEFORMAT_BDF + +#endif // SUPPORT_MODULE_RTEXT diff --git a/src/libraries/raylib/raylib/src/rtextures.c b/src/libraries/raylib/raylib/src/rtextures.c new file mode 100644 index 0000000..18d8a95 --- /dev/null +++ b/src/libraries/raylib/raylib/src/rtextures.c @@ -0,0 +1,5744 @@ +/********************************************************************************************** +* +* rtextures - Basic functions to load and draw textures +* +* CONFIGURATION: +* #define SUPPORT_MODULE_RTEXTURES 1 +* rtextures module is included in the build +* +* #define SUPPORT_FILEFORMAT_BMP 1 +* #define SUPPORT_FILEFORMAT_PNG 1 +* #define SUPPORT_FILEFORMAT_TGA 0 +* #define SUPPORT_FILEFORMAT_JPG 0 +* #define SUPPORT_FILEFORMAT_GIF 1 +* #define SUPPORT_FILEFORMAT_QOI 1 +* #define SUPPORT_FILEFORMAT_PEP 1 +* #define SUPPORT_FILEFORMAT_PSD 0 +* #define SUPPORT_FILEFORMAT_HDR 0 +* #define SUPPORT_FILEFORMAT_PIC 0 +* #define SUPPORT_FILEFORMAT_PNM 0 +* #define SUPPORT_FILEFORMAT_DDS 1 +* #define SUPPORT_FILEFORMAT_PKM 0 +* #define SUPPORT_FILEFORMAT_KTX 0 +* #define SUPPORT_FILEFORMAT_PVR 0 +* #define SUPPORT_FILEFORMAT_ASTC 0 +* Selected desired fileformats to be supported for image data loading. Some of those formats are +* supported by default, to remove support, #define as 0 in this module or your build system +* +* #define SUPPORT_IMAGE_EXPORT 1 +* Support image export in multiple file formats +* +* #define SUPPORT_IMAGE_GENERATION 1 +* Support procedural image generation functionality (gradient, spot, perlin-noise, cellular) +* +* DEPENDENCIES: +* stb_image - Multiple image formats loading (JPEG, PNG, BMP, TGA, PSD, GIF, PIC) +* NOTE: stb_image has been slightly modified to support Android platform +* stb_image_resize - Multiple image resize algorithms +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" // Declares module functions + +#include "config.h" // Defines module configuration flags + +#if SUPPORT_MODULE_RTEXTURES + +#include "rlgl.h" // OpenGL abstraction layer to multiple versions + +#include // Required for: malloc(), calloc(), free() +#include // Required for: strlen() [Used in ImageTextEx()], strcmp() [Used in LoadImageFromMemory()/LoadImageAnimFromMemory()/ExportImageToMemory()] +#include // Required for: fabsf() [Used in DrawTextureRec()] +#include // Required for: sprintf() [Used in ExportImageAsCode()] +#include // Required for: INT_MAX + +// Support only desired texture formats on stb_image +#if !SUPPORT_FILEFORMAT_BMP + #define STBI_NO_BMP +#endif +#if !SUPPORT_FILEFORMAT_PNG + #define STBI_NO_PNG +#endif +#if !SUPPORT_FILEFORMAT_TGA + #define STBI_NO_TGA +#endif +#if !SUPPORT_FILEFORMAT_JPG + #define STBI_NO_JPEG // Image format .jpg and .jpeg +#endif +#if !SUPPORT_FILEFORMAT_PSD + #define STBI_NO_PSD +#endif +#if !SUPPORT_FILEFORMAT_GIF + #define STBI_NO_GIF +#endif +#if !SUPPORT_FILEFORMAT_PIC + #define STBI_NO_PIC +#endif +#if !SUPPORT_FILEFORMAT_HDR + #define STBI_NO_HDR +#endif +#if !SUPPORT_FILEFORMAT_PNM + #define STBI_NO_PNM +#endif + +#if SUPPORT_FILEFORMAT_DDS + #define RLTEXGPU_SUPPORT_DDS +#endif +#if SUPPORT_FILEFORMAT_PKM + #define RLTEXGPU_SUPPORT_PKM +#endif +#if SUPPORT_FILEFORMAT_KTX + #define RLTEXGPU_SUPPORT_KTX +#endif +#if SUPPORT_FILEFORMAT_PVR + #define RLTEXGPU_SUPPORT_PVR +#endif +#if SUPPORT_FILEFORMAT_ASTC + #define RLTEXGPU_SUPPORT_ASTC +#endif + +// Image fileformats not supported by default +#if (SUPPORT_FILEFORMAT_BMP || \ + SUPPORT_FILEFORMAT_PNG || \ + SUPPORT_FILEFORMAT_TGA || \ + SUPPORT_FILEFORMAT_JPG || \ + SUPPORT_FILEFORMAT_PSD || \ + SUPPORT_FILEFORMAT_GIF || \ + SUPPORT_FILEFORMAT_HDR || \ + SUPPORT_FILEFORMAT_PIC || \ + SUPPORT_FILEFORMAT_PNM) + + #if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-function" + #endif + + #define STBI_MALLOC RL_MALLOC + #define STBI_FREE RL_FREE + #define STBI_REALLOC RL_REALLOC + + #define STBI_NO_THREAD_LOCALS + + #if defined(__TINYC__) + #define STBI_NO_SIMD + #endif + + #define STB_IMAGE_IMPLEMENTATION + #include "external/stb_image.h" // Required for: stbi_load_from_file() + // NOTE: Used to read image data (multiple formats support) + + #if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic pop + #endif +#endif + +#if (SUPPORT_FILEFORMAT_DDS || \ + SUPPORT_FILEFORMAT_PKM || \ + SUPPORT_FILEFORMAT_KTX || \ + SUPPORT_FILEFORMAT_PVR || \ + SUPPORT_FILEFORMAT_ASTC) + + #if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-function" + #endif + + #define RLTEXGPU_MALLOC RL_MALLOC + #define RLTEXGPU_FREE RL_FREE + #define RLTEXGPU_LOG(...) TRACELOG(LOG_WARNING, "IMAGE: " __VA_ARGS__) + #define RLTEXGPU_SHOW_LOG_INFO + #define RLTEXGPU_IMPLEMENTATION + #include "external/rltexgpu.h" // Required for: rl_load_xxx_from_memory() + // NOTE: Used to read compressed textures data (multiple formats support) + #if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic pop + #endif +#endif + +#if SUPPORT_FILEFORMAT_QOI + #define QOI_MALLOC RL_MALLOC + #define QOI_FREE RL_FREE + + #if defined(_MSC_VER) // Disable some MSVC warning + #pragma warning(push) + #pragma warning(disable : 4267) + #endif + + #define QOI_IMPLEMENTATION + #include "external/qoi.h" + + #if defined(_MSC_VER) + #pragma warning(pop) // Disable MSVC warning suppression + #endif +#endif + +#if SUPPORT_FILEFORMAT_PEP + #define PEP_MALLOC RL_MALLOC + #define PEP_REALLOC RL_REALLOC + #define PEP_FREE RL_FREE + + #define PEP_IMPLEMENTATION + #include "external/pep.h" +#endif + +#if SUPPORT_IMAGE_EXPORT + #define STBIW_MALLOC RL_MALLOC + #define STBIW_FREE RL_FREE + #define STBIW_REALLOC RL_REALLOC + + #define STB_IMAGE_WRITE_IMPLEMENTATION + #include "external/stb_image_write.h" // Required for: stbi_write_*() +#endif + +#if SUPPORT_IMAGE_GENERATION + #define STB_PERLIN_IMPLEMENTATION + #include "external/stb_perlin.h" // Required for: stb_perlin_fbm_noise3 +#endif + +#define STBIR_MALLOC(size,c) ((void)(c), RL_MALLOC(size)) +#define STBIR_FREE(ptr,c) ((void)(c), RL_FREE(ptr)) + +#if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-function" +#endif + +#if defined(__TINYC__) + #define STBIR_NO_SIMD +#endif +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "external/stb_image_resize2.h" // Required for: stbir_resize_uint8_linear() [ImageResize()] + +#if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic pop +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD + #define PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD 50 // Threshold over 255 to set alpha as 0 +#endif + +#ifndef GAUSSIAN_BLUR_ITERATIONS + #define GAUSSIAN_BLUR_ITERATIONS 4 // Number of box blur iterations to approximate gaussian blur +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// It's lonely here... + +//---------------------------------------------------------------------------------- +// Other Modules Functions Declaration (required by text) +//---------------------------------------------------------------------------------- +extern void LoadFontDefault(void); // [Module: text] Loads default font, required by ImageDrawText() + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +static float HalfToFloat(unsigned short x); +static unsigned short FloatToHalf(float x); +static Vector4 *LoadImageDataNormalized(Image image); // Load pixel data from image as Vector4 array (float normalized) + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +// Load image from file into CPU memory (RAM) +Image LoadImage(const char *fileName) +{ + Image image = { 0 }; + +#if SUPPORT_FILEFORMAT_PNG || \ + SUPPORT_FILEFORMAT_BMP || \ + SUPPORT_FILEFORMAT_TGA || \ + SUPPORT_FILEFORMAT_JPG || \ + SUPPORT_FILEFORMAT_GIF || \ + SUPPORT_FILEFORMAT_PIC || \ + SUPPORT_FILEFORMAT_HDR || \ + SUPPORT_FILEFORMAT_PNM || \ + SUPPORT_FILEFORMAT_PSD + + #define STBI_REQUIRED +#endif + + // Loading file to memory + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + // Loading image from memory data + if (fileData != NULL) + { + image = LoadImageFromMemory(GetFileExtension(fileName), fileData, dataSize); + + UnloadFileData(fileData); + } + + return image; +} + +// Load an image from RAW file data +Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize) +{ + Image image = { 0 }; + + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData != NULL) + { + unsigned char *dataPtr = fileData; + int size = GetPixelDataSize(width, height, format); + + if (size <= dataSize) // Security check + { + // Offset file data to expected raw image by header size + if ((headerSize > 0) && ((headerSize + size) <= dataSize)) dataPtr += headerSize; + + image.data = RL_MALLOC(size); // Allocate required memory in bytes + memcpy(image.data, dataPtr, size); // Copy required data to image + image.width = width; + image.height = height; + image.mipmaps = 1; + image.format = format; + } + + UnloadFileData(fileData); + } + + return image; +} + +// Load animated image data +// - Image.data buffer includes all frames: [image#0][image#1][image#2][...] +// - Number of frames is returned through 'frames' parameter +// - All frames are returned in RGBA format +// - Frames delay data is discarded +Image LoadImageAnim(const char *fileName, int *frames) +{ + Image image = { 0 }; + int frameCount = 0; + +#if SUPPORT_FILEFORMAT_GIF + if (IsFileExtension(fileName, ".gif")) + { + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + if (fileData != NULL) + { + int comp = 0; + int *delays = NULL; + image.data = stbi_load_gif_from_memory(fileData, dataSize, &delays, &image.width, &image.height, &frameCount, &comp, 4); + + image.mipmaps = 1; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + UnloadFileData(fileData); + RL_FREE(delays); // NOTE: Frames delays are discarded + } + } +#else + if (false) { } +#endif + else + { + image = LoadImage(fileName); + frameCount = 1; + } + + *frames = frameCount; + return image; +} + +// Load animated image data from memory +// - Image.data buffer includes all frames: [image#0][image#1][image#2][...] +// - Number of frames is returned through 'frames' parameter +// - All frames are returned in RGBA format +// - Frames delay data is discarded +Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames) +{ + Image image = { 0 }; + int frameCount = 0; + + if ((fileType == NULL) || (fileData == NULL) || (dataSize == 0)) return image; // Security check + +#if SUPPORT_FILEFORMAT_GIF + if ((strcmp(fileType, ".gif") == 0) || (strcmp(fileType, ".GIF") == 0)) + { + if (fileData != NULL) + { + int comp = 0; + int *delays = NULL; + image.data = stbi_load_gif_from_memory(fileData, dataSize, &delays, &image.width, &image.height, &frameCount, &comp, 4); + + image.mipmaps = 1; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + RL_FREE(delays); // NOTE: Frames delays are discarded + } + } +#else + if (false) { } +#endif + else + { + image = LoadImageFromMemory(fileType, fileData, dataSize); + frameCount = 1; + } + + *frames = frameCount; + return image; +} + +// Load image from memory buffer, fileType refers to extension: i.e. ".png" +// WARNING: File extension must be provided in lower-case +Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) +{ + Image image = { 0 }; + + // Security checks for input data + if ((fileData == NULL) || (dataSize == 0)) + { + TRACELOG(LOG_WARNING, "IMAGE: Invalid file data"); + return image; + } + if (fileType == NULL) + { + TRACELOG(LOG_WARNING, "IMAGE: Missing file extension"); + return image; + } + + if ((false) +#if SUPPORT_FILEFORMAT_PNG + || (strcmp(fileType, ".png") == 0) || (strcmp(fileType, ".PNG") == 0) +#endif +#if SUPPORT_FILEFORMAT_BMP + || (strcmp(fileType, ".bmp") == 0) || (strcmp(fileType, ".BMP") == 0) +#endif +#if SUPPORT_FILEFORMAT_TGA + || (strcmp(fileType, ".tga") == 0) || (strcmp(fileType, ".TGA") == 0) +#endif +#if SUPPORT_FILEFORMAT_JPG + || (strcmp(fileType, ".jpg") == 0) || (strcmp(fileType, ".jpeg") == 0) + || (strcmp(fileType, ".JPG") == 0) || (strcmp(fileType, ".JPEG") == 0) +#endif +#if SUPPORT_FILEFORMAT_GIF + || (strcmp(fileType, ".gif") == 0) || (strcmp(fileType, ".GIF") == 0) +#endif +#if SUPPORT_FILEFORMAT_PIC + || (strcmp(fileType, ".pic") == 0) || (strcmp(fileType, ".PIC") == 0) +#endif +#if SUPPORT_FILEFORMAT_PNM + || (strcmp(fileType, ".ppm") == 0) || (strcmp(fileType, ".pgm") == 0) + || (strcmp(fileType, ".PPM") == 0) || (strcmp(fileType, ".PGM") == 0) +#endif +#if SUPPORT_FILEFORMAT_PSD + || (strcmp(fileType, ".psd") == 0) || (strcmp(fileType, ".PSD") == 0) +#endif + ) + { +#if defined(STBI_REQUIRED) + // NOTE: Using stb_image to load images (Supports multiple image formats) + + if (fileData != NULL) + { + int comp = 0; + image.data = stbi_load_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); + + if (image.data != NULL) + { + image.mipmaps = 1; + + if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + else if (comp == 2) image.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; + else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + } + } +#endif + } +#if SUPPORT_FILEFORMAT_HDR + else if ((strcmp(fileType, ".hdr") == 0) || (strcmp(fileType, ".HDR") == 0)) + { +#if defined(STBI_REQUIRED) + if (fileData != NULL) + { + int comp = 0; + image.data = stbi_loadf_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); + + image.mipmaps = 1; + + if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_R32; + else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32; + else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32A32; + else + { + TRACELOG(LOG_WARNING, "IMAGE: HDR file format not supported"); + UnloadImage(image); + } + } +#endif + } +#endif +#if SUPPORT_FILEFORMAT_QOI + else if ((strcmp(fileType, ".qoi") == 0) || (strcmp(fileType, ".QOI") == 0)) + { + if (fileData != NULL) + { + qoi_desc desc = { 0 }; + image.data = qoi_decode(fileData, dataSize, &desc, (int) fileData[12]); + image.width = desc.width; + image.height = desc.height; + image.format = (desc.channels == 4)? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 : PIXELFORMAT_UNCOMPRESSED_R8G8B8; + image.mipmaps = 1; + } + } +#endif +#if SUPPORT_FILEFORMAT_PEP + else if ((strcmp(fileType, ".pep") == 0) || (strcmp(fileType, ".PEP") == 0)) + { + if (fileData != NULL) + { + // Deserialize file data into pep structure + pep p = pep_deserialize(fileData, dataSize); + + // Decompress pep data for image struct + // Decompression options: + // - First color transparent, 3rd parameter: 1 + // - Premultiply alpha on loading, 4th parameter: 1 + unsigned int *pepData = pep_decompress(&p, pep_rgba, 1, 1); + + image.data = pepData; + image.width = p.width; + image.height = p.height; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + image.mipmaps = 1; + + PEP_FREE(pepData); + pep_free(&p); + } + } +#endif +#if SUPPORT_FILEFORMAT_DDS + else if ((strcmp(fileType, ".dds") == 0) || (strcmp(fileType, ".DDS") == 0)) + { + image.data = rl_load_dds_from_memory(fileData, dataSize, &image.width, &image.height, &image.format, &image.mipmaps); + } +#endif +#if SUPPORT_FILEFORMAT_PKM + else if ((strcmp(fileType, ".pkm") == 0) || (strcmp(fileType, ".PKM") == 0)) + { + image.data = rl_load_pkm_from_memory(fileData, dataSize, &image.width, &image.height, &image.format, &image.mipmaps); + } +#endif +#if SUPPORT_FILEFORMAT_KTX + else if ((strcmp(fileType, ".ktx") == 0) || (strcmp(fileType, ".KTX") == 0)) + { + image.data = rl_load_ktx_from_memory(fileData, dataSize, &image.width, &image.height, &image.format, &image.mipmaps); + } +#endif +#if SUPPORT_FILEFORMAT_PVR + else if ((strcmp(fileType, ".pvr") == 0) || (strcmp(fileType, ".PVR") == 0)) + { + image.data = rl_load_pvr_from_memory(fileData, dataSize, &image.width, &image.height, &image.format, &image.mipmaps); + } +#endif +#if SUPPORT_FILEFORMAT_ASTC + else if ((strcmp(fileType, ".astc") == 0) || (strcmp(fileType, ".ASTC") == 0)) + { + image.data = rl_load_astc_from_memory(fileData, dataSize, &image.width, &image.height, &image.format, &image.mipmaps); + } +#endif + else TRACELOG(LOG_WARNING, "IMAGE: Data format not supported"); + + if (image.data != NULL) TRACELOG(LOG_INFO, "IMAGE: Data loaded successfully (%ix%i | %s | %i mipmaps)", image.width, image.height, rlGetPixelFormatName(image.format), image.mipmaps); + else TRACELOG(LOG_WARNING, "IMAGE: Failed to load image data"); + + return image; +} + +// Load image from GPU texture data +// NOTE: Compressed texture formats not supported +Image LoadImageFromTexture(Texture2D texture) +{ + Image image = { 0 }; + + if (texture.format < PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + image.data = rlReadTexturePixels(texture.id, texture.width, texture.height, texture.format); + + if (image.data != NULL) + { + image.width = texture.width; + image.height = texture.height; +#if defined(FBO_READ_TEXTURE_AS_RGBA) + // WARNING: On OpenGL ES 2.0/WebGL 1.0, there is no glGetTexImage() so, + // texture data is retrieved by creating an RGBA fbo, binding the texture + // to the fbo color attachment, and reading it as RGBA data with glReadPixels() + // Returned data *should* be RGBA but it seems on some platforms (RPI, WASM) + // original texture format is retrieved, so adding a define for this patch + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; +#else + image.format = texture.format; +#endif + image.mipmaps = 1; + + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Pixel data retrieved successfully", texture.id); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve pixel data", texture.id); + } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to retrieve compressed pixel data", texture.id); + + return image; +} + +// Load image from screen buffer (screenshot) +Image LoadImageFromScreen(void) +{ + Image image = { 0 }; + + image.width = (int)(GetRenderWidth()); + image.height = (int)(GetRenderHeight()); + image.mipmaps = 1; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + image.data = rlReadScreenPixels(image.width, image.height); + + return image; +} + +// Check if an image is ready +bool IsImageValid(Image image) +{ + bool result = false; + + if ((image.data != NULL) && // Validate pixel data available + (image.width > 0) && // Validate image width + (image.height > 0) && // Validate image height + (image.format > 0) && // Validate image format + (image.mipmaps > 0)) result = true; // Validate image mipmaps (at least 1 for basic mipmap level) + + return result; +} + +// Unload image from CPU memory (RAM) +void UnloadImage(Image image) +{ + RL_FREE(image.data); +} + +// Export image data to file +// NOTE: File format depends on fileName extension +bool ExportImage(Image image, const char *fileName) +{ + int result = 0; + + if ((image.width == 0) || (image.height == 0) || (image.data == NULL)) return result; // Security check + +#if SUPPORT_IMAGE_EXPORT + int channels = 4; + bool allocatedData = false; + unsigned char *imgData = (unsigned char *)image.data; + + if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) channels = 1; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) channels = 2; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4; + else + { + // NOTE: Getting Color array as RGBA unsigned char values + imgData = (unsigned char *)LoadImageColors(image); + allocatedData = true; + } + + if (false) { } // Required to attach following 'else' cases +#if SUPPORT_FILEFORMAT_PNG + else if (IsFileExtension(fileName, ".png")) + { + int dataSize = 0; + unsigned char *fileData = stbi_write_png_to_mem((const unsigned char *)imgData, image.width*channels, image.width, image.height, channels, &dataSize); + result = SaveFileData(fileName, fileData, dataSize); + RL_FREE(fileData); + } +#endif +#if SUPPORT_FILEFORMAT_BMP + else if (IsFileExtension(fileName, ".bmp")) result = stbi_write_bmp(fileName, image.width, image.height, channels, imgData); +#endif +#if SUPPORT_FILEFORMAT_TGA + else if (IsFileExtension(fileName, ".tga")) result = stbi_write_tga(fileName, image.width, image.height, channels, imgData); +#endif +#if SUPPORT_FILEFORMAT_JPG + else if (IsFileExtension(fileName, ".jpg") || + IsFileExtension(fileName, ".jpeg")) result = stbi_write_jpg(fileName, image.width, image.height, channels, imgData, 90); // JPG quality: between 1 and 100 +#endif +#if SUPPORT_FILEFORMAT_QOI + else if (IsFileExtension(fileName, ".qoi")) + { + channels = 0; + if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4; + else TRACELOG(LOG_WARNING, "IMAGE: Image pixel format must be R8G8B8 or R8G8B8A8"); + + if ((channels == 3) || (channels == 4)) + { + qoi_desc desc = { 0 }; + desc.width = image.width; + desc.height = image.height; + desc.channels = channels; + desc.colorspace = QOI_SRGB; + + result = qoi_write(fileName, imgData, &desc); + } + } +#endif +#if SUPPORT_FILEFORMAT_PEP + else if (IsFileExtension(fileName, ".pep")) + { + if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + // NOTE: Only RGBA 32bit (8bit per channel) image format supported + pep p = pep_compress(image.data, image.width, image.height, pep_rgba, pep_8bit); + + unsigned int pepDataSize = 0; + unsigned char *pepData = pep_serialize(&p, &pepDataSize); + + result = SaveFileData(fileName, pepData, pepDataSize); + + PEP_FREE(pepData); + pep_free(&p); + } + else TRACELOG(LOG_WARNING, "IMAGE: Image can not be exported, .pep requires RGBA 32bit input"); + } +#endif +#if SUPPORT_FILEFORMAT_KTX + else if (IsFileExtension(fileName, ".ktx")) + { + result = rl_save_ktx(fileName, image.data, image.width, image.height, image.format, image.mipmaps); + } +#endif + else if (IsFileExtension(fileName, ".raw")) + { + // Export raw pixel data (without header) + // NOTE: It's up to the user to track image parameters + result = SaveFileData(fileName, image.data, GetPixelDataSize(image.width, image.height, image.format)); + } + else TRACELOG(LOG_WARNING, "IMAGE: Export image format requested not supported"); + + if (allocatedData) RL_FREE(imgData); +#endif // SUPPORT_IMAGE_EXPORT + + if (result != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName); + + return result; +} + +// Export image to memory buffer +unsigned char *ExportImageToMemory(Image image, const char *fileType, int *dataSize) +{ + unsigned char *fileData = NULL; + *dataSize = 0; + + if ((image.width == 0) || (image.height == 0) || (image.data == NULL)) return fileData; // Security check + + int channels = 4; + + if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) channels = 1; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) channels = 2; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4; + +#if SUPPORT_IMAGE_EXPORT + if ((strcmp(fileType, ".png") == 0) || (strcmp(fileType, ".PNG") == 0)) + { + fileData = stbi_write_png_to_mem((const unsigned char *)image.data, image.width*channels, image.width, image.height, channels, dataSize); + } +#else + TRACELOG(LOG_WARNING, "IMAGE: To export image, enable flag SUPPORT_IMAGE_EXPORT"); +#endif + + return fileData; +} + +// Export image as code file (.h) defining an array of bytes +bool ExportImageAsCode(Image image, const char *fileName) +{ + bool result = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + int dataSize = GetPixelDataSize(image.width, image.height, image.format); + + // NOTE: Text data buffer size is estimated considering image data size in bytes + // and requiring 6 char bytes for every byte: "0x00, " + char *txtData = (char *)RL_CALLOC(dataSize*6 + 2000, sizeof(char)); + + int byteCount = 0; + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// ImageAsCode exporter v1.0 - Image pixel data exported as an array of bytes //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2026 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path and convert variable name to uppercase + char varFileName[256] = { 0 }; + snprintf(varFileName, 256, "%s", GetFileNameWithoutExt(fileName)); // NOTE: Using function provided by [rcore] module + for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } + + // Add image information + byteCount += sprintf(txtData + byteCount, "// Image data information\n"); + byteCount += sprintf(txtData + byteCount, "#define %s_WIDTH %i\n", varFileName, image.width); + byteCount += sprintf(txtData + byteCount, "#define %s_HEIGHT %i\n", varFileName, image.height); + byteCount += sprintf(txtData + byteCount, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format); + + byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize); + for (int i = 0; i < dataSize - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]); + byteCount += sprintf(txtData + byteCount, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); + + // NOTE: Text data size exported is determined by '\0' (NULL) character + result = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + if (result != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image as code exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image as code", fileName); + + return result; +} + +//------------------------------------------------------------------------------------ +// Image generation functions +//------------------------------------------------------------------------------------ +// Generate image: plain color +Image GenImageColor(int width, int height, Color color) +{ + int dataSize = GetPixelDataSize(width, height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); + Color *pixels = (Color *)RL_CALLOC(dataSize, 1); + + for (int i = 0; (pixels != NULL) && (i < width*height); i++) pixels[i] = color; + + Image image = { + .data = pixels, + .width = width, + .height = height, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + }; + + return image; +} + +#if SUPPORT_IMAGE_GENERATION +// Generate image: linear gradient +// The direction value specifies the direction of the gradient (in degrees) +// with 0 being vertical (from top to bottom), 90 being horizontal (from left to right) +// The gradient effectively rotates counter-clockwise by the specified amount +Image GenImageGradientLinear(int width, int height, int direction, Color start, Color end) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + float radianDirection = (float)(90 - direction)/180.f*3.14159f; + float cosDir = cosf(radianDirection); + float sinDir = sinf(radianDirection); + + // Calculate how far the top-left pixel is along the gradient direction from the center of said gradient + float startingPos = 0.5f - (cosDir*width/2) - (sinDir*height/2); + + // With directions that lie in the first or third quadrant (i.e. from top-left to + // bottom-right or vice-versa), pixel (0, 0) is the farthest point on the gradient + // (i.e. the pixel which should become one of the gradient's ends color); while for + // directions that lie in the second or fourth quadrant, that point is pixel (width, 0) + float maxPosValue = ((signbit(sinDir) != 0) == (signbit(cosDir) != 0))? fabsf(startingPos) : fabsf(startingPos + width*cosDir); + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + // Calculate the relative position of the pixel along the gradient direction + float pos = (startingPos + (i*cosDir + j*sinDir))/maxPosValue; + + float factor = pos; + factor = (factor > 1.0f)? 1.0f : factor; // Clamp to [-1,1] + factor = (factor < -1.0f)? -1.0f : factor; // Clamp to [-1,1] + factor = factor/2.0f + 0.5f; + + // Generate the color for this pixel + pixels[j*width + i].r = (int)((float)end.r*factor + (float)start.r*(1.0f - factor)); + pixels[j*width + i].g = (int)((float)end.g*factor + (float)start.g*(1.0f - factor)); + pixels[j*width + i].b = (int)((float)end.b*factor + (float)start.b*(1.0f - factor)); + pixels[j*width + i].a = (int)((float)end.a*factor + (float)start.a*(1.0f - factor)); + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + }; + + return image; +} + +// Generate image: radial gradient +Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + float radius = (width < height)? (float)width/2.0f : (float)height/2.0f; + + float centerX = (float)width/2.0f; + float centerY = (float)height/2.0f; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float dist = hypotf((float)x - centerX, (float)y - centerY); + float factor = (dist - radius*density)/(radius*(1.0f - density)); + + factor = (float)fmax(factor, 0.0f); + factor = (float)fmin(factor, 1.f); // Distance can be bigger than radius, so it needs to be checked + + pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); + pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor)); + pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor)); + pixels[y*width + x].a = (int)((float)outer.a*factor + (float)inner.a*(1.0f - factor)); + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + }; + + return image; +} + +// Generate image: square gradient +Image GenImageGradientSquare(int width, int height, float density, Color inner, Color outer) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + float centerX = (float)width/2.0f; + float centerY = (float)height/2.0f; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + // Calculate the Manhattan distance from the center + float distX = fabsf(x - centerX); + float distY = fabsf(y - centerY); + + // Normalize the distances by the dimensions of the gradient rectangle + float normalizedDistX = distX/centerX; + float normalizedDistY = distY/centerY; + + // Calculate the total normalized Manhattan distance + float manhattanDist = fmaxf(normalizedDistX, normalizedDistY); + + // Subtract the density from the manhattanDist, then divide by (1 - density) + // This makes the gradient start from the center when density is 0, and from the edge when density is 1 + float factor = (manhattanDist - density)/(1.0f - density); + + // Clamp the factor between 0 and 1 + factor = fminf(fmaxf(factor, 0.0f), 1.0f); + + // Blend the colors based on the calculated factor + pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); + pixels[y*width + x].g = (int)((float)outer.g*factor + (float)inner.g*(1.0f - factor)); + pixels[y*width + x].b = (int)((float)outer.b*factor + (float)inner.b*(1.0f - factor)); + pixels[y*width + x].a = (int)((float)outer.a*factor + (float)inner.a*(1.0f - factor)); + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + }; + + return image; +} + +// Generate image: checked +Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if ((x/checksX + y/checksY)%2 == 0) pixels[y*width + x] = col1; + else pixels[y*width + x] = col2; + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + }; + + return image; +} + +// Generate image: white noise +// NOTE: It requires GetRandomValue(), defined in [rcore] +Image GenImageWhiteNoise(int width, int height, float factor) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + for (int i = 0; i < width*height; i++) + { + if (GetRandomValue(0, 99) < (int)(factor*100.0f)) pixels[i] = WHITE; + else pixels[i] = BLACK; + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + }; + + return image; +} + +// Generate image: perlin noise +Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + float aspectRatio = (float)width/(float)height; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float nx = (float)(x + offsetX)*(scale/(float)width); + float ny = (float)(y + offsetY)*(scale/(float)height); + + // Apply aspect ratio compensation to wider side + if (width > height) nx *= aspectRatio; + else ny /= aspectRatio; + + // Basic perlin noise implementation (not used) + //float p = (stb_perlin_noise3(nx, ny, 0.0f, 0, 0, 0); + + // Calculate a better perlin noise using fbm (fractal brownian motion) + // Typical values to start playing with: + // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) + // gain = 0.5 -- relative weighting applied to each successive octave + // octaves = 6 -- number of "octaves" of noise3() to sum + float p = stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6); + + // Clamp between -1.0f and 1.0f + if (p < -1.0f) p = -1.0f; + if (p > 1.0f) p = 1.0f; + + // Data needs to be normalized from [-1..1] to [0..1] + float np = (p + 1.0f)/2.0f; + + unsigned char intensity = (unsigned char)(np*255.0f); + pixels[y*width + x] = (Color){ intensity, intensity, intensity, 255 }; + } + } + + Image image = { + .data = pixels, + .width = width, + .height = height, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + }; + + return image; +} + +// Generate image: cellular algorithm. Bigger tileSize means bigger cells +Image GenImageCellular(int width, int height, int tileSize) +{ + Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + + int seedsPerRow = width/tileSize; + int seedsPerCol = height/tileSize; + int seedCount = seedsPerRow*seedsPerCol; + + Vector2 *seeds = (Vector2 *)RL_MALLOC(seedCount*sizeof(Vector2)); + + for (int i = 0; i < seedCount; i++) + { + int y = (i/seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); + int x = (i%seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); + seeds[i] = (Vector2){ (float)x, (float)y }; + } + + for (int y = 0; y < height; y++) + { + int tileY = y/tileSize; + + for (int x = 0; x < width; x++) + { + int tileX = x/tileSize; + + float minDistance = 65536.0f; //(float)strtod("Inf", NULL); + + // Check all adjacent tiles + for (int i = -1; i < 2; i++) + { + if ((tileX + i < 0) || (tileX + i >= seedsPerRow)) continue; + + for (int j = -1; j < 2; j++) + { + if ((tileY + j < 0) || (tileY + j >= seedsPerCol)) continue; + + Vector2 neighborSeed = seeds[(tileY + j)*seedsPerRow + tileX + i]; + + float dist = (float)hypot(x - (int)neighborSeed.x, y - (int)neighborSeed.y); + minDistance = (float)fmin(minDistance, dist); + } + } + + // This approach seems to give good results at all tile sizes + int intensity = (int)(minDistance*256.0f/tileSize); + if (intensity > 255) intensity = 255; + + unsigned char intensityUC = (unsigned char)intensity; + pixels[y*width + x] = (Color){ intensityUC, intensityUC, intensityUC, 255 }; + } + } + + RL_FREE(seeds); + + Image image = { + .data = pixels, + .width = width, + .height = height, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + }; + + return image; +} + +// Generate image: grayscale image from text data +Image GenImageText(int width, int height, const char *text) +{ + Image image = { 0 }; + + int imageSize = width*height; + image.width = width; + image.height = height; + image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + image.data = RL_CALLOC(imageSize, 1); + image.mipmaps = 1; + + if (text != NULL) + { + int textLength = (int)strlen(text); + memcpy(image.data, text, (textLength > imageSize)? imageSize : textLength); + } + + return image; +} +#endif // SUPPORT_IMAGE_GENERATION + +//------------------------------------------------------------------------------------ +// Image manipulation functions +//------------------------------------------------------------------------------------ +// Copy an image to a new image +Image ImageCopy(Image image) +{ + Image newImage = { 0 }; + + int width = image.width; + int height = image.height; + int size = 0; + + for (int i = 0; i < image.mipmaps; i++) + { + size += GetPixelDataSize(width, height, image.format); + + width /= 2; + height /= 2; + + // Security check for NPOT textures + if (width < 1) width = 1; + if (height < 1) height = 1; + } + + newImage.data = RL_CALLOC(size, 1); + + if (newImage.data != NULL) + { + // NOTE: Size must be provided in bytes + memcpy(newImage.data, image.data, size); + + newImage.width = image.width; + newImage.height = image.height; + newImage.mipmaps = image.mipmaps; + newImage.format = image.format; + } + + return newImage; +} + +// Create an image from another image piece +Image ImageFromImage(Image image, Rectangle rec) +{ + Image result = { 0 }; + + // Security check to avoid program crash + if ((image.data == NULL) || (image.width == 0) || (image.height == 0)) return result; + + if (image.format < PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + // Basic rectangle validation: size smaller than image size + if ((rec.x >= 0) && (rec.y >= 0) && (rec.width > 0) && (rec.height > 0) && + (((int)rec.x + (int)rec.width) <= image.width) && + (((int)rec.y + (int)rec.height) <= image.height)) + { + int bytesPerPixel = GetPixelDataSize(1, 1, image.format); + + result.width = (int)rec.width; + result.height = (int)rec.height; + result.data = RL_CALLOC((int)rec.width*(int)rec.height*bytesPerPixel, 1); + result.format = image.format; + result.mipmaps = 1; + + for (int y = 0; y < (int)rec.height; y++) + { + memcpy(((unsigned char *)result.data) + y*(int)rec.width*bytesPerPixel, + ((unsigned char *)image.data) + ((y + (int)rec.y)*image.width + (int)rec.x)*bytesPerPixel, + (int)rec.width*bytesPerPixel); + } + } + else TRACELOG(LOG_WARNING, "IMAGE: ImageToImage(), rectangle provided not valid"); + } + else TRACELOG(LOG_WARNING, "IMAGE: Image manipulation not supported for compressed formats"); + + return result; +} + +// Crop an image to area defined by a rectangle +// NOTE: Security checks are performed in case rectangle goes out of bounds +void ImageCrop(Image *image, Rectangle crop) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + // Security checks to validate crop rectangle + if (crop.x < 0) { crop.width += crop.x; crop.x = 0; } + if (crop.y < 0) { crop.height += crop.y; crop.y = 0; } + if ((crop.x + crop.width) > image->width) crop.width = image->width - crop.x; + if ((crop.y + crop.height) > image->height) crop.height = image->height - crop.y; + if ((crop.x > image->width) || (crop.y > image->height)) + { + TRACELOG(LOG_WARNING, "IMAGE: Failed to crop, rectangle out of bounds"); + return; + } + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + + unsigned char *croppedData = (unsigned char *)RL_MALLOC((int)(crop.width*crop.height)*bytesPerPixel); + + // OPTION 1: Move cropped data line-by-line + for (int y = (int)crop.y, offsetSize = 0; y < (int)(crop.y + crop.height); y++) + { + memcpy(croppedData + offsetSize, ((unsigned char *)image->data) + (y*image->width + (int)crop.x)*bytesPerPixel, (int)crop.width*bytesPerPixel); + offsetSize += ((int)crop.width*bytesPerPixel); + } + + /* + // OPTION 2: Move cropped data pixel-by-pixel or byte-by-byte + for (int y = (int)crop.y; y < (int)(crop.y + crop.height); y++) + { + for (int x = (int)crop.x; x < (int)(crop.x + crop.width); x++) + { + //memcpy(croppedData + ((y - (int)crop.y)*(int)crop.width + (x - (int)crop.x))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + x)*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) croppedData[((y - (int)crop.y)*(int)crop.width + (x - (int)crop.x))*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + x)*bytesPerPixel + i]; + } + } + */ + + RL_FREE(image->data); + image->data = croppedData; + image->width = (int)crop.width; + image->height = (int)crop.height; + } +} + +// Convert image data to desired format +void ImageFormat(Image *image, int newFormat) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if ((newFormat != 0) && (image->format != newFormat)) + { + if ((image->format < PIXELFORMAT_COMPRESSED_DXT1_RGB) && (newFormat < PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + Vector4 *pixels = LoadImageDataNormalized(*image); // Supports 8 to 32 bit per channel + + RL_FREE(image->data); // WARNING! Loosing mipmaps data --> Regenerated at the end + image->data = NULL; + image->format = newFormat; + + switch (image->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*sizeof(unsigned char)); + + for (int i = 0; i < image->width*image->height; i++) + { + ((unsigned char *)image->data)[i] = (unsigned char)((pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f)*255.0f); + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*2*sizeof(unsigned char)); + + for (int i = 0, k = 0; i < image->width*image->height*2; i += 2, k++) + { + ((unsigned char *)image->data)[i] = (unsigned char)((pixels[k].x*0.299f + (float)pixels[k].y*0.587f + (float)pixels[k].z*0.114f)*255.0f); + ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].w*255.0f); + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + unsigned char r = 0; + unsigned char g = 0; + unsigned char b = 0; + + for (int i = 0; i < image->width*image->height; i++) + { + r = (unsigned char)(round(pixels[i].x*31.0f)); + g = (unsigned char)(round(pixels[i].y*63.0f)); + b = (unsigned char)(round(pixels[i].z*31.0f)); + + ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*3*sizeof(unsigned char)); + + for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) + { + ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f); + ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f); + ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f); + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + unsigned char r = 0; + unsigned char g = 0; + unsigned char b = 0; + unsigned char a = 0; + + for (int i = 0; i < image->width*image->height; i++) + { + r = (unsigned char)(round(pixels[i].x*31.0f)); + g = (unsigned char)(round(pixels[i].y*31.0f)); + b = (unsigned char)(round(pixels[i].z*31.0f)); + a = (pixels[i].w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0; + + ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + unsigned char r = 0; + unsigned char g = 0; + unsigned char b = 0; + unsigned char a = 0; + + for (int i = 0; i < image->width*image->height; i++) + { + r = (unsigned char)(round(pixels[i].x*15.0f)); + g = (unsigned char)(round(pixels[i].y*15.0f)); + b = (unsigned char)(round(pixels[i].z*15.0f)); + a = (unsigned char)(round(pixels[i].w*15.0f)); + + ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + } + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + image->data = (unsigned char *)RL_MALLOC(image->width*image->height*4*sizeof(unsigned char)); + + for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) + { + ((unsigned char *)image->data)[i] = (unsigned char)(pixels[k].x*255.0f); + ((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].y*255.0f); + ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f); + ((unsigned char *)image->data)[i + 3] = (unsigned char)(pixels[k].w*255.0f); + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + // WARNING: Image is converted to GRAYSCALE equivalent 32bit + + image->data = (float *)RL_MALLOC(image->width*image->height*sizeof(float)); + + for (int i = 0; i < image->width*image->height; i++) + { + ((float *)image->data)[i] = (float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f); + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + image->data = (float *)RL_MALLOC(image->width*image->height*3*sizeof(float)); + + for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) + { + ((float *)image->data)[i] = pixels[k].x; + ((float *)image->data)[i + 1] = pixels[k].y; + ((float *)image->data)[i + 2] = pixels[k].z; + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + image->data = (float *)RL_MALLOC(image->width*image->height*4*sizeof(float)); + + for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) + { + ((float *)image->data)[i] = pixels[k].x; + ((float *)image->data)[i + 1] = pixels[k].y; + ((float *)image->data)[i + 2] = pixels[k].z; + ((float *)image->data)[i + 3] = pixels[k].w; + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R16: + { + // WARNING: Image is converted to GRAYSCALE equivalent 16bit + + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + for (int i = 0; i < image->width*image->height; i++) + { + ((unsigned short *)image->data)[i] = FloatToHalf((float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f)); + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16: + { + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*3*sizeof(unsigned short)); + + for (int i = 0, k = 0; i < image->width*image->height*3; i += 3, k++) + { + ((unsigned short *)image->data)[i] = FloatToHalf(pixels[k].x); + ((unsigned short *)image->data)[i + 1] = FloatToHalf(pixels[k].y); + ((unsigned short *)image->data)[i + 2] = FloatToHalf(pixels[k].z); + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + { + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*4*sizeof(unsigned short)); + + for (int i = 0, k = 0; i < image->width*image->height*4; i += 4, k++) + { + ((unsigned short *)image->data)[i] = FloatToHalf(pixels[k].x); + ((unsigned short *)image->data)[i + 1] = FloatToHalf(pixels[k].y); + ((unsigned short *)image->data)[i + 2] = FloatToHalf(pixels[k].z); + ((unsigned short *)image->data)[i + 3] = FloatToHalf(pixels[k].w); + } + } break; + default: break; + } + + RL_FREE(pixels); + pixels = NULL; + + // In case original image had mipmaps, generate mipmaps for formatted image + // NOTE: Original mipmaps are replaced by new ones, if custom mipmaps were used, they are lost + if (image->mipmaps > 1) + { + image->mipmaps = 1; + if (image->data != NULL) ImageMipmaps(image); + } + } + else TRACELOG(LOG_WARNING, "IMAGE: Data format is compressed, can not be converted"); + } +} + +// Create an image from text (default font) +Image ImageText(const char *text, int fontSize, Color color) +{ + Image imText = { 0 }; +#if SUPPORT_MODULE_RTEXT + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); // WARNING: Module required: rtext +#else + imText = GenImageColor(200, 60, BLACK); // Generating placeholder black image rectangle + TRACELOG(LOG_WARNING, "IMAGE: ImageTextEx() requires module: rtext"); +#endif + return imText; +} + +// Create an image from text (custom sprite font) +// WARNING: Module required: rtext +Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) +{ + Image imText = { 0 }; +#if SUPPORT_MODULE_RTEXT + if (text == NULL) return imText; + + int textLength = (int)strlen(text); // Get length of text in bytes + int textOffsetX = 0; // Image drawing position X + int textOffsetY = 0; // Offset between lines (on linebreak '\n') + + // NOTE: Text image is generated at font base size, later scaled to desired font size + Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); // WARNING: Module required: rtext + Vector2 textSize = MeasureTextEx(font, text, fontSize, spacing); + + // Create image to store text + imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); + + for (int i = 0; i < textLength;) + { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetCodepointNext(&text[i], &codepointByteCount); // WARNING: Module required: rtext + int index = GetGlyphIndex(font, codepoint); // WARNING: Module required: rtext + + if (codepoint == '\n') + { + // NOTE: Fixed line spacing of 1.5 line-height + // TODO: Support custom line spacing defined by user + textOffsetY += (font.baseSize + font.baseSize/2); + textOffsetX = 0; + } + else + { + if ((codepoint != ' ') && (codepoint != '\t')) + { + Rectangle rec = { (float)(textOffsetX + font.glyphs[index].offsetX), (float)(textOffsetY + font.glyphs[index].offsetY), (float)font.recs[index].width, (float)font.recs[index].height }; + ImageDrawImagePro(&imText, font.glyphs[index].image, (Rectangle){ 0, 0, (float)font.glyphs[index].image.width, (float)font.glyphs[index].image.height }, + rec, (Vector2){ 0 }, 0.0f, tint); + } + + if (font.glyphs[index].advanceX == 0) textOffsetX += (int)(font.recs[index].width + spacing); + else textOffsetX += font.glyphs[index].advanceX + (int)spacing; + } + + i += codepointByteCount; // Move text bytes counter to next codepoint + } + + // Scale image depending on text size + if (textSize.y != imSize.y) + { + float scaleFactor = textSize.y/imSize.y; + TRACELOG(LOG_INFO, "IMAGE: Text scaled by factor: %f", scaleFactor); + + // Using nearest-neighbor scaling algorithm for default font + // TODO: Allow defining the preferred scaling mechanism externally + if (font.texture.id == GetFontDefault().texture.id) ImageResizeNN(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); + else ImageResize(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); + } +#else + imText = GenImageColor(200, 60, BLACK); // Generating placeholder black image rectangle + TRACELOG(LOG_WARNING, "IMAGE: ImageTextEx() requires module: rtext"); +#endif + return imText; +} + +// Create an image from a selected channel of another image +Image ImageFromChannel(Image image, int selectedChannel) +{ + Image result = { 0 }; + + if ((image.data == NULL) || (image.width == 0) || (image.height == 0)) return result; // Security check + + // Check selected channel is valid + if (selectedChannel < 0) + { + TRACELOG(LOG_WARNING, "Channel cannot be negative. Setting channel to 0."); + selectedChannel = 0; + } + + if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE || + image.format == PIXELFORMAT_UNCOMPRESSED_R32 || + image.format == PIXELFORMAT_UNCOMPRESSED_R16) + { + if (selectedChannel > 0) + { + TRACELOG(LOG_WARNING, "This image has only 1 channel. Setting channel to it."); + selectedChannel = 0; + } + } + else if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { + if (selectedChannel > 1) + { + TRACELOG(LOG_WARNING, "This image has only 2 channels. Setting channel to alpha."); + selectedChannel = 1; + } + } + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R5G6B5 || + image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8 || + image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32 || + image.format == PIXELFORMAT_UNCOMPRESSED_R16G16B16) + { + if (selectedChannel > 2) + { + TRACELOG(LOG_WARNING, "This image has only 3 channels. Setting channel to red."); + selectedChannel = 0; + } + } + + // Check for RGBA formats + if (selectedChannel > 3) + { + TRACELOG(LOG_WARNING, "ImageFromChannel supports channels 0 to 3 (RGBA). Setting channel to alpha."); + selectedChannel = 3; + } + + // TODO: Consider other one-channel formats: R16, R32 + result.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + result.height = image.height; + result.width = image.width; + result.mipmaps = 1; + + unsigned char *pixels = (unsigned char *)RL_CALLOC(image.width*image.height, sizeof(unsigned char)); // Values from 0 to 255 + + if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else + { + for (int i = 0, k = 0; i < image.width*image.height; i++) + { + float pixelValue = -1; + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + pixelValue = (float)((unsigned char *)image.data)[i + selectedChannel]/255.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + pixelValue = (float)((unsigned char *)image.data)[k + selectedChannel]/255.0f; + k += 2; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + if (selectedChannel == 0) pixelValue = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + else if (selectedChannel == 1) pixelValue = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31); + else if (selectedChannel == 2) pixelValue = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31); + else if (selectedChannel == 3) pixelValue = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + if (selectedChannel == 0) pixelValue = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + else if (selectedChannel == 1) pixelValue = (float)((pixel & 0b0000011111100000) >> 5)*(1.0f/63); + else if (selectedChannel == 2) pixelValue = (float)(pixel & 0b0000000000011111)*(1.0f/31); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + if (selectedChannel == 0) pixelValue = (float)((pixel & 0b1111000000000000) >> 12)*(1.0f/15); + else if (selectedChannel == 1) pixelValue = (float)((pixel & 0b0000111100000000) >> 8)*(1.0f/15); + else if (selectedChannel == 2) pixelValue = (float)((pixel & 0b0000000011110000) >> 4)*(1.0f/15); + else if (selectedChannel == 3) pixelValue = (float)(pixel & 0b0000000000001111)*(1.0f/15); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + pixelValue = (float)((unsigned char *)image.data)[k + selectedChannel]/255.0f; + k += 4; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + pixelValue = (float)((unsigned char *)image.data)[k + selectedChannel]/255.0f; + k += 3; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + pixelValue = ((float *)image.data)[k]; + k += 1; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + pixelValue = ((float *)image.data)[k + selectedChannel]; + k += 3; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + pixelValue = ((float *)image.data)[k + selectedChannel]; + k += 4; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R16: + { + pixelValue = HalfToFloat(((unsigned short *)image.data)[k]); + k += 1; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16: + { + pixelValue = HalfToFloat(((unsigned short *)image.data)[k+selectedChannel]); + k += 3; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + { + pixelValue = HalfToFloat(((unsigned short *)image.data)[k + selectedChannel]); + k += 4; + + } break; + default: break; + } + + pixels[i] = (unsigned char)(pixelValue*255); + } + } + + result.data = pixels; + + return result; +} + +// Resize and image to new size using Nearest-Neighbor scaling algorithm +void ImageResizeNN(Image *image, int newWidth, int newHeight) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); + + // EDIT: added +1 to account for an early rounding problem + int xRatio = (int)((image->width << 16)/newWidth) + 1; + int yRatio = (int)((image->height << 16)/newHeight) + 1; + + int x2 = 0; + int y2 = 0; + for (int y = 0; y < newHeight; y++) + { + for (int x = 0; x < newWidth; x++) + { + x2 = ((x*xRatio) >> 16); + y2 = ((y*yRatio) >> 16); + + output[(y*newWidth) + x] = pixels[(y2*image->width) + x2] ; + } + } + + int format = image->format; + + RL_FREE(image->data); + + image->data = output; + image->width = newWidth; + image->height = newHeight; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); // Reformat 32bit RGBA image to original format + + UnloadImageColors(pixels); +} + +// Resize and image to new size +// NOTE: Uses stb default scaling filters (both bicubic): +// STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +// STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL (high-quality Catmull-Rom) +void ImageResize(Image *image, int newWidth, int newHeight) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + // Check if fast path can be used on image scaling + // It can be for 8 bit per channel images with 1 to 4 channels per pixel + if ((image->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) || + (image->format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) || + (image->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) || + (image->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *output = (unsigned char *)RL_MALLOC(newWidth*newHeight*bytesPerPixel); + + switch (image->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: stbir_resize_uint8_linear((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, (stbir_pixel_layout)1); break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: stbir_resize_uint8_linear((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, (stbir_pixel_layout)2); break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: stbir_resize_uint8_linear((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, (stbir_pixel_layout)3); break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: stbir_resize_uint8_linear((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, (stbir_pixel_layout)4); break; + default: break; + } + + RL_FREE(image->data); + image->data = output; + image->width = newWidth; + image->height = newHeight; + } + else + { + // Get data as Color pixels array to work with it + Color *pixels = LoadImageColors(*image); + Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); + + // NOTE: Color data is cast to (unsigned char *), there shouldn't been any problem... + stbir_resize_uint8_linear((unsigned char *)pixels, image->width, image->height, 0, (unsigned char *)output, newWidth, newHeight, 0, (stbir_pixel_layout)4); + + int format = image->format; + + UnloadImageColors(pixels); + RL_FREE(image->data); + + image->data = output; + image->width = newWidth; + image->height = newHeight; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); // Reformat 32bit RGBA image to original format + } +} + +// Resize canvas and fill with color +// NOTE: Resize offset is relative to the top-left corner of the original image +void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else if ((newWidth != image->width) || (newHeight != image->height)) + { + Rectangle srcRec = { 0, 0, (float)image->width, (float)image->height }; + Vector2 dstPos = { (float)offsetX, (float)offsetY }; + + if (offsetX < 0) + { + srcRec.x = (float)-offsetX; + srcRec.width += (float)offsetX; + dstPos.x = 0; + } + else if ((offsetX + image->width) > newWidth) srcRec.width = (float)(newWidth - offsetX); + + if (offsetY < 0) + { + srcRec.y = (float)-offsetY; + srcRec.height += (float)offsetY; + dstPos.y = 0; + } + else if ((offsetY + image->height) > newHeight) srcRec.height = (float)(newHeight - offsetY); + + if (newWidth < srcRec.width) srcRec.width = (float)newWidth; + if (newHeight < srcRec.height) srcRec.height = (float)newHeight; + + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *resizedData = (unsigned char *)RL_CALLOC(newWidth*newHeight*bytesPerPixel, 1); + + // Fill resized canvas with fill color + // Set first pixel with image->format + SetPixelColor(resizedData, fill, image->format); + + // Fill remaining bytes of first row + for (int x = 1; x < newWidth; x++) + { + memcpy(resizedData + x*bytesPerPixel, resizedData, bytesPerPixel); + } + // Copy the first row into the other rows + for (int y = 1; y < newHeight; y++) + { + memcpy(resizedData + y*newWidth*bytesPerPixel, resizedData, newWidth*bytesPerPixel); + } + + // Copy old image to resized canvas + int dstOffsetSize = ((int)dstPos.y*newWidth + (int)dstPos.x)*bytesPerPixel; + + for (int y = 0; y < (int)srcRec.height; y++) + { + memcpy(resizedData + dstOffsetSize, ((unsigned char *)image->data) + ((y + (int)srcRec.y)*image->width + (int)srcRec.x)*bytesPerPixel, (int)srcRec.width*bytesPerPixel); + dstOffsetSize += (newWidth*bytesPerPixel); + } + + RL_FREE(image->data); + image->data = resizedData; + image->width = newWidth; + image->height = newHeight; + } +} + +// Convert image to POT (power-of-two) +// NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) +void ImageToPOT(Image *image, Color fill) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + // Calculate next power-of-two values + // NOTE: Add the required amount of pixels at the right and bottom sides of image... + int potWidth = (int)powf(2, ceilf(logf((float)image->width)/logf(2))); + int potHeight = (int)powf(2, ceilf(logf((float)image->height)/logf(2))); + + // Check if POT texture generation is required (if texture is not already POT) + if ((potWidth != image->width) || (potHeight != image->height)) ImageResizeCanvas(image, potWidth, potHeight, 0, 0, fill); +} + +// Crop image depending on alpha value +// NOTE: Threshold is defined as a percentage: 0.0f -> 1.0f +void ImageAlphaCrop(Image *image, float threshold) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Rectangle crop = GetImageAlphaBorder(*image, threshold); + + // Crop if rectangle is valid + if (((int)crop.width != 0) && ((int)crop.height != 0)) ImageCrop(image, crop); +} + +// Clear alpha channel to desired color +// NOTE: Threshold defines the alpha limit, 0.0f to 1.0f +void ImageAlphaClear(Image *image, Color color, float threshold) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + switch (image->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + unsigned char thresholdValue = (unsigned char)(threshold*255.0f); + for (int i = 1; i < image->width*image->height*2; i += 2) + { + if (((unsigned char *)image->data)[i] <= thresholdValue) + { + ((unsigned char *)image->data)[i - 1] = color.r; + ((unsigned char *)image->data)[i] = color.a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned char thresholdValue = ((threshold < 0.5f)? 0 : 1); + + unsigned char r = (unsigned char)(round((float)color.r*31.0f)); + unsigned char g = (unsigned char)(round((float)color.g*31.0f)); + unsigned char b = (unsigned char)(round((float)color.b*31.0f)); + unsigned char a = (color.a < 128)? 0 : 1; + + for (int i = 0; i < image->width*image->height; i++) + { + if ((((unsigned short *)image->data)[i] & 0b0000000000000001) <= thresholdValue) + { + ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned char thresholdValue = (unsigned char)(threshold*15.0f); + + unsigned char r = (unsigned char)(round((float)color.r*15.0f)); + unsigned char g = (unsigned char)(round((float)color.g*15.0f)); + unsigned char b = (unsigned char)(round((float)color.b*15.0f)); + unsigned char a = (unsigned char)(round((float)color.a*15.0f)); + + for (int i = 0; i < image->width*image->height; i++) + { + if ((((unsigned short *)image->data)[i] & 0x000f) <= thresholdValue) + { + ((unsigned short *)image->data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + unsigned char thresholdValue = (unsigned char)(threshold*255.0f); + for (int i = 3; i < image->width*image->height*4; i += 4) + { + if (((unsigned char *)image->data)[i] <= thresholdValue) + { + ((unsigned char *)image->data)[i - 3] = color.r; + ((unsigned char *)image->data)[i - 2] = color.g; + ((unsigned char *)image->data)[i - 1] = color.b; + ((unsigned char *)image->data)[i] = color.a; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + for (int i = 3; i < image->width*image->height*4; i += 4) + { + if (((float *)image->data)[i] <= threshold) + { + ((float *)image->data)[i - 3] = (float)color.r/255.0f; + ((float *)image->data)[i - 2] = (float)color.g/255.0f; + ((float *)image->data)[i - 1] = (float)color.b/255.0f; + ((float *)image->data)[i] = (float)color.a/255.0f; + } + } + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + { + for (int i = 3; i < image->width*image->height*4; i += 4) + { + if (HalfToFloat(((unsigned short *)image->data)[i]) <= threshold) + { + ((unsigned short *)image->data)[i - 3] = FloatToHalf((float)color.r/255.0f); + ((unsigned short *)image->data)[i - 2] = FloatToHalf((float)color.g/255.0f); + ((unsigned short *)image->data)[i - 1] = FloatToHalf((float)color.b/255.0f); + ((unsigned short *)image->data)[i] = FloatToHalf((float)color.a/255.0f); + } + } + } break; + default: break; + } + } +} + +// Apply alpha mask to image +// NOTE 1: Returned image is GRAY_ALPHA (16bit) or RGBA (32bit) +// NOTE 2: alphaMask should be same size as image +void ImageAlphaMask(Image *image, Image alphaMask) +{ + if ((image->width != alphaMask.width) || (image->height != alphaMask.height)) + { + TRACELOG(LOG_WARNING, "IMAGE: Alpha mask must be same size as image"); + } + else if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(LOG_WARNING, "IMAGE: Alpha mask can not be applied to compressed data formats"); + } + else + { + // Force mask to be Grayscale + Image mask = ImageCopy(alphaMask); + if (mask.format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) ImageFormat(&mask, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); + + // In case image is only grayscale, add alpha channel + if (image->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + unsigned char *data = (unsigned char *)RL_MALLOC(image->width*image->height*2); + + // Apply alpha mask to alpha channel + for (int i = 0, k = 0; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 2) + { + data[k] = ((unsigned char *)image->data)[i]; + data[k + 1] = ((unsigned char *)mask.data)[i]; + } + + RL_FREE(image->data); + image->data = data; + image->format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + } + else + { + // Convert image to RGBA + if (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); + + // Apply alpha mask to alpha channel + for (int i = 0, k = 3; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 4) + { + ((unsigned char *)image->data)[k] = ((unsigned char *)mask.data)[i]; + } + } + + UnloadImage(mask); + } +} + +// Premultiply alpha channel +void ImageAlphaPremultiply(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + float alpha = 0.0f; + Color *pixels = LoadImageColors(*image); + + for (int i = 0; i < image->width*image->height; i++) + { + if (pixels[i].a == 0) + { + pixels[i].r = 0; + pixels[i].g = 0; + pixels[i].b = 0; + } + else if (pixels[i].a < 255) + { + alpha = (float)pixels[i].a/255.0f; + pixels[i].r = (unsigned char)((float)pixels[i].r*alpha); + pixels[i].g = (unsigned char)((float)pixels[i].g*alpha); + pixels[i].b = (unsigned char)((float)pixels[i].b*alpha); + } + } + + RL_FREE(image->data); + + int format = image->format; + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Apply box blur to image +void ImageBlurGaussian(Image *image, int blurSize) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + ImageAlphaPremultiply(image); + + Color *pixels = LoadImageColors(*image); + + // Loop switches between pixelsCopy1 and pixelsCopy2 + Vector4 *pixelsCopy1 = (Vector4 *)RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); + Vector4 *pixelsCopy2 = (Vector4 *)RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); + + for (int i = 0; i < (image->height*image->width); i++) + { + pixelsCopy1[i].x = pixels[i].r; + pixelsCopy1[i].y = pixels[i].g; + pixelsCopy1[i].z = pixels[i].b; + pixelsCopy1[i].w = pixels[i].a; + } + + // Repeated convolution of rectangular window signal by itself converges to a gaussian distribution + for (int j = 0; j < GAUSSIAN_BLUR_ITERATIONS; j++) + { + // Horizontal motion blur + for (int row = 0; row < image->height; row++) + { + float avgR = 0.0f; + float avgG = 0.0f; + float avgB = 0.0f; + float avgAlpha = 0.0f; + int convolutionSize = blurSize; + + for (int i = 0; i < blurSize; i++) + { + avgR += pixelsCopy1[row*image->width + i].x; + avgG += pixelsCopy1[row*image->width + i].y; + avgB += pixelsCopy1[row*image->width + i].z; + avgAlpha += pixelsCopy1[row*image->width + i].w; + } + + for (int x = 0; x < image->width; x++) + { + if (x-blurSize-1 >= 0) + { + avgR -= pixelsCopy1[row*image->width + x-blurSize-1].x; + avgG -= pixelsCopy1[row*image->width + x-blurSize-1].y; + avgB -= pixelsCopy1[row*image->width + x-blurSize-1].z; + avgAlpha -= pixelsCopy1[row*image->width + x-blurSize-1].w; + convolutionSize--; + } + + if (x+blurSize < image->width) + { + avgR += pixelsCopy1[row*image->width + x+blurSize].x; + avgG += pixelsCopy1[row*image->width + x+blurSize].y; + avgB += pixelsCopy1[row*image->width + x+blurSize].z; + avgAlpha += pixelsCopy1[row*image->width + x+blurSize].w; + convolutionSize++; + } + + pixelsCopy2[row*image->width + x].x = avgR/convolutionSize; + pixelsCopy2[row*image->width + x].y = avgG/convolutionSize; + pixelsCopy2[row*image->width + x].z = avgB/convolutionSize; + pixelsCopy2[row*image->width + x].w = avgAlpha/convolutionSize; + } + } + + // Vertical motion blur + for (int col = 0; col < image->width; col++) + { + float avgR = 0.0f; + float avgG = 0.0f; + float avgB = 0.0f; + float avgAlpha = 0.0f; + int convolutionSize = blurSize; + + for (int i = 0; i < blurSize; i++) + { + avgR += pixelsCopy2[i*image->width + col].x; + avgG += pixelsCopy2[i*image->width + col].y; + avgB += pixelsCopy2[i*image->width + col].z; + avgAlpha += pixelsCopy2[i*image->width + col].w; + } + + for (int y = 0; y < image->height; y++) + { + if (y-blurSize-1 >= 0) + { + avgR -= pixelsCopy2[(y-blurSize-1)*image->width + col].x; + avgG -= pixelsCopy2[(y-blurSize-1)*image->width + col].y; + avgB -= pixelsCopy2[(y-blurSize-1)*image->width + col].z; + avgAlpha -= pixelsCopy2[(y-blurSize-1)*image->width + col].w; + convolutionSize--; + } + if (y+blurSize < image->height) + { + avgR += pixelsCopy2[(y+blurSize)*image->width + col].x; + avgG += pixelsCopy2[(y+blurSize)*image->width + col].y; + avgB += pixelsCopy2[(y+blurSize)*image->width + col].z; + avgAlpha += pixelsCopy2[(y+blurSize)*image->width + col].w; + convolutionSize++; + } + + pixelsCopy1[y*image->width + col].x = (unsigned char) (avgR/convolutionSize); + pixelsCopy1[y*image->width + col].y = (unsigned char) (avgG/convolutionSize); + pixelsCopy1[y*image->width + col].z = (unsigned char) (avgB/convolutionSize); + pixelsCopy1[y*image->width + col].w = (unsigned char) (avgAlpha/convolutionSize); + } + } + } + + // Reverse premultiply + for (int i = 0; i < (image->width)*(image->height); i++) + { + if (pixelsCopy1[i].w == 0.0f) + { + pixels[i].r = 0; + pixels[i].g = 0; + pixels[i].b = 0; + pixels[i].a = 0; + } + else if (pixelsCopy1[i].w <= 255.0f) + { + float alpha = (float)pixelsCopy1[i].w/255.0f; + pixels[i].r = (unsigned char)fminf((float)pixelsCopy1[i].x/alpha, 255.0); + pixels[i].g = (unsigned char)fminf((float)pixelsCopy1[i].y/alpha, 255.0); + pixels[i].b = (unsigned char)fminf((float)pixelsCopy1[i].z/alpha, 255.0); + pixels[i].a = (unsigned char) pixelsCopy1[i].w; + } + } + + int format = image->format; + RL_FREE(image->data); + RL_FREE(pixelsCopy1); + RL_FREE(pixelsCopy2); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Apply custom square convolution kernel to image +// NOTE: The convolution kernel matrix is expected to be square +void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize) +{ + if ((image->data == NULL) || (image->width == 0) || (image->height == 0) || kernel == NULL) return; + + int kernelWidth = (int)sqrtf((float)kernelSize); + + if (kernelWidth*kernelWidth != kernelSize) + { + TRACELOG(LOG_WARNING, "IMAGE: Convolution kernel must be square to be applied"); + return; + } + + Color *pixels = LoadImageColors(*image); + + Vector4 *imageCopy2 = (Vector4 *)RL_MALLOC((image->height)*(image->width)*sizeof(Vector4)); + Vector4 *temp = (Vector4 *)RL_MALLOC(kernelSize*sizeof(Vector4)); + + for (int i = 0; i < kernelSize; i++) + { + temp[i].x = 0.0f; + temp[i].y = 0.0f; + temp[i].z = 0.0f; + temp[i].w = 0.0f; + } + + float rRes = 0.0f; + float gRes = 0.0f; + float bRes = 0.0f; + float aRes = 0.0f; + + int startRange = 0, endRange = 0; + + if (kernelWidth%2 == 0) + { + startRange = -kernelWidth/2; + endRange = kernelWidth/2; + } + else + { + startRange = -kernelWidth/2; + endRange = kernelWidth/2 + 1; + } + + for (int x = 0; x < image->height; x++) + { + for (int y = 0; y < image->width; y++) + { + for (int xk = startRange; xk < endRange; xk++) + { + for (int yk = startRange; yk < endRange; yk++) + { + int xkabs = xk + kernelWidth/2; + int ykabs = yk + kernelWidth/2; + unsigned int imgindex = image->width*(x + xk) + (y + yk); + + if (imgindex >= (unsigned int)(image->width*image->height)) + { + temp[kernelWidth*xkabs + ykabs].x = 0.0f; + temp[kernelWidth*xkabs + ykabs].y = 0.0f; + temp[kernelWidth*xkabs + ykabs].z = 0.0f; + temp[kernelWidth*xkabs + ykabs].w = 0.0f; + } + else + { + temp[kernelWidth*xkabs + ykabs].x = ((float)pixels[imgindex].r)/255.0f*kernel[kernelWidth*xkabs + ykabs]; + temp[kernelWidth*xkabs + ykabs].y = ((float)pixels[imgindex].g)/255.0f*kernel[kernelWidth*xkabs + ykabs]; + temp[kernelWidth*xkabs + ykabs].z = ((float)pixels[imgindex].b)/255.0f*kernel[kernelWidth*xkabs + ykabs]; + temp[kernelWidth*xkabs + ykabs].w = ((float)pixels[imgindex].a)/255.0f*kernel[kernelWidth*xkabs + ykabs]; + } + } + } + + for (int i = 0; i < kernelSize; i++) + { + rRes += temp[i].x; + gRes += temp[i].y; + bRes += temp[i].z; + aRes += temp[i].w; + } + + if (rRes < 0.0f) rRes = 0.0f; + if (gRes < 0.0f) gRes = 0.0f; + if (bRes < 0.0f) bRes = 0.0f; + + if (rRes > 1.0f) rRes = 1.0f; + if (gRes > 1.0f) gRes = 1.0f; + if (bRes > 1.0f) bRes = 1.0f; + + imageCopy2[image->width*x + y].x = rRes; + imageCopy2[image->width*x + y].y = gRes; + imageCopy2[image->width*x + y].z = bRes; + imageCopy2[image->width*x + y].w = aRes; + + rRes = 0.0f; + gRes = 0.0f; + bRes = 0.0f; + aRes = 0.0f; + + for (int i = 0; i < kernelSize; i++) + { + temp[i].x = 0.0f; + temp[i].y = 0.0f; + temp[i].z = 0.0f; + temp[i].w = 0.0f; + } + } + } + + for (int i = 0; i < (image->width*image->height); i++) + { + float alpha = (float)imageCopy2[i].w; + + pixels[i].r = (unsigned char)((imageCopy2[i].x)*255.0f); + pixels[i].g = (unsigned char)((imageCopy2[i].y)*255.0f); + pixels[i].b = (unsigned char)((imageCopy2[i].z)*255.0f); + pixels[i].a = (unsigned char)((alpha)*255.0f); + } + + int format = image->format; + RL_FREE(image->data); + RL_FREE(imageCopy2); + RL_FREE(temp); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + ImageFormat(image, format); +} + +// Generate all mipmap levels for a provided image +// NOTE 1: Supports POT and NPOT images +// NOTE 2: image.data is scaled to include mipmap levels +// NOTE 3: Mipmaps format is the same as base image +void ImageMipmaps(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + int mipCount = 1; // Required mipmap levels count (including base level) + int mipWidth = image->width; // Base image width + int mipHeight = image->height; // Base image height + int mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); // Image data size (in bytes) + + // Count mipmap levels required + while ((mipWidth != 1) || (mipHeight != 1)) + { + if (mipWidth != 1) mipWidth /= 2; + if (mipHeight != 1) mipHeight /= 2; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + + TRACELOG(LOG_DEBUG, "IMAGE: Next mipmap level: %i x %i - current size %i", mipWidth, mipHeight, mipSize); + + mipCount++; + mipSize += GetPixelDataSize(mipWidth, mipHeight, image->format); // Add mipmap size (in bytes) + } + + if (image->mipmaps < mipCount) + { + // Create second buffer and copy data manually to it + void *temp = RL_CALLOC(mipSize, 1); + memcpy(temp, image->data, GetPixelDataSize(image->width, image->height, image->format)); + RL_FREE(image->data); + image->data = temp; + + // Pointer to allocated memory point where store next mipmap level data + unsigned char *nextmip = (unsigned char *)image->data; + + mipWidth = image->width; + mipHeight = image->height; + mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); + Image imCopy = ImageCopy(*image); + + for (int i = 1; i < mipCount; i++) + { + nextmip += mipSize; + + mipWidth /= 2; + mipHeight /= 2; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + + mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); + + if (i < image->mipmaps) continue; + + TRACELOG(LOG_DEBUG, "IMAGE: Generating mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip); + ImageResize(&imCopy, mipWidth, mipHeight); // Uses internally Mitchell cubic downscale filter + memcpy(nextmip, imCopy.data, mipSize); + } + + UnloadImage(imCopy); + + image->mipmaps = mipCount; + } + else TRACELOG(LOG_WARNING, "IMAGE: Mipmaps already available"); +} + +// Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +// NOTE: In case selected bpp do not represent a known 16bit format, +// dithered data is stored in the LSB part of the unsigned short +void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(LOG_WARNING, "IMAGE: Compressed data formats can not be dithered"); + return; + } + + if ((rBpp + gBpp + bBpp + aBpp) > 16) + { + TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (rBpp+gBpp+bBpp+aBpp)); + } + else + { + Color *pixels = LoadImageColors(*image); + + RL_FREE(image->data); // free old image data + + if ((image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) + { + TRACELOG(LOG_WARNING, "IMAGE: Format is already 16bpp or lower, dithering could have no effect"); + } + + // Define new image format, check if desired bpp match internal known format + if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; + else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; + else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; + else + { + image->format = 0; + TRACELOG(LOG_WARNING, "IMAGE: Unsupported dithered OpenGL internal format: %ibpp (R%iG%iB%iA%i)", (rBpp+gBpp+bBpp+aBpp), rBpp, gBpp, bBpp, aBpp); + } + + // NOTE: Storing the dithered data as unsigned short (16bpp) + image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); + + Color oldPixel = WHITE; + Color newPixel = WHITE; + + int rError = 0; + int gError = 0; + int bError = 0; + unsigned short rPixel = 0; // Used for 16bit pixel composition + unsigned short gPixel = 0; + unsigned short bPixel = 0; + unsigned short aPixel = 0; + + #define MIN(a,b) (((a)<(b))?(a):(b)) + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + oldPixel = pixels[y*image->width + x]; + + // NOTE: New pixel obtained by bits truncate, it would be better to round values (check ImageFormat()) + newPixel.r = oldPixel.r >> (8 - rBpp); // R bits + newPixel.g = oldPixel.g >> (8 - gBpp); // G bits + newPixel.b = oldPixel.b >> (8 - bBpp); // B bits + newPixel.a = oldPixel.a >> (8 - aBpp); // A bits (not used on dithering) + + // NOTE: Error must be computed between new and old pixel but using same number of bits, + // to know how much color precision has been lost + rError = (int)oldPixel.r - (int)(newPixel.r << (8 - rBpp)); + gError = (int)oldPixel.g - (int)(newPixel.g << (8 - gBpp)); + bError = (int)oldPixel.b - (int)(newPixel.b << (8 - bBpp)); + + pixels[y*image->width + x] = newPixel; + + // NOTE: Some cases are out of the array and should be ignored + if (x < (image->width - 1)) + { + pixels[y*image->width + x+1].r = MIN((int)pixels[y*image->width + x+1].r + (int)((float)rError*7.0f/16), 0xff); + pixels[y*image->width + x+1].g = MIN((int)pixels[y*image->width + x+1].g + (int)((float)gError*7.0f/16), 0xff); + pixels[y*image->width + x+1].b = MIN((int)pixels[y*image->width + x+1].b + (int)((float)bError*7.0f/16), 0xff); + } + + if ((x > 0) && (y < (image->height - 1))) + { + pixels[(y+1)*image->width + x-1].r = MIN((int)pixels[(y+1)*image->width + x-1].r + (int)((float)rError*3.0f/16), 0xff); + pixels[(y+1)*image->width + x-1].g = MIN((int)pixels[(y+1)*image->width + x-1].g + (int)((float)gError*3.0f/16), 0xff); + pixels[(y+1)*image->width + x-1].b = MIN((int)pixels[(y+1)*image->width + x-1].b + (int)((float)bError*3.0f/16), 0xff); + } + + if (y < (image->height - 1)) + { + pixels[(y+1)*image->width + x].r = MIN((int)pixels[(y+1)*image->width + x].r + (int)((float)rError*5.0f/16), 0xff); + pixels[(y+1)*image->width + x].g = MIN((int)pixels[(y+1)*image->width + x].g + (int)((float)gError*5.0f/16), 0xff); + pixels[(y+1)*image->width + x].b = MIN((int)pixels[(y+1)*image->width + x].b + (int)((float)bError*5.0f/16), 0xff); + } + + if ((x < (image->width - 1)) && (y < (image->height - 1))) + { + pixels[(y+1)*image->width + x+1].r = MIN((int)pixels[(y+1)*image->width + x+1].r + (int)((float)rError*1.0f/16), 0xff); + pixels[(y+1)*image->width + x+1].g = MIN((int)pixels[(y+1)*image->width + x+1].g + (int)((float)gError*1.0f/16), 0xff); + pixels[(y+1)*image->width + x+1].b = MIN((int)pixels[(y+1)*image->width + x+1].b + (int)((float)bError*1.0f/16), 0xff); + } + + rPixel = (unsigned short)newPixel.r; + gPixel = (unsigned short)newPixel.g; + bPixel = (unsigned short)newPixel.b; + aPixel = (unsigned short)newPixel.a; + + ((unsigned short *)image->data)[y*image->width + x] = (rPixel << (gBpp + bBpp + aBpp)) | (gPixel << (bBpp + aBpp)) | (bPixel << aBpp) | aPixel; + } + } + + UnloadImageColors(pixels); + } +} + +// Flip image vertically +void ImageFlipVertical(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int i = (image->height - 1), offsetSize = 0; i >= 0; i--) + { + memcpy(flippedData + offsetSize, ((unsigned char *)image->data) + i*image->width*bytesPerPixel, image->width*bytesPerPixel); + offsetSize += image->width*bytesPerPixel; + } + + RL_FREE(image->data); + image->data = flippedData; + } +} + +// Flip image horizontally +void ImageFlipHorizontal(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + // OPTION 1: Move pixels with memcpy() + //memcpy(flippedData + (y*image->width + x)*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + (image->width - 1 - x))*bytesPerPixel, bytesPerPixel); + + // OPTION 2: Copy data pixel by pixel + for (int i = 0; i < bytesPerPixel; i++) flippedData[(y*image->width + x)*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + (image->width - 1 - x))*bytesPerPixel + i]; + } + } + + RL_FREE(image->data); + image->data = flippedData; + + /* + // OPTION 3: Faster implementation (specific for 32bit pixels) + // NOTE: It does not require additional allocations + uint32_t *ptr = (uint32_t *)image->data; + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width/2; x++) + { + uint32_t backup = ptr[y*image->width + x]; + ptr[y*image->width + x] = ptr[y*image->width + (image->width - 1 - x)]; + ptr[y*image->width + (image->width - 1 - x)] = backup; + } + } + */ + } +} + +// Rotate image in degrees +void ImageRotate(Image *image, int degrees) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + float rad = degrees*PI/180.0f; + float sinRadius = sinf(rad); + float cosRadius = cosf(rad); + + int width = (int)(fabsf(image->width*cosRadius) + fabsf(image->height*sinRadius)); + int height = (int)(fabsf(image->height*cosRadius) + fabsf(image->width*sinRadius)); + + int dataSize = GetPixelDataSize(width, height, image->format); + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *rotatedData = (unsigned char *)RL_CALLOC(dataSize, 1); + + if (rotatedData != NULL) + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float oldX = ((x - width/2.0f)*cosRadius + (y - height/2.0f)*sinRadius) + image->width/2.0f; + float oldY = ((y - height/2.0f)*cosRadius - (x - width/2.0f)*sinRadius) + image->height/2.0f; + + if ((oldX >= 0) && (oldX < image->width) && (oldY >= 0) && (oldY < image->height)) + { + int x1 = (int)floorf(oldX); + int y1 = (int)floorf(oldY); + int x2 = MIN(x1 + 1, image->width - 1); + int y2 = MIN(y1 + 1, image->height - 1); + + float px = oldX - x1; + float py = oldY - y1; + + for (int i = 0; i < bytesPerPixel; i++) + { + float f1 = ((unsigned char *)image->data)[(y1*image->width + x1)*bytesPerPixel + i]; + float f2 = ((unsigned char *)image->data)[(y1*image->width + x2)*bytesPerPixel + i]; + float f3 = ((unsigned char *)image->data)[(y2*image->width + x1)*bytesPerPixel + i]; + float f4 = ((unsigned char *)image->data)[(y2*image->width + x2)*bytesPerPixel + i]; + + float val = f1*(1 - px)*(1 - py) + f2*px*(1 - py) + f3*(1 - px)*py + f4*px*py; + + rotatedData[(y*width + x)*bytesPerPixel + i] = (unsigned char)val; + } + } + } + } + + RL_FREE(image->data); + image->data = rotatedData; + image->width = width; + image->height = height; + } + } +} + +// Rotate image clockwise 90deg +void ImageRotateCW(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + //memcpy(rotatedData + (x*image->height + (image->height - y - 1))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + x)*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) rotatedData[(x*image->height + (image->height - y - 1))*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + x)*bytesPerPixel + i]; + } + } + + RL_FREE(image->data); + image->data = rotatedData; + int width = image->width; + int height = image-> height; + + image->width = height; + image->height = width; + } +} + +// Rotate image counter-clockwise 90deg +void ImageRotateCCW(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + else + { + int bytesPerPixel = GetPixelDataSize(1, 1, image->format); + unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + //memcpy(rotatedData + (x*image->height + y))*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + (image->width - x - 1))*bytesPerPixel, bytesPerPixel); + for (int i = 0; i < bytesPerPixel; i++) rotatedData[(x*image->height + y)*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + (image->width - x - 1))*bytesPerPixel + i]; + } + } + + RL_FREE(image->data); + image->data = rotatedData; + int width = image->width; + int height = image-> height; + + image->width = height; + image->height = width; + } +} + +// Modify image color: tint +void ImageColorTint(Image *image, Color color) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + + for (int i = 0; i < image->width*image->height; i++) + { + unsigned char r = (unsigned char)(((int)pixels[i].r*(int)color.r)/255); + unsigned char g = (unsigned char)(((int)pixels[i].g*(int)color.g)/255); + unsigned char b = (unsigned char)(((int)pixels[i].b*(int)color.b)/255); + unsigned char a = (unsigned char)(((int)pixels[i].a*(int)color.a)/255); + + pixels[i].r = r; + pixels[i].g = g; + pixels[i].b = b; + pixels[i].a = a; + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: invert +void ImageColorInvert(Image *image) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + + for (int i = 0; i < image->width*image->height; i++) + { + pixels[i].r = 255 - pixels[i].r; + pixels[i].g = 255 - pixels[i].g; + pixels[i].b = 255 - pixels[i].b; + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: grayscale +void ImageColorGrayscale(Image *image) +{ + ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); +} + +// Modify image color: contrast +// NOTE: Contrast values between -100 and 100 +void ImageColorContrast(Image *image, int contrast) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (contrast < -100) contrast = -100; + if (contrast > 100) contrast = 100; + + float factor = (float)(100.0f + contrast)/100.0f; + factor *= factor; + + Color *pixels = LoadImageColors(*image); + + for (int i = 0; i < image->width*image->height; i++) + { + float pR = (float)pixels[i].r/255.0f; + pR -= 0.5f; + pR *= factor; + pR += 0.5f; + pR *= 255; + if (pR < 0) pR = 0; + if (pR > 255) pR = 255; + + float pG = (float)pixels[i].g/255.0f; + pG -= 0.5f; + pG *= factor; + pG += 0.5f; + pG *= 255; + if (pG < 0) pG = 0; + if (pG > 255) pG = 255; + + float pB = (float)pixels[i].b/255.0f; + pB -= 0.5f; + pB *= factor; + pB += 0.5f; + pB *= 255; + if (pB < 0) pB = 0; + if (pB > 255) pB = 255; + + pixels[i].r = (unsigned char)pR; + pixels[i].g = (unsigned char)pG; + pixels[i].b = (unsigned char)pB; + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: brightness +// NOTE: Brightness values between -255 and 255 +void ImageColorBrightness(Image *image, int brightness) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + if (brightness < -255) brightness = -255; + if (brightness > 255) brightness = 255; + + Color *pixels = LoadImageColors(*image); + + for (int i = 0; i < image->width*image->height; i++) + { + int cR = pixels[i].r + brightness; + int cG = pixels[i].g + brightness; + int cB = pixels[i].b + brightness; + + if (cR < 0) cR = 1; + if (cR > 255) cR = 255; + + if (cG < 0) cG = 1; + if (cG > 255) cG = 255; + + if (cB < 0) cB = 1; + if (cB > 255) cB = 255; + + pixels[i].r = (unsigned char)cR; + pixels[i].g = (unsigned char)cG; + pixels[i].b = (unsigned char)cB; + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + ImageFormat(image, format); +} + +// Modify image color: replace color +void ImageColorReplace(Image *image, Color color, Color replace) +{ + // Security check to avoid program crash + if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; + + Color *pixels = LoadImageColors(*image); + + for (int i = 0; i < image->width*image->height; i++) + { + if ((pixels[i].r == color.r) && + (pixels[i].g == color.g) && + (pixels[i].b == color.b) && + (pixels[i].a == color.a)) + { + pixels[i].r = replace.r; + pixels[i].g = replace.g; + pixels[i].b = replace.b; + pixels[i].a = replace.a; + } + } + + int format = image->format; + RL_FREE(image->data); + + image->data = pixels; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + + // Only convert back to original format if it supported alpha + if ((format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) || + (format == PIXELFORMAT_UNCOMPRESSED_R5G6B5) || + (format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) || + (format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) || + (format == PIXELFORMAT_UNCOMPRESSED_R16G16B16) || + (format == PIXELFORMAT_COMPRESSED_DXT1_RGB) || + (format == PIXELFORMAT_COMPRESSED_ETC1_RGB) || + (format == PIXELFORMAT_COMPRESSED_ETC2_RGB) || + (format == PIXELFORMAT_COMPRESSED_PVRT_RGB)) ImageFormat(image, format); +} + +// Load color data from image as a Color array (RGBA - 32bit) +// NOTE: Memory allocated should be freed using UnloadImageColors(); +Color *LoadImageColors(Image image) +{ + if ((image.width == 0) || (image.height == 0)) return NULL; // Security check + + Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); + + if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else + { + if ((image.format == PIXELFORMAT_UNCOMPRESSED_R32) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 32bit to 8bit per channel"); + + if ((image.format == PIXELFORMAT_UNCOMPRESSED_R16) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R16G16B16) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 16bit to 8bit per channel"); + + for (int i = 0, k = 0; i < image.width*image.height; i++) + { + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + pixels[i].r = ((unsigned char *)image.data)[i]; + pixels[i].g = ((unsigned char *)image.data)[i]; + pixels[i].b = ((unsigned char *)image.data)[i]; + pixels[i].a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k]; + pixels[i].b = ((unsigned char *)image.data)[k]; + pixels[i].a = ((unsigned char *)image.data)[k + 1]; + + k += 2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31)); + pixels[i].a = (unsigned char)((pixel & 0b0000000000000001)*255); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63)); + pixels[i].b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31)); + pixels[i].a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15)); + pixels[i].a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15)); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k + 1]; + pixels[i].b = ((unsigned char *)image.data)[k + 2]; + pixels[i].a = ((unsigned char *)image.data)[k + 3]; + + k += 4; + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + pixels[i].r = (unsigned char)((unsigned char *)image.data)[k]; + pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1]; + pixels[i].b = (unsigned char)((unsigned char *)image.data)[k + 2]; + pixels[i].a = 255; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = 0; + pixels[i].b = 0; + pixels[i].a = 255; + + k += 1; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); + pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f); + pixels[i].a = 255; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); + pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); + pixels[i].b = (unsigned char)(((float *)image.data)[k + 2]*255.0f); + pixels[i].a = (unsigned char)(((float *)image.data)[k + 3]*255.0f); + + k += 4; + } break; + case PIXELFORMAT_UNCOMPRESSED_R16: + { + pixels[i].r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k])*255.0f); + pixels[i].g = 0; + pixels[i].b = 0; + pixels[i].a = 255; + + k += 1; + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16: + { + pixels[i].r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k])*255.0f); + pixels[i].g = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k + 1])*255.0f); + pixels[i].b = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k + 2])*255.0f); + pixels[i].a = 255; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + { + pixels[i].r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k])*255.0f); + pixels[i].g = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k + 1])*255.0f); + pixels[i].b = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k + 2])*255.0f); + pixels[i].a = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[k + 3])*255.0f); + + k += 4; + } break; + default: break; + } + } + } + + return pixels; +} + +// Load colors palette from image as a Color array (RGBA - 32bit) +// NOTE: Memory allocated should be freed using UnloadImagePalette() +Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount) +{ + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) + + int palCount = 0; + Color *palette = NULL; + Color *pixels = LoadImageColors(image); + + if (pixels != NULL) + { + palette = (Color *)RL_MALLOC(maxPaletteSize*sizeof(Color)); + + for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK + + for (int i = 0; i < image.width*image.height; i++) + { + if (pixels[i].a > 0) + { + bool colorInPalette = false; + + // Check if the color is already on palette + for (int j = 0; j < maxPaletteSize; j++) + { + if (COLOR_EQUAL(pixels[i], palette[j])) + { + colorInPalette = true; + break; + } + } + + // Store color if not on the palette + if (!colorInPalette) + { + palette[palCount] = pixels[i]; // Add pixels[i] to palette + palCount++; + + // Reached the limit of colors supported by palette + if (palCount >= maxPaletteSize) + { + i = image.width*image.height; // Finish palette get + TRACELOG(LOG_WARNING, "IMAGE: Palette is greater than %i colors", maxPaletteSize); + } + } + } + } + + UnloadImageColors(pixels); + } + + *colorCount = palCount; + + return palette; +} + +// Unload color data loaded with LoadImageColors() +void UnloadImageColors(Color *colors) +{ + RL_FREE(colors); +} + +// Unload colors palette loaded with LoadImagePalette() +void UnloadImagePalette(Color *colors) +{ + RL_FREE(colors); +} + +// Get image alpha border rectangle +// NOTE: Threshold is defined as a percentage: 0.0f -> 1.0f +Rectangle GetImageAlphaBorder(Image image, float threshold) +{ + Rectangle crop = { 0 }; + + Color *pixels = LoadImageColors(image); + + if (pixels != NULL) + { + int xMin = 65536; // Define a big enough number + int xMax = 0; + int yMin = 65536; + int yMax = 0; + + for (int y = 0; y < image.height; y++) + { + for (int x = 0; x < image.width; x++) + { + if (pixels[y*image.width + x].a > (unsigned char)(threshold*255.0f)) + { + if (x < xMin) xMin = x; + if (x > xMax) xMax = x; + if (y < yMin) yMin = y; + if (y > yMax) yMax = y; + } + } + } + + // Check for empty blank image + if ((xMin != 65536) && (xMax != 65536)) + { + crop = (Rectangle){ (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; + } + + UnloadImageColors(pixels); + } + + return crop; +} + +// Get image pixel color at (x, y) position +Color GetImageColor(Image image, int x, int y) +{ + Color color = { 0 }; + + if ((x >=0) && (x < image.width) && (y >= 0) && (y < image.height)) + { + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + color.r = ((unsigned char *)image.data)[y*image.width + x]; + color.g = ((unsigned char *)image.data)[y*image.width + x]; + color.b = ((unsigned char *)image.data)[y*image.width + x]; + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + color.r = ((unsigned char *)image.data)[(y*image.width + x)*2]; + color.g = ((unsigned char *)image.data)[(y*image.width + x)*2]; + color.b = ((unsigned char *)image.data)[(y*image.width + x)*2]; + color.a = ((unsigned char *)image.data)[(y*image.width + x)*2 + 1]; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[y*image.width + x]; + + color.r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + color.g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31)); + color.b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31)); + color.a = (unsigned char)((pixel & 0b0000000000000001)*255); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[y*image.width + x]; + + color.r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + color.g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63)); + color.b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31)); + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[y*image.width + x]; + + color.r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15)); + color.g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15)); + color.b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15)); + color.a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15)); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + color.r = ((unsigned char *)image.data)[(y*image.width + x)*4]; + color.g = ((unsigned char *)image.data)[(y*image.width + x)*4 + 1]; + color.b = ((unsigned char *)image.data)[(y*image.width + x)*4 + 2]; + color.a = ((unsigned char *)image.data)[(y*image.width + x)*4 + 3]; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + color.r = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3]; + color.g = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3 + 1]; + color.b = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3 + 2]; + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + color.r = (unsigned char)(((float *)image.data)[y*image.width + x]*255.0f); + color.g = 0; + color.b = 0; + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + color.r = (unsigned char)(((float *)image.data)[(y*image.width + x)*3]*255.0f); + color.g = (unsigned char)(((float *)image.data)[(y*image.width + x)*3 + 1]*255.0f); + color.b = (unsigned char)(((float *)image.data)[(y*image.width + x)*3 + 2]*255.0f); + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + color.r = (unsigned char)(((float *)image.data)[(y*image.width + x)*4]*255.0f); + color.g = (unsigned char)(((float *)image.data)[(y*image.width + x)*4 + 1]*255.0f); + color.b = (unsigned char)(((float *)image.data)[(y*image.width + x)*4 + 2]*255.0f); + color.a = (unsigned char)(((float *)image.data)[(y*image.width + x)*4 + 3]*255.0f); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R16: + { + color.r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[y*image.width + x])*255.0f); + color.g = 0; + color.b = 0; + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16: + { + color.r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*3])*255.0f); + color.g = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*3 + 1])*255.0f); + color.b = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*3 + 2])*255.0f); + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + { + color.r = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*4])*255.0f); + color.g = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*4 + 1])*255.0f); + color.b = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*4 + 2])*255.0f); + color.a = (unsigned char)(HalfToFloat(((unsigned short *)image.data)[(y*image.width + x)*4 + 3])*255.0f); + + } break; + default: TRACELOG(LOG_WARNING, "Compressed image format does not support color reading"); break; + } + } + else TRACELOG(LOG_WARNING, "Requested image pixel (%i, %i) out of bounds", x, y); + + return color; +} + +//------------------------------------------------------------------------------------ +// Image drawing functions +//------------------------------------------------------------------------------------ +// Clear image background with given color +void ImageClearBackground(Image *dst, Color color) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; + + // Fill in first pixel based on image format + ImageDrawPixel(dst, 0, 0, color); + + unsigned char *pSrcPixel = (unsigned char *)dst->data; + int bytesPerPixel = GetPixelDataSize(1, 1, dst->format); + int totalPixels = dst->width*dst->height; + + // Repeat the first pixel data throughout the image, + // doubling the pixels copied on each iteration + for (int i = 1; i < totalPixels; i *= 2) + { + int pixelsToCopy = MIN(i, totalPixels - i); + memcpy(pSrcPixel + i*bytesPerPixel, pSrcPixel, pixelsToCopy*bytesPerPixel); + } +} + +// Draw pixel within an image +// NOTE: Compressed image formats not supported +void ImageDrawPixel(Image *dst, int x, int y, Color color) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (x < 0) || (x >= dst->width) || (y < 0) || (y >= dst->height)) return; + + switch (dst->format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dst->data)[y*dst->width + x] = gray; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dst->data)[(y*dst->width + x)*2] = gray; + ((unsigned char *)dst->data)[(y*dst->width + x)*2 + 1] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + // NOTE: Calculate R5G6B5 equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*63.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*31.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0; + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*15.0f)); + unsigned char g = (unsigned char)(round(coln.y*15.0f)); + unsigned char b = (unsigned char)(round(coln.z*15.0f)); + unsigned char a = (unsigned char)(round(coln.w*15.0f)); + + ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + ((unsigned char *)dst->data)[(y*dst->width + x)*3] = color.r; + ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 1] = color.g; + ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 2] = color.b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + ((unsigned char *)dst->data)[(y*dst->width + x)*4] = color.r; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 1] = color.g; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 2] = color.b; + ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 3] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + // NOTE: Calculate grayscale equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((float *)dst->data)[y*dst->width + x] = coln.x*0.299f + coln.y*0.587f + coln.z*0.114f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + // NOTE: Calculate R32G32B32 equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((float *)dst->data)[(y*dst->width + x)*3] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*3 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*3 + 2] = coln.z; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + // NOTE: Calculate R32G32B32A32 equivalent color (normalized to 32bit) + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + ((float *)dst->data)[(y*dst->width + x)*4] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*4 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*4 + 2] = coln.z; + ((float *)dst->data)[(y*dst->width + x)*4 + 3] = coln.w; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R16: + { + // NOTE: Calculate grayscale equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((unsigned short*)dst->data)[y*dst->width + x] = FloatToHalf(coln.x*0.299f + coln.y*0.587f + coln.z*0.114f); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16: + { + // NOTE: Calculate R32G32B32 equivalent color (normalized to 32bit) + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + ((unsigned short *)dst->data)[(y*dst->width + x)*3] = FloatToHalf(coln.x); + ((unsigned short *)dst->data)[(y*dst->width + x)*3 + 1] = FloatToHalf(coln.y); + ((unsigned short *)dst->data)[(y*dst->width + x)*3 + 2] = FloatToHalf(coln.z); + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + { + // NOTE: Calculate R32G32B32A32 equivalent color (normalized to 32bit) + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + ((unsigned short *)dst->data)[(y*dst->width + x)*4] = FloatToHalf(coln.x); + ((unsigned short *)dst->data)[(y*dst->width + x)*4 + 1] = FloatToHalf(coln.y); + ((unsigned short *)dst->data)[(y*dst->width + x)*4 + 2] = FloatToHalf(coln.z); + ((unsigned short *)dst->data)[(y*dst->width + x)*4 + 3] = FloatToHalf(coln.w); + + } break; + default: break; + } +} + +// Draw pixel within an image (Vector version) +void ImageDrawPixelV(Image *dst, Vector2 position, Color color) +{ + ImageDrawPixel(dst, (int)position.x, (int)position.y, color); +} + +// Draw line within an image +void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color) +{ + // Calculate differences in coordinates + int shortLen = endPosY - startPosY; + int longLen = endPosX - startPosX; + bool yLonger = false; + + // Determine if the line is more vertical than horizontal + if (abs(shortLen) > abs(longLen)) + { + // Swap the lengths if the line is more vertical + int temp = shortLen; + shortLen = longLen; + longLen = temp; + yLonger = true; + } + + // Initialize variables for drawing loop + int endVal = longLen + 1; + int sgnInc = 1; + + // Adjust direction increment based on longLen sign + if (longLen < 0) + { + longLen = -longLen; + sgnInc = -1; + endVal -= 2; + } + + // Calculate fixed-point increment for shorter length + int decInc = (longLen == 0)? 0 : (shortLen << 16)/longLen; + + // Draw the line pixel by pixel + if (yLonger) + { + // If line is more vertical, iterate over y-axis + // Init j with 0.5 in 16-bit fixed point (1 << 15) for better rounding. + for (int i = 0, j = (1 << 15); i != endVal; i += sgnInc, j += decInc) + { + // Calculate pixel position and draw it + ImageDrawPixel(dst, startPosX + (j >> 16), startPosY + i, color); + } + } + else + { + // If line is more horizontal, iterate over x-axis + for (int i = 0, j = (1 << 15); i != endVal; i += sgnInc, j += decInc) + { + // Calculate pixel position and draw it + ImageDrawPixel(dst, startPosX + i, startPosY + (j >> 16), color); + } + } +} + +// Draw line within an image (Vector version) +void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color) +{ + // Round start and end positions to nearest integer coordinates + int x1 = (int)(start.x + 0.5f); + int y1 = (int)(start.y + 0.5f); + int x2 = (int)(end.x + 0.5f); + int y2 = (int)(end.y + 0.5f); + + // Draw a vertical line using ImageDrawLine function + ImageDrawLine(dst, x1, y1, x2, y2, color); +} + +// Draw a line defining thickness within an image +void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color color) +{ + // Round start and end positions to nearest integer coordinates + int x1 = (int)(start.x + 0.5f); + int y1 = (int)(start.y + 0.5f); + int x2 = (int)(end.x + 0.5f); + int y2 = (int)(end.y + 0.5f); + + // Calculate differences in x and y coordinates + int dx = x2 - x1; + int dy = y2 - y1; + + // Determine if the line is more horizontal or vertical + if ((dx != 0) && (abs(dy/dx) < 1)) + { + // Line is more horizontal + + // How many additional lines to draw + int wy = thick - 1; + + // Draw the main line and lower half + for (int i = 0; i <= ((wy+1)/2); i++) + { + ImageDrawLine(dst, x1, y1 + i, x2, y2 + i, color); + } + + // Draw the upper half + for (int i = 1; i <= (wy/2); i++) + { + ImageDrawLine(dst, x1, y1 - i, x2, y2 - i, color); + } + } + else if (dy != 0) + { + // Line is more vertical or perfectly horizontal + + // How many additional lines to draw + int wx = thick - 1; + + //Draw the main line and right half + for (int i = 0; i <= ((wx+1)/2); i++) + { + ImageDrawLine(dst, x1 + i, y1, x2 + i, y2, color); + } + + // Draw the left half + for (int i = 1; i <= (wx/2); i++) + { + ImageDrawLine(dst, x1 - i, y1, x2 - i, y2, color); + } + } +} + +// Draw a lines sequence within an image +void ImageDrawLineStrip(Image *dst, const Vector2 *points, int pointCount, Color color) +{ + for (int i = 0; i < pointCount - 1; i++) + { + ImageDrawLineV(dst, points[i], points[i + 1], color); + } +} + +// Draw triangle within an image +void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color) +{ + // Calculate the 2D bounding box of the triangle + // Determine the minimum and maximum x and y coordinates of the triangle vertices + int xMin = (int)((v1.x < v2.x)? ((v1.x < v3.x)? v1.x : v3.x) : ((v2.x < v3.x)? v2.x : v3.x)); + int yMin = (int)((v1.y < v2.y)? ((v1.y < v3.y)? v1.y : v3.y) : ((v2.y < v3.y)? v2.y : v3.y)); + int xMax = (int)((v1.x > v2.x)? ((v1.x > v3.x)? v1.x : v3.x) : ((v2.x > v3.x)? v2.x : v3.x)); + int yMax = (int)((v1.y > v2.y)? ((v1.y > v3.y)? v1.y : v3.y) : ((v2.y > v3.y)? v2.y : v3.y)); + + // Clamp the bounding box to the image dimensions + if (xMin < 0) xMin = 0; + if (yMin < 0) yMin = 0; + if (xMax > dst->width) xMax = dst->width; + if (yMax > dst->height) yMax = dst->height; + + // Check the order of the vertices to determine if it's a front or back face + // NOTE: if signedArea is equal to 0, the face is degenerate + float signedArea = (v2.x - v1.x)*(v3.y - v1.y) - (v3.x - v1.x)*(v2.y - v1.y); + bool isBackFace = (signedArea > 0); + + // Barycentric interpolation setup + // Calculate the step increments for the barycentric coordinates + int w1XStep = (int)(v3.y - v2.y), w1YStep = (int)(v2.x - v3.x); + int w2XStep = (int)(v1.y - v3.y), w2YStep = (int)(v3.x - v1.x); + int w3XStep = (int)(v2.y - v1.y), w3YStep = (int)(v1.x - v2.x); + + // If the triangle is a back face, invert the steps + if (isBackFace) + { + w1XStep = -w1XStep, w1YStep = -w1YStep; + w2XStep = -w2XStep, w2YStep = -w2YStep; + w3XStep = -w3XStep, w3YStep = -w3YStep; + } + + // Calculate the initial barycentric coordinates for the top-left point of the bounding box + int w1Row = (int)((xMin - v2.x)*w1XStep + w1YStep*(yMin - v2.y)); + int w2Row = (int)((xMin - v3.x)*w2XStep + w2YStep*(yMin - v3.y)); + int w3Row = (int)((xMin - v1.x)*w3XStep + w3YStep*(yMin - v1.y)); + + // Rasterization loop + // Iterate through each pixel in the bounding box + for (int y = yMin; y <= yMax; y++) + { + int w1 = w1Row; + int w2 = w2Row; + int w3 = w3Row; + + for (int x = xMin; x <= xMax; x++) + { + // Check if the pixel is inside the triangle using barycentric coordinates + // If it is, the pixel can be drawn with the given color + if ((w1 | w2 | w3) >= 0) ImageDrawPixel(dst, x, y, color); + + // Increment the barycentric coordinates for the next pixel + w1 += w1XStep; + w2 += w2XStep; + w3 += w3XStep; + } + + // Move to the next row in the bounding box + w1Row += w1YStep; + w2Row += w2YStep; + w3Row += w3YStep; + } +} + +// Draw triangle with interpolated colors within an image +void ImageDrawTriangleGradient(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3) +{ + // Calculate the 2D bounding box of the triangle + // Determine the minimum and maximum x and y coordinates of the triangle vertices + int xMin = (int)((v1.x < v2.x)? ((v1.x < v3.x)? v1.x : v3.x) : ((v2.x < v3.x)? v2.x : v3.x)); + int yMin = (int)((v1.y < v2.y)? ((v1.y < v3.y)? v1.y : v3.y) : ((v2.y < v3.y)? v2.y : v3.y)); + int xMax = (int)((v1.x > v2.x)? ((v1.x > v3.x)? v1.x : v3.x) : ((v2.x > v3.x)? v2.x : v3.x)); + int yMax = (int)((v1.y > v2.y)? ((v1.y > v3.y)? v1.y : v3.y) : ((v2.y > v3.y)? v2.y : v3.y)); + + // Clamp the bounding box to the image dimensions + if (xMin < 0) xMin = 0; + if (yMin < 0) yMin = 0; + if (xMax > dst->width) xMax = dst->width; + if (yMax > dst->height) yMax = dst->height; + + // Check the order of the vertices to determine if it's a front or back face + // NOTE: if signedArea is equal to 0, the face is degenerate + float signedArea = (v2.x - v1.x)*(v3.y - v1.y) - (v3.x - v1.x)*(v2.y - v1.y); + bool isBackFace = (signedArea > 0); + + // Barycentric interpolation setup + // Calculate the step increments for the barycentric coordinates + int w1XStep = (int)(v3.y - v2.y), w1YStep = (int)(v2.x - v3.x); + int w2XStep = (int)(v1.y - v3.y), w2YStep = (int)(v3.x - v1.x); + int w3XStep = (int)(v2.y - v1.y), w3YStep = (int)(v1.x - v2.x); + + // If the triangle is a back face, invert the steps + if (isBackFace) + { + w1XStep = -w1XStep, w1YStep = -w1YStep; + w2XStep = -w2XStep, w2YStep = -w2YStep; + w3XStep = -w3XStep, w3YStep = -w3YStep; + } + + // Calculate the initial barycentric coordinates for the top-left point of the bounding box + int w1Row = (int)((xMin - v2.x)*w1XStep + w1YStep*(yMin - v2.y)); + int w2Row = (int)((xMin - v3.x)*w2XStep + w2YStep*(yMin - v3.y)); + int w3Row = (int)((xMin - v1.x)*w3XStep + w3YStep*(yMin - v1.y)); + + // Calculate the inverse of the sum of the barycentric coordinates for normalization + float wInvSum = 255.0f/(w1Row + w2Row + w3Row); + + // Rasterization loop + // Iterate through each pixel in the bounding box + for (int y = yMin; y <= yMax; y++) + { + int w1 = w1Row; + int w2 = w2Row; + int w3 = w3Row; + + for (int x = xMin; x <= xMax; x++) + { + // Check if the pixel is inside the triangle using barycentric coordinates + if ((w1 | w2 | w3) >= 0) + { + // Compute the normalized barycentric coordinates + unsigned char aW1 = (unsigned char)((float)w1*wInvSum); + unsigned char aW2 = (unsigned char)((float)w2*wInvSum); + unsigned char aW3 = (unsigned char)((float)w3*wInvSum); + + // Interpolate the color using the barycentric coordinates + Color finalColor = { 0 }; + finalColor.r = (c1.r*aW1 + c2.r*aW2 + c3.r*aW3)/255; + finalColor.g = (c1.g*aW1 + c2.g*aW2 + c3.g*aW3)/255; + finalColor.b = (c1.b*aW1 + c2.b*aW2 + c3.b*aW3)/255; + finalColor.a = (c1.a*aW1 + c2.a*aW2 + c3.a*aW3)/255; + + // Draw the pixel with the interpolated color + ImageDrawPixel(dst, x, y, finalColor); + } + + // Increment the barycentric coordinates for the next pixel + w1 += w1XStep; + w2 += w2XStep; + w3 += w3XStep; + } + + // Move to the next row in the bounding box + w1Row += w1YStep; + w2Row += w2YStep; + w3Row += w3YStep; + } +} + +// Draw triangle outline within an image +void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color) +{ + ImageDrawLine(dst, (int)v1.x, (int)v1.y, (int)v2.x, (int)v2.y, color); + ImageDrawLine(dst, (int)v2.x, (int)v2.y, (int)v3.x, (int)v3.y, color); + ImageDrawLine(dst, (int)v3.x, (int)v3.y, (int)v1.x, (int)v1.y, color); +} + +// Draw a triangle fan defined by points within an image (first vertex is the center) +void ImageDrawTriangleFan(Image *dst, const Vector2 *points, int pointCount, Color color) +{ + if (pointCount >= 3) + { + for (int i = 1; i < pointCount - 1; i++) + { + ImageDrawTriangle(dst, points[0], points[i], points[i + 1], color); + } + } +} + +// Draw a triangle strip defined by points within an image +void ImageDrawTriangleStrip(Image *dst, const Vector2 *points, int pointCount, Color color) +{ + if (pointCount >= 3) + { + for (int i = 2; i < pointCount; i++) + { + if ((i%2) == 0) ImageDrawTriangle(dst, points[i], points[i - 2], points[i - 1], color); + else ImageDrawTriangle(dst, points[i], points[i - 1], points[i - 2], color); + } + } +} + +// Draw rectangle within an image +void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color) +{ + ImageDrawRectangleRec(dst, (Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color); +} + +// Draw rectangle within an image (Vector version) +void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color) +{ + ImageDrawRectangle(dst, (int)position.x, (int)position.y, (int)size.x, (int)size.y, color); +} + +// Draw rectangle within an image +void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; + + // Security check to avoid drawing out of bounds in case of bad user data + if (rec.x < 0) { rec.width += rec.x; rec.x = 0; } + if (rec.y < 0) { rec.height += rec.y; rec.y = 0; } + if (rec.width < 0) rec.width = 0; + if (rec.height < 0) rec.height = 0; + + // Clamp the size the the image bounds + if ((rec.x + rec.width) >= dst->width) rec.width = dst->width - rec.x; + if ((rec.y + rec.height) >= dst->height) rec.height = dst->height - rec.y; + + // Check if the rect is even inside the image + if ((rec.x >= dst->width) || (rec.y >= dst->height)) return; + if (((rec.x + rec.width) <= 0) || (rec.y + rec.height <= 0)) return; + + int sy = (int)rec.y; + int sx = (int)rec.x; + + int bytesPerPixel = GetPixelDataSize(1, 1, dst->format); + + // Fill in the first pixel of the first row based on image format + ImageDrawPixel(dst, sx, sy, color); + + int bytesOffset = ((sy*dst->width) + sx)*bytesPerPixel; + unsigned char *pSrcPixel = (unsigned char *)dst->data + bytesOffset; + + // Repeat the first pixel data throughout the row + for (int x = 1; x < (int)rec.width; x *= 2) + { + int pixelsToCopy = MIN(x, (int)rec.width - x); + memcpy(pSrcPixel + x*bytesPerPixel, pSrcPixel, pixelsToCopy*bytesPerPixel); + } + + // Repeat the first row data for all other rows + int bytesPerRow = bytesPerPixel*(int)rec.width; + for (int y = 1; y < (int)rec.height; y++) + { + memcpy(pSrcPixel + (y*dst->width)*bytesPerPixel, pSrcPixel, bytesPerRow); + } +} + +// Draw a color-filled rectangle with pro parameters within and image +void ImageDrawRectanglePro(Image *dst, Rectangle rec, Vector2 origin, float rotation, Color color) +{ + // TODO: NEW: Implement ImageDrawRectanglePro() +} + +// Draw rectangle lines within an image +void ImageDrawRectangleLines(Image *dst, int posX, int posY, int width, int height, Color color) +{ + Rectangle rec = { (float)posX, (float)posY, (float)width, (float)height }; + ImageDrawRectangleLinesEx(dst, rec, 1, color); +} + +// Draw rectangle lines within an image with line thickness +void ImageDrawRectangleLinesEx(Image *dst, Rectangle rec, int thick, Color color) +{ + ImageDrawRectangle(dst, (int)rec.x, (int)rec.y, (int)rec.width, thick, color); + ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); + ImageDrawRectangle(dst, (int)(rec.x + rec.width - thick), (int)(rec.y + thick), thick, (int)(rec.height - thick*2), color); + ImageDrawRectangle(dst, (int)rec.x, (int)(rec.y + rec.height - thick), (int)rec.width, thick, color); +} + +// Draw rectangle with gradient colors within an image, counter-clockwise color order +void ImageDrawRectangleGradientEx(Image *dst, Rectangle rec, Color col1, Color col2, Color col3, Color col4) +{ + // TODO: NEW: Implement ImageDrawRectangleGradientEx() +} + +// Draw circle within an image +void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color) +{ + int x = 0; + int y = radius; + int decesionParameter = 3 - 2*radius; + + while (y >= x) + { + ImageDrawRectangle(dst, centerX - x, centerY + y, x*2 + 1, 1, color); + ImageDrawRectangle(dst, centerX - x, centerY - y, x*2 + 1, 1, color); + ImageDrawRectangle(dst, centerX - y, centerY + x, y*2 + 1, 1, color); + ImageDrawRectangle(dst, centerX - y, centerY - x, y*2 + 1, 1, color); + x++; + + if (decesionParameter > 0) + { + y--; + decesionParameter = decesionParameter + 4*(x - y) + 10; + } + else decesionParameter = decesionParameter + 4*x + 6; + } +} + +// Draw circle within an image (Vector version) +void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color) +{ + ImageDrawCircle(dst, (int)center.x, (int)center.y, radius, color); +} + +// Draw circle outline within an image +void ImageDrawCircleLines(Image *dst, int centerX, int centerY, int radius, Color color) +{ + int x = 0; + int y = radius; + int decesionParameter = 3 - 2*radius; + + while (y >= x) + { + ImageDrawPixel(dst, centerX + x, centerY + y, color); + ImageDrawPixel(dst, centerX - x, centerY + y, color); + ImageDrawPixel(dst, centerX + x, centerY - y, color); + ImageDrawPixel(dst, centerX - x, centerY - y, color); + ImageDrawPixel(dst, centerX + y, centerY + x, color); + ImageDrawPixel(dst, centerX - y, centerY + x, color); + ImageDrawPixel(dst, centerX + y, centerY - x, color); + ImageDrawPixel(dst, centerX - y, centerY - x, color); + x++; + + if (decesionParameter > 0) + { + y--; + decesionParameter = decesionParameter + 4*(x - y) + 10; + } + else decesionParameter = decesionParameter + 4*x + 6; + } +} + +// Draw circle outline within an image (Vector version) +void ImageDrawCircleLinesV(Image *dst, Vector2 center, int radius, Color color) +{ + ImageDrawCircleLines(dst, (int)center.x, (int)center.y, radius, color); +} + +// Draw a gradient-filled circle within an image +void ImageDrawCircleGradient(Image *dst, Vector2 center, float radius, Color inner, Color outer) +{ + // TODO: NEW: Implement ImageDrawCircleGradient() +} + +// Draw an image within an image +void ImageDrawImage(Image *dst, Image src, int posX, int posY, Color tint) +{ + Rectangle srcRec = { 0.0f, 0.0f, (float)src.width, (float)src.height }; + Rectangle dstRec = { (float)posX, (float)posY, srcRec.width, srcRec.height }; + ImageDrawImagePro(dst, src, srcRec, dstRec, (Vector2){ 0 }, 0.0f, tint); +} + +// Draw an image with scaling and rotation within an image +void ImageDrawImageEx(Image *dst, Image src, Vector2 position, float rotation, float scale, Color tint) +{ + // TODO: NEW: Implement ImageDrawImageEx() +} + +// Draw a part of an image defined by a rectangle within an image +void ImageDrawImageRec(Image *dst, Image src, Rectangle srcRec, Vector2 position, Color tint) +{ + Rectangle dstRec = { position.x, position.y, srcRec.width, srcRec.height }; + ImageDrawImagePro(dst, src, srcRec, dstRec, (Vector2){ 0 }, 0.0f, tint); +} + +// Draw a part of an image defined by a rectangle into destination rectangle, with scaling and rotation, within an image +// TODO: REVIEW: ImageDrawImagePro(), implement origin and rotation for image drawing +void ImageDrawImagePro(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Vector2 origin, float rotation, Color tint) +{ + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || + (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; + + if (dst->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image drawing not supported for compressed formats"); + else + { + Image srcMod = { 0 }; // Source copy (in case it was required) + Image *srcPtr = &src; // Pointer to source image + bool useSrcMod = false; // Track source copy required + + // Source rectangle out-of-bounds security checks + if (srcRec.x < 0) { srcRec.width += srcRec.x; srcRec.x = 0; } + if (srcRec.y < 0) { srcRec.height += srcRec.y; srcRec.y = 0; } + if ((srcRec.x + srcRec.width) > src.width) srcRec.width = src.width - srcRec.x; + if ((srcRec.y + srcRec.height) > src.height) srcRec.height = src.height - srcRec.y; + + // Check if source rectangle needs to be resized to destination rectangle + // In that case, make a copy of source, and apply all required transform + if (((int)srcRec.width != (int)dstRec.width) || ((int)srcRec.height != (int)dstRec.height)) + { + srcMod = ImageFromImage(src, srcRec); // Create image from another image + ImageResize(&srcMod, (int)dstRec.width, (int)dstRec.height); // Resize to destination rectangle + srcRec = (Rectangle){ 0, 0, (float)srcMod.width, (float)srcMod.height }; + + srcPtr = &srcMod; + useSrcMod = true; + } + + // Destination rectangle out-of-bounds security checks + if (dstRec.x < 0) + { + srcRec.x -= dstRec.x; + srcRec.width += dstRec.x; + dstRec.x = 0; + } + else if ((dstRec.x + srcRec.width) > dst->width) srcRec.width = dst->width - dstRec.x; + + if (dstRec.y < 0) + { + srcRec.y -= dstRec.y; + srcRec.height += dstRec.y; + dstRec.y = 0; + } + else if ((dstRec.y + srcRec.height) > dst->height) srcRec.height = dst->height - dstRec.y; + + if (dst->width < srcRec.width) srcRec.width = (float)dst->width; + if (dst->height < srcRec.height) srcRec.height = (float)dst->height; + + // This blitting method is quite fast! The process followed is: + // for every pixel -> [get_src_format/get_dst_format -> blend -> format_to_dst] + // Some optimization ideas: + // [x] Avoid creating source copy if not required (no resize required) + // [x] Optimize ImageResize() for pixel format (alternative: ImageResizeNN()) + // [x] Optimize ColorAlphaBlend() to avoid processing (alpha = 0) and (alpha = 1) + // [x] Optimize ColorAlphaBlend() for faster operations (maybe avoiding divs?) + // [x] Consider fast path: no alpha blending required cases (src has no alpha) + // [x] Consider fast path: same src/dst format with no alpha -> direct line copy + // [-] GetPixelColor(): Get Vector4 instead of Color, easier for ColorAlphaBlend() + // [-] Support 16bit and 32bit (float) channels drawing + + Color colSrc = { 0 }; + Color colDst = { 0 }; + Color blend = { 0 }; + bool blendRequired = true; + + // Fast path: Avoid blend if source has no alpha to blend + if ((tint.a == 255) && + ((srcPtr->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) || + (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R5G6B5) || + (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) || + (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R32) || + (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) || + (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R16) || + (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R16G16B16))) + blendRequired = false; + + int strideDst = GetPixelDataSize(dst->width, 1, dst->format); + int bytesPerPixelDst = strideDst/(dst->width); + + int strideSrc = GetPixelDataSize(srcPtr->width, 1, srcPtr->format); + int bytesPerPixelSrc = strideSrc/(srcPtr->width); + + unsigned char *pSrcBase = (unsigned char *)srcPtr->data + ((int)srcRec.y*srcPtr->width + (int)srcRec.x)*bytesPerPixelSrc; + unsigned char *pDstBase = (unsigned char *)dst->data + ((int)dstRec.y*dst->width + (int)dstRec.x)*bytesPerPixelDst; + + for (int y = 0; y < (int)srcRec.height; y++) + { + unsigned char *pSrc = pSrcBase; + unsigned char *pDst = pDstBase; + + // Fast path: Avoid moving pixel by pixel if no blend required and same format + if (!blendRequired && (srcPtr->format == dst->format)) memcpy(pDst, pSrc, (int)(srcRec.width)*bytesPerPixelSrc); + else + { + for (int x = 0; x < (int)srcRec.width; x++) + { + colSrc = GetPixelColor(pSrc, srcPtr->format); + colDst = GetPixelColor(pDst, dst->format); + + // Fast path: Avoid blend if source has no alpha to blend + if (blendRequired) blend = ColorAlphaBlend(colDst, colSrc, tint); + else blend = colSrc; + + SetPixelColor(pDst, blend, dst->format); + + pDst += bytesPerPixelDst; + pSrc += bytesPerPixelSrc; + } + } + + pSrcBase += strideSrc; + pDstBase += strideDst; + } + + if (useSrcMod) UnloadImage(srcMod); // Unload source modified image + + if ((dst->mipmaps > 1) && (src.mipmaps > 1)) + { + Image mipmapDst = *dst; + mipmapDst.data = (char *)mipmapDst.data + GetPixelDataSize(mipmapDst.width, mipmapDst.height, mipmapDst.format); + mipmapDst.width /= 2; + mipmapDst.height /= 2; + mipmapDst.mipmaps--; + + Image mipmapSrc = src; + mipmapSrc.data = (char *)mipmapSrc.data + GetPixelDataSize(mipmapSrc.width, mipmapSrc.height, mipmapSrc.format); + mipmapSrc.width /= 2; + mipmapSrc.height /= 2; + mipmapSrc.mipmaps--; + + Rectangle mipmapSrcRec = srcRec; + mipmapSrcRec.width /= 2; + mipmapSrcRec.height /= 2; + mipmapSrcRec.x /= 2; + mipmapSrcRec.y /= 2; + + Rectangle mipmapDstRec = dstRec; + mipmapDstRec.width /= 2; + mipmapDstRec.height /= 2; + mipmapDstRec.x /= 2; + mipmapDstRec.y /= 2; + + ImageDrawImagePro(&mipmapDst, mipmapSrc, mipmapSrcRec, mipmapDstRec, (Vector2){ 0 }, 0.0f, tint); + } + } +} + +// Draw text (default font) within an image (destination) +void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color) +{ +#if SUPPORT_MODULE_RTEXT + // Make sure default font is loaded to be used on image text drawing + if (GetFontDefault().texture.id == 0) LoadFontDefault(); + + Vector2 position = { (float)posX, (float)posY }; + ImageDrawTextEx(dst, GetFontDefault(), text, position, (float)fontSize, 1.0f, color); // WARNING: Module required: rtext +#else + TRACELOG(LOG_WARNING, "IMAGE: ImageDrawText() requires module: rtext"); +#endif +} + +// Draw text (custom sprite font) within an image (destination) +void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) +{ + Image imText = ImageTextEx(font, text, fontSize, spacing, tint); + + Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height }; + Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height }; + + ImageDrawImagePro(dst, imText, srcRec, dstRec, (Vector2){ 0 }, 0.0f, WHITE); + + UnloadImage(imText); +} + +// Draw text using Font and pro parameters (rotation) +void ImageDrawTextPro(Image *dst, Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint) +{ + // TODO: NEW: Implement ImageDrawTextPro() +} + +//------------------------------------------------------------------------------------ +// Texture loading functions +//------------------------------------------------------------------------------------ +// Load texture from file into GPU memory (VRAM) +Texture2D LoadTexture(const char *fileName) +{ + Texture2D texture = { 0 }; + + Image image = LoadImage(fileName); + + if (image.data != NULL) + { + texture = LoadTextureFromImage(image); + UnloadImage(image); + } + + return texture; +} + +// Load a texture from image data +// NOTE: image is not unloaded, it must be done manually +Texture2D LoadTextureFromImage(Image image) +{ + Texture2D texture = { 0 }; + + if ((image.width != 0) && (image.height != 0)) + { + texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps); + } + else TRACELOG(LOG_WARNING, "IMAGE: Data is not valid to load texture"); + + texture.width = image.width; + texture.height = image.height; + texture.mipmaps = image.mipmaps; + texture.format = image.format; + + return texture; +} + +// Load cubemap from image, multiple image cubemap layouts supported +TextureCubemap LoadTextureCubemap(Image image, int layout) +{ + TextureCubemap cubemap = { 0 }; + + if (layout == CUBEMAP_LAYOUT_AUTO_DETECT) // Try to automatically guess layout type + { + // Check image width/height to determine the type of cubemap provided + if (image.width > image.height) + { + if ((image.width/6) == image.height) { layout = CUBEMAP_LAYOUT_LINE_HORIZONTAL; cubemap.width = image.width/6; } + else if ((image.width/4) == (image.height/3)) { layout = CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; } + } + else if (image.height > image.width) + { + if ((image.height/6) == image.width) { layout = CUBEMAP_LAYOUT_LINE_VERTICAL; cubemap.width = image.height/6; } + else if ((image.width/3) == (image.height/4)) { layout = CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; } + } + } + else + { + if (layout == CUBEMAP_LAYOUT_LINE_VERTICAL) cubemap.width = image.height/6; + if (layout == CUBEMAP_LAYOUT_LINE_HORIZONTAL) cubemap.width = image.width/6; + if (layout == CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR) cubemap.width = image.width/3; + if (layout == CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE) cubemap.width = image.width/4; + } + + cubemap.height = cubemap.width; + + // Layout provided or already auto-detected + if (layout != CUBEMAP_LAYOUT_AUTO_DETECT) + { + int size = cubemap.width; + + Image faces = { 0 }; // Vertical column image + Rectangle faceRecs[6] = { 0 }; // Face source rectangles + + for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, (float)size, (float)size }; + + if (layout == CUBEMAP_LAYOUT_LINE_VERTICAL) + { + faces = ImageCopy(image); // Image data already follows expected convention + } + /*else if (layout == CUBEMAP_LAYOUT_PANORAMA) + { + // TODO: Implement panorama by converting image to square faces... + // REF: https://github.com/denivip/panorama/blob/master/panorama.cpp + } */ + else + { + if (layout == CUBEMAP_LAYOUT_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = (float)size*i; + else if (layout == CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR) + { + faceRecs[0].x = (float)size; faceRecs[0].y = (float)size; + faceRecs[1].x = (float)size; faceRecs[1].y = (float)size*3; + faceRecs[2].x = (float)size; faceRecs[2].y = 0; + faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; + faceRecs[4].x = 0; faceRecs[4].y = (float)size; + faceRecs[5].x = (float)size*2; faceRecs[5].y = (float)size; + } + else if (layout == CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE) + { + faceRecs[0].x = (float)size*2; faceRecs[0].y = (float)size; + faceRecs[1].x = 0; faceRecs[1].y = (float)size; + faceRecs[2].x = (float)size; faceRecs[2].y = 0; + faceRecs[3].x = (float)size; faceRecs[3].y = (float)size*2; + faceRecs[4].x = (float)size; faceRecs[4].y = (float)size; + faceRecs[5].x = (float)size*3; faceRecs[5].y = (float)size; + } + + // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading + // NOTE: Image formatting does not work with compressed textures + faces = GenImageColor(size, size*6, MAGENTA); + ImageFormat(&faces, image.format); + + Image mipmapped = ImageCopy(image); + if (image.mipmaps > 1) + { + ImageMipmaps(&mipmapped); + ImageMipmaps(&faces); + } + + for (int i = 0; i < 6; i++) ImageDrawImagePro(&faces, mipmapped, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, (Vector2){ 0 }, 0.0f, WHITE); + + UnloadImage(mipmapped); + } + + // NOTE: Cubemap data is expected to be provided as 6 images in a single data array, + // one after the other (that's a vertical image), following convention: +X, -X, +Y, -Y, +Z, -Z + cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format, faces.mipmaps); + + if (cubemap.id != 0) + { + cubemap.format = faces.format; + cubemap.mipmaps = faces.mipmaps; + } + else TRACELOG(LOG_WARNING, "IMAGE: Failed to load cubemap image"); + + UnloadImage(faces); + } + else TRACELOG(LOG_WARNING, "IMAGE: Failed to detect cubemap image layout"); + + return cubemap; +} + +// Load RGBA texture for rendering (framebuffer) +RenderTexture2D LoadRenderTexture(int width, int height) +{ + return LoadRenderTextureEx(width, height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); +} + +// Load texture for rendering (framebuffer), with specific format +// NOTE: Render texture is loaded by default with RGBA color attachment and depth RenderBuffer +RLAPI RenderTexture2D LoadRenderTextureEx(int width, int height, int format) +{ + RenderTexture2D target = { 0 }; + + if (format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(LOG_WARNING, "FBO: Render texture format not supported"); + return target; + } + + target.id = rlLoadFramebuffer(); // Load an empty framebuffer + + if (target.id > 0) + { + rlEnableFramebuffer(target.id); + + // Create color texture (default to RGBA) + target.texture.id = rlLoadTexture(NULL, width, height, format, 1); + target.texture.width = width; + target.texture.height = height; + target.texture.format = format; + target.texture.mipmaps = 1; + + // Create depth renderbuffer/texture + target.depth.id = rlLoadTextureDepth(width, height, true); + target.depth.width = width; + target.depth.height = height; + target.depth.format = 19; //DEPTH_COMPONENT_24BIT? + target.depth.mipmaps = 1; + + // Attach color texture and depth renderbuffer/texture to FBO + rlFramebufferAttach(target.id, target.texture.id, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); + rlFramebufferAttach(target.id, target.depth.id, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0); + + // Check if fbo is complete with attachments (valid) + if (rlFramebufferComplete(target.id)) TRACELOG(LOG_INFO, "FBO: [ID %i] Framebuffer object created successfully", target.id); + + rlDisableFramebuffer(); + } + else TRACELOG(LOG_WARNING, "FBO: Framebuffer object can not be created"); + + return target; +} + +// Check if texture is valid (loaded in GPU) +bool IsTextureValid(Texture2D texture) +{ + bool result = false; + + if ((texture.id > 0) && // Validate OpenGL id (texture uploaded to GPU) + (texture.width > 0) && // Validate texture width + (texture.height > 0) && // Validate texture height + (texture.format > 0) && // Validate texture pixel format + (texture.mipmaps > 0)) result = true; // Validate texture mipmaps (at least 1 for basic mipmap level) + + return result; +} + +// Unload texture from GPU memory (VRAM) +void UnloadTexture(Texture2D texture) +{ + if (texture.id > 0) + { + rlUnloadTexture(texture.id); + + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id); + } +} + +// Check if render texture is valid (loaded in GPU) +bool IsRenderTextureValid(RenderTexture2D target) +{ + bool result = false; + + if ((target.id > 0) && // Validate OpenGL id (loaded on GPU) + IsTextureValid(target.depth) && // Validate FBO depth texture/renderbuffer attachment + IsTextureValid(target.texture)) result = true; // Validate FBO texture attachment + + return result; +} + +// Unload render texture from GPU memory (VRAM) +void UnloadRenderTexture(RenderTexture2D target) +{ + if (target.id > 0) + { + if (target.texture.id > 0) + { + // Color texture attached to FBO is deleted + rlUnloadTexture(target.texture.id); + } + + // NOTE: Depth texture/renderbuffer is automatically + // queried and deleted before deleting framebuffer + rlUnloadFramebuffer(target.id); + } +} + +// Update GPU texture with new data +// NOTE 1: pixels data must match texture.format +// NOTE 2: pixels data must contain at least as many pixels as texture +void UpdateTexture(Texture2D texture, const void *pixels) +{ + rlUpdateTexture(texture.id, 0, 0, texture.width, texture.height, texture.format, pixels); +} + +// Update GPU texture rectangle with new data +// NOTE 1: pixels data must match texture.format +// NOTE 2: pixels data must contain as many pixels as rec contains +// NOTE 3: rec must fit completely within texture's width and height +void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels) +{ + rlUpdateTexture(texture.id, (int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, texture.format, pixels); +} + +//------------------------------------------------------------------------------------ +// Texture configuration functions +//------------------------------------------------------------------------------------ +// Generate GPU mipmaps for a texture +void GenTextureMipmaps(Texture2D *texture) +{ + // NOTE: NPOT textures support check inside function + // On WebGL (OpenGL ES 2.0) NPOT textures support is limited + rlGenTextureMipmaps(texture->id, texture->width, texture->height, texture->format, &texture->mipmaps); +} + +// Set texture scaling filter mode +void SetTextureFilter(Texture2D texture, int filter) +{ + switch (filter) + { + case TEXTURE_FILTER_POINT: + { + if (texture.mipmaps > 1) + { + // RL_TEXTURE_FILTER_MIP_NEAREST - tex filter: POINT, mipmaps filter: POINT (sharp switching between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_NEAREST); + + // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); + } + else + { + // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_NEAREST); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); + } + } break; + case TEXTURE_FILTER_BILINEAR: + { + if (texture.mipmaps > 1) + { + // RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST - tex filter: BILINEAR, mipmaps filter: POINT (sharp switching between mipmaps) + // Alternative: RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR - tex filter: POINT, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST); + + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + else + { + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + } break; + case TEXTURE_FILTER_TRILINEAR: + { + if (texture.mipmaps > 1) + { + // RL_TEXTURE_FILTER_MIP_LINEAR - tex filter: BILINEAR, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_LINEAR); + + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + else + { + TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] No mipmaps available for TRILINEAR texture filtering", texture.id); + + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); + } + } break; + case TEXTURE_FILTER_ANISOTROPIC_4X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 4); break; + case TEXTURE_FILTER_ANISOTROPIC_8X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 8); break; + case TEXTURE_FILTER_ANISOTROPIC_16X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 16); break; + default: break; + } +} + +// Set texture wrapping mode +void SetTextureWrap(Texture2D texture, int wrap) +{ + switch (wrap) + { + case TEXTURE_WRAP_REPEAT: + { + // NOTE: It only works if NPOT textures are supported, i.e. OpenGL ES 2.0 could not support it + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_REPEAT); + } break; + case TEXTURE_WRAP_CLAMP: + { + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_CLAMP); + } break; + case TEXTURE_WRAP_MIRROR_REPEAT: + { + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_REPEAT); + } break; + case TEXTURE_WRAP_MIRROR_CLAMP: + { + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_CLAMP); + } break; + default: break; + } +} + +//------------------------------------------------------------------------------------ +// Texture drawing functions +//------------------------------------------------------------------------------------ +// Draw a texture +void DrawTexture(Texture2D texture, int posX, int posY, Color tint) +{ + DrawTextureEx(texture, (Vector2){ (float)posX, (float)posY }, 0.0f, 1.0f, tint); +} + +// Draw a texture with position defined as Vector2 +void DrawTextureV(Texture2D texture, Vector2 position, Color tint) +{ + DrawTextureEx(texture, position, 0, 1.0f, tint); +} + +// Draw a texture with rotation and scale +void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint) +{ + Rectangle srcrec = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; + Rectangle dstrec = { position.x, position.y, (float)texture.width*scale, (float)texture.height*scale }; + Vector2 origin = { 0.0f, 0.0f }; + + DrawTexturePro(texture, srcrec, dstrec, origin, rotation, tint); +} + +// Draw a part of a texture (defined by a rectangle) +void DrawTextureRec(Texture2D texture, Rectangle rec, Vector2 position, Color tint) +{ + Rectangle dstrec = { position.x, position.y, fabsf(rec.width), fabsf(rec.height) }; + Vector2 origin = { 0.0f, 0.0f }; + + DrawTexturePro(texture, rec, dstrec, origin, 0.0f, tint); +} + +// Draw a part of a texture (defined by a rectangle) with 'pro' parameters +// NOTE: origin is relative to destination rectangle size +void DrawTexturePro(Texture2D texture, Rectangle srcrec, Rectangle dstrec, Vector2 origin, float rotation, Color tint) +{ + // Check if texture is valid + if (texture.id > 0) + { + float width = (float)texture.width; + float height = (float)texture.height; + + bool flipX = false; + + if (srcrec.width < 0) { flipX = true; srcrec.width *= -1; } + if (srcrec.height < 0) srcrec.y -= srcrec.height; + + if (dstrec.width < 0) dstrec.width *= -1; + if (dstrec.height < 0) dstrec.height *= -1; + + Vector2 topLeft = { 0 }; + Vector2 topRight = { 0 }; + Vector2 bottomLeft = { 0 }; + Vector2 bottomRight = { 0 }; + + // Only calculate rotation if needed + if (rotation == 0.0f) + { + float x = dstrec.x - origin.x; + float y = dstrec.y - origin.y; + topLeft = (Vector2){ x, y }; + topRight = (Vector2){ x + dstrec.width, y }; + bottomLeft = (Vector2){ x, y + dstrec.height }; + bottomRight = (Vector2){ x + dstrec.width, y + dstrec.height }; + } + else + { + float sinRotation = sinf(rotation*DEG2RAD); + float cosRotation = cosf(rotation*DEG2RAD); + float x = dstrec.x; + float y = dstrec.y; + float dx = -origin.x; + float dy = -origin.y; + + topLeft.x = x + dx*cosRotation - dy*sinRotation; + topLeft.y = y + dx*sinRotation + dy*cosRotation; + + topRight.x = x + (dx + dstrec.width)*cosRotation - dy*sinRotation; + topRight.y = y + (dx + dstrec.width)*sinRotation + dy*cosRotation; + + bottomLeft.x = x + dx*cosRotation - (dy + dstrec.height)*sinRotation; + bottomLeft.y = y + dx*sinRotation + (dy + dstrec.height)*cosRotation; + + bottomRight.x = x + (dx + dstrec.width)*cosRotation - (dy + dstrec.height)*sinRotation; + bottomRight.y = y + (dx + dstrec.width)*sinRotation + (dy + dstrec.height)*cosRotation; + } + + rlSetTexture(texture.id); + rlBegin(RL_QUADS); + + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + // Top-left corner for texture and quad + if (flipX) rlTexCoord2f((srcrec.x + srcrec.width)/width, srcrec.y/height); + else rlTexCoord2f(srcrec.x/width, srcrec.y/height); + rlVertex2f(topLeft.x, topLeft.y); + + // Bottom-left corner for texture and quad + if (flipX) rlTexCoord2f((srcrec.x + srcrec.width)/width, (srcrec.y + srcrec.height)/height); + else rlTexCoord2f(srcrec.x/width, (srcrec.y + srcrec.height)/height); + rlVertex2f(bottomLeft.x, bottomLeft.y); + + // Bottom-right corner for texture and quad + if (flipX) rlTexCoord2f(srcrec.x/width, (srcrec.y + srcrec.height)/height); + else rlTexCoord2f((srcrec.x + srcrec.width)/width, (srcrec.y + srcrec.height)/height); + rlVertex2f(bottomRight.x, bottomRight.y); + + // Top-right corner for texture and quad + if (flipX) rlTexCoord2f(srcrec.x/width, srcrec.y/height); + else rlTexCoord2f((srcrec.x + srcrec.width)/width, srcrec.y/height); + rlVertex2f(topRight.x, topRight.y); + + rlEnd(); + rlSetTexture(0); + + // NOTE: Vertex position can be transformed using matrices + // but the process is way more costly than calculating + // the vertex positions manually, like done above + // Old implementation is left here for educational purposes, + // in case someone wants to do some performance test + /* + rlSetTexture(texture.id); + rlPushMatrix(); + rlTranslatef(dstrec.x, dstrec.y, 0.0f); + if (rotation != 0.0f) rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + rlTranslatef(-origin.x, -origin.y, 0.0f); + + rlBegin(RL_QUADS); + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + // Bottom-left corner for texture and quad + if (flipX) rlTexCoord2f((srcrec.x + srcrec.width)/width, srcrec.y/height); + else rlTexCoord2f(srcrec.x/width, srcrec.y/height); + rlVertex2f(0.0f, 0.0f); + + // Bottom-right corner for texture and quad + if (flipX) rlTexCoord2f((srcrec.x + srcrec.width)/width, (srcrec.y + srcrec.height)/height); + else rlTexCoord2f(srcrec.x/width, (srcrec.y + srcrec.height)/height); + rlVertex2f(0.0f, dstrec.height); + + // Top-right corner for texture and quad + if (flipX) rlTexCoord2f(srcrec.x/width, (srcrec.y + srcrec.height)/height); + else rlTexCoord2f((srcrec.x + srcrec.width)/width, (srcrec.y + srcrec.height)/height); + rlVertex2f(dstrec.width, dstrec.height); + + // Top-left corner for texture and quad + if (flipX) rlTexCoord2f(srcrec.x/width, srcrec.y/height); + else rlTexCoord2f((srcrec.x + srcrec.width)/width, srcrec.y/height); + rlVertex2f(dstrec.width, 0.0f); + rlEnd(); + rlPopMatrix(); + rlSetTexture(0); + */ + } +} + +// Draw a texture (or part of it) that stretches or shrinks nicely using n-patch info +void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dstrec, Vector2 origin, float rotation, Color tint) +{ + if (texture.id > 0) + { + float width = (float)texture.width; + float height = (float)texture.height; + + float patchWidth = ((int)dstrec.width <= 0)? 0.0f : dstrec.width; + float patchHeight = ((int)dstrec.height <= 0)? 0.0f : dstrec.height; + + if (nPatchInfo.source.width < 0) nPatchInfo.source.x -= nPatchInfo.source.width; + if (nPatchInfo.source.height < 0) nPatchInfo.source.y -= nPatchInfo.source.height; + if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) patchHeight = nPatchInfo.source.height; + if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) patchWidth = nPatchInfo.source.width; + + bool drawCenter = true; + bool drawMiddle = true; + float leftBorder = (float)nPatchInfo.left; + float topBorder = (float)nPatchInfo.top; + float rightBorder = (float)nPatchInfo.right; + float bottomBorder = (float)nPatchInfo.bottom; + + // Adjust the lateral (left and right) border widths in case patchWidth < texture.width + if (patchWidth <= (leftBorder + rightBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_VERTICAL) + { + drawCenter = false; + leftBorder = (leftBorder/(leftBorder + rightBorder))*patchWidth; + rightBorder = patchWidth - leftBorder; + } + + // Adjust the lateral (top and bottom) border heights in case patchHeight < texture.height + if (patchHeight <= (topBorder + bottomBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_HORIZONTAL) + { + drawMiddle = false; + topBorder = (topBorder/(topBorder + bottomBorder))*patchHeight; + bottomBorder = patchHeight - topBorder; + } + + Vector2 vertA = { 0 }; + Vector2 vertB = { 0 }; + Vector2 vertC = { 0 }; + Vector2 vertD = { 0 }; + vertA.x = 0.0f; // Outer left + vertA.y = 0.0f; // Outer top + vertB.x = leftBorder; // Inner left + vertB.y = topBorder; // Inner top + vertC.x = patchWidth - rightBorder; // Inner right + vertC.y = patchHeight - bottomBorder; // Inner bottom + vertD.x = patchWidth; // Outer right + vertD.y = patchHeight; // Outer bottom + + Vector2 coordA = { 0 }; + Vector2 coordB = { 0 }; + Vector2 coordC = { 0 }; + Vector2 coordD = { 0 }; + coordA.x = nPatchInfo.source.x/width; + coordA.y = nPatchInfo.source.y/height; + coordB.x = (nPatchInfo.source.x + leftBorder)/width; + coordB.y = (nPatchInfo.source.y + topBorder)/height; + coordC.x = (nPatchInfo.source.x + nPatchInfo.source.width - rightBorder)/width; + coordC.y = (nPatchInfo.source.y + nPatchInfo.source.height - bottomBorder)/height; + coordD.x = (nPatchInfo.source.x + nPatchInfo.source.width)/width; + coordD.y = (nPatchInfo.source.y + nPatchInfo.source.height)/height; + + rlSetTexture(texture.id); + + rlPushMatrix(); + rlTranslatef(dstrec.x, dstrec.y, 0.0f); + rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + rlTranslatef(-origin.x, -origin.y, 0.0f); + + rlBegin(RL_QUADS); + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + if (nPatchInfo.layout == NPATCH_NINE_PATCH) + { + // ------------------------------------------------------------ + // TOP-LEFT QUAD + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad + if (drawCenter) + { + // TOP-CENTER QUAD + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-left corner for texture and quad + } + // TOP-RIGHT QUAD + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-left corner for texture and quad + if (drawMiddle) + { + // ------------------------------------------------------------ + // MIDDLE-LEFT QUAD + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Top-left corner for texture and quad + if (drawCenter) + { + // MIDDLE-CENTER QUAD + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordB.y); rlVertex2f(vertB.x, vertB.y); // Top-left corner for texture and quad + } + + // MIDDLE-RIGHT QUAD + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordB.y); rlVertex2f(vertC.x, vertB.y); // Top-left corner for texture and quad + } + + // ------------------------------------------------------------ + // BOTTOM-LEFT QUAD + rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad + if (drawCenter) + { + // BOTTOM-CENTER QUAD + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordC.y); rlVertex2f(vertB.x, vertC.y); // Top-left corner for texture and quad + } + + // BOTTOM-RIGHT QUAD + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-left corner for texture and quad + } + else if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) + { + // TOP QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad + if (drawCenter) + { + // MIDDLE QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordB.y); rlVertex2f(vertD.x, vertB.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordB.y); rlVertex2f(vertA.x, vertB.y); // Top-left corner for texture and quad + } + // BOTTOM QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad + } + else if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) + { + // LEFT QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordA.x, coordD.y); rlVertex2f(vertA.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordA.x, coordA.y); rlVertex2f(vertA.x, vertA.y); // Top-left corner for texture and quad + if (drawCenter) + { + // CENTER QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordB.x, coordD.y); rlVertex2f(vertB.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordB.x, coordA.y); rlVertex2f(vertB.x, vertA.y); // Top-left corner for texture and quad + } + // RIGHT QUAD + // ----------------------------------------------------------- + // Texture coords Vertices + rlTexCoord2f(coordC.x, coordD.y); rlVertex2f(vertC.x, vertD.y); // Bottom-left corner for texture and quad + rlTexCoord2f(coordD.x, coordD.y); rlVertex2f(vertD.x, vertD.y); // Bottom-right corner for texture and quad + rlTexCoord2f(coordD.x, coordA.y); rlVertex2f(vertD.x, vertA.y); // Top-right corner for texture and quad + rlTexCoord2f(coordC.x, coordA.y); rlVertex2f(vertC.x, vertA.y); // Top-left corner for texture and quad + } + rlEnd(); + rlPopMatrix(); + + rlSetTexture(0); + } +} + +// Check if two colors are equal +bool ColorIsEqual(Color col1, Color col2) +{ + bool result = false; + + if ((col1.r == col2.r) && (col1.g == col2.g) && (col1.b == col2.b) && (col1.a == col2.a)) result = true; + + return result; +} + +// Get color with alpha applied, alpha goes from 0.0f to 1.0f +Color Fade(Color color, float alpha) +{ + Color result = color; + + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + result.a = (unsigned char)(255.0f*alpha); + + return result; +} + +// Get hexadecimal value for a Color +int ColorToInt(Color color) +{ + int result = 0; + + result = (int)(((unsigned int)color.r << 24) | + ((unsigned int)color.g << 16) | + ((unsigned int)color.b << 8) | + (unsigned int)color.a); + + return result; +} + +// Get color normalized as float [0..1] +Vector4 ColorNormalize(Color color) +{ + Vector4 result; + + result.x = (float)color.r/255.0f; + result.y = (float)color.g/255.0f; + result.z = (float)color.b/255.0f; + result.w = (float)color.a/255.0f; + + return result; +} + +// Get color from normalized values [0..1] +Color ColorFromNormalized(Vector4 normalized) +{ + Color result; + + result.r = (unsigned char)(normalized.x*255.0f); + result.g = (unsigned char)(normalized.y*255.0f); + result.b = (unsigned char)(normalized.z*255.0f); + result.a = (unsigned char)(normalized.w*255.0f); + + return result; +} + +// Get HSV values for a Color +// NOTE: Hue is returned as degrees [0..360] +Vector3 ColorToHSV(Color color) +{ + Vector3 hsv = { 0 }; + Vector3 rgb = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + float min = 0.0f; + float max = 0.0f; + float delta = 0.0f; + + min = (rgb.x < rgb.y)? rgb.x : rgb.y; + min = (min < rgb.z)? min : rgb.z; + + max = (rgb.x > rgb.y)? rgb.x : rgb.y; + max = (max > rgb.z)? max : rgb.z; + + hsv.z = max; // Value + delta = max - min; + + if (delta < 0.00001f) + { + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + if (max > 0.0f) + { + // NOTE: If max is 0, this divide would cause a crash + hsv.y = (delta/max); // Saturation + } + else + { + // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined + hsv.y = 0.0f; + hsv.x = NAN; // Undefined + return hsv; + } + + // NOTE: Comparing float values could not work properly + if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta + else + { + if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow + else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan + } + + hsv.x *= 60.0f; // Convert to degrees + + if (hsv.x < 0.0f) hsv.x += 360.0f; + + return hsv; +} + +// Get a Color from HSV values +// Implementation reference: https://en.wikipedia.org/wiki/HSL_and_HSV#Alternative_HSV_conversion +// NOTE: Color->HSV->Color conversion will not yield exactly the same color due to rounding errors +// Hue is provided in degrees: [0..360] +// Saturation/Value are provided normalized: [0.0f..1.0f] +Color ColorFromHSV(float hue, float saturation, float value) +{ + Color color = { 0, 0, 0, 255 }; + + // Red channel + float k = fmodf((5.0f + hue/60.0f), 6); + float t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.r = (unsigned char)((value - value*saturation*k)*255.0f); + + // Green channel + k = fmodf((3.0f + hue/60.0f), 6); + t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.g = (unsigned char)((value - value*saturation*k)*255.0f); + + // Blue channel + k = fmodf((1.0f + hue/60.0f), 6); + t = 4.0f - k; + k = (t < k)? t : k; + k = (k < 1)? k : 1; + k = (k > 0)? k : 0; + color.b = (unsigned char)((value - value*saturation*k)*255.0f); + + return color; +} + +// Get color multiplied with another color +Color ColorTint(Color color, Color tint) +{ + Color result = color; + + unsigned char r = (unsigned char)(((int)color.r*(int)tint.r)/255); + unsigned char g = (unsigned char)(((int)color.g*(int)tint.g)/255); + unsigned char b = (unsigned char)(((int)color.b*(int)tint.b)/255); + unsigned char a = (unsigned char)(((int)color.a*(int)tint.a)/255); + + result.r = r; + result.g = g; + result.b = b; + result.a = a; + + return result; +} + +// Get color with brightness correction, brightness factor goes from -1.0f to 1.0f +Color ColorBrightness(Color color, float factor) +{ + Color result = color; + + if (factor > 1.0f) factor = 1.0f; + else if (factor < -1.0f) factor = -1.0f; + + float red = (float)color.r; + float green = (float)color.g; + float blue = (float)color.b; + + if (factor < 0.0f) + { + factor = 1.0f + factor; + red *= factor; + green *= factor; + blue *= factor; + } + else + { + red = (255 - red)*factor + red; + green = (255 - green)*factor + green; + blue = (255 - blue)*factor + blue; + } + + result.r = (unsigned char)red; + result.g = (unsigned char)green; + result.b = (unsigned char)blue; + + return result; +} + +// Get color with contrast correction +// NOTE: Contrast values between -1.0f and 1.0f +Color ColorContrast(Color color, float contrast) +{ + Color result = color; + + if (contrast < -1.0f) contrast = -1.0f; + else if (contrast > 1.0f) contrast = 1.0f; + + contrast = (1.0f + contrast); + contrast *= contrast; + + float pR = (float)color.r/255.0f; + pR -= 0.5f; + pR *= contrast; + pR += 0.5f; + pR *= 255; + if (pR < 0) pR = 0; + else if (pR > 255) pR = 255; + + float pG = (float)color.g/255.0f; + pG -= 0.5f; + pG *= contrast; + pG += 0.5f; + pG *= 255; + if (pG < 0) pG = 0; + else if (pG > 255) pG = 255; + + float pB = (float)color.b/255.0f; + pB -= 0.5f; + pB *= contrast; + pB += 0.5f; + pB *= 255; + if (pB < 0) pB = 0; + else if (pB > 255) pB = 255; + + result.r = (unsigned char)pR; + result.g = (unsigned char)pG; + result.b = (unsigned char)pB; + + return result; +} + +// Get color with alpha applied, alpha goes from 0.0f to 1.0f +Color ColorAlpha(Color color, float alpha) +{ + Color result = color; + + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + result.a = (unsigned char)(255.0f*alpha); + + return result; +} + +// Get src alpha-blended into dst color with tint +Color ColorAlphaBlend(Color dst, Color src, Color tint) +{ + Color result = WHITE; + + // Apply color tint to source color + src.r = (unsigned char)(((unsigned int)src.r*((unsigned int)tint.r+1)) >> 8); + src.g = (unsigned char)(((unsigned int)src.g*((unsigned int)tint.g+1)) >> 8); + src.b = (unsigned char)(((unsigned int)src.b*((unsigned int)tint.b+1)) >> 8); + src.a = (unsigned char)(((unsigned int)src.a*((unsigned int)tint.a+1)) >> 8); + +//#define COLORALPHABLEND_FLOAT +#define COLORALPHABLEND_INTEGERS +#if defined(COLORALPHABLEND_INTEGERS) + if (src.a == 0) result = dst; + else if (src.a == 255) result = src; + else + { + unsigned int alpha = (unsigned int)src.a + 1; // Shifting by 8 (dividing by 256), so need to take that excess into account + result.a = (unsigned char)(((unsigned int)alpha*256 + (unsigned int)dst.a*(256 - alpha)) >> 8); + + if (result.a > 0) + { + result.r = (unsigned char)((((unsigned int)src.r*alpha*256 + (unsigned int)dst.r*(unsigned int)dst.a*(256 - alpha))/result.a) >> 8); + result.g = (unsigned char)((((unsigned int)src.g*alpha*256 + (unsigned int)dst.g*(unsigned int)dst.a*(256 - alpha))/result.a) >> 8); + result.b = (unsigned char)((((unsigned int)src.b*alpha*256 + (unsigned int)dst.b*(unsigned int)dst.a*(256 - alpha))/result.a) >> 8); + } + } +#endif +#if defined(COLORALPHABLEND_FLOAT) + if (src.a == 0) result = dst; + else if (src.a == 255) result = src; + else + { + Vector4 fdst = ColorNormalize(dst); + Vector4 fsrc = ColorNormalize(src); + Vector4 ftint = ColorNormalize(tint); + Vector4 fout = { 0 }; + + fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); + + if (fout.w > 0.0f) + { + fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; + fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; + fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; + } + + result = (Color){ (unsigned char)(fout.x*255.0f), (unsigned char)(fout.y*255.0f), (unsigned char)(fout.z*255.0f), (unsigned char)(fout.w*255.0f) }; + } +#endif + + return result; +} + +// Get color lerp interpolation between two colors, factor [0.0f..1.0f] +Color ColorLerp(Color color1, Color color2, float factor) +{ + Color color = { 0 }; + + if (factor < 0.0f) factor = 0.0f; + else if (factor > 1.0f) factor = 1.0f; + + color.r = (unsigned char)((1.0f - factor)*color1.r + factor*color2.r); + color.g = (unsigned char)((1.0f - factor)*color1.g + factor*color2.g); + color.b = (unsigned char)((1.0f - factor)*color1.b + factor*color2.b); + color.a = (unsigned char)((1.0f - factor)*color1.a + factor*color2.a); + + return color; +} + +// Get a Color struct from hexadecimal value +Color GetColor(unsigned int hexValue) +{ + Color color; + + color.r = (unsigned char)(hexValue >> 24) & 0xff; + color.g = (unsigned char)(hexValue >> 16) & 0xff; + color.b = (unsigned char)(hexValue >> 8) & 0xff; + color.a = (unsigned char)hexValue & 0xff; + + return color; +} + +// Get color from a pixel from certain format +Color GetPixelColor(const void *srcPtr, int format) +{ + Color color = { 0 }; + + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], 255 }; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1] }; break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + color.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); + color.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 5) & 0b0000000000111111)*255/63); + color.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31); + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + color.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); + color.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 6) & 0b0000000000011111)*255/31); + color.b = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 1) & 0b0000000000011111)*255/31); + color.a = (((unsigned short *)srcPtr)[0] & 0b0000000000000001)? 255 : 0; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + color.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 12)*255/15); + color.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 8) & 0b0000000000001111)*255/15); + color.b = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 4) & 0b0000000000001111)*255/15); + color.a = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000001111)*255/15); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], ((unsigned char *)srcPtr)[3] }; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: color = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], 255 }; break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + // NOTE: Pixel normalized float value is converted to [0..255] + color.r = (unsigned char)(((float *)srcPtr)[0]*255.0f); + color.g = (unsigned char)(((float *)srcPtr)[0]*255.0f); + color.b = (unsigned char)(((float *)srcPtr)[0]*255.0f); + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + // NOTE: Pixel normalized float value is converted to [0..255] + color.r = (unsigned char)(((float *)srcPtr)[0]*255.0f); + color.g = (unsigned char)(((float *)srcPtr)[1]*255.0f); + color.b = (unsigned char)(((float *)srcPtr)[2]*255.0f); + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + // NOTE: Pixel normalized float value is converted to [0..255] + color.r = (unsigned char)(((float *)srcPtr)[0]*255.0f); + color.g = (unsigned char)(((float *)srcPtr)[1]*255.0f); + color.b = (unsigned char)(((float *)srcPtr)[2]*255.0f); + color.a = (unsigned char)(((float *)srcPtr)[3]*255.0f); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R16: + { + // NOTE: Pixel normalized float value is converted to [0..255] + color.r = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[0])*255.0f); + color.g = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[0])*255.0f); + color.b = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[0])*255.0f); + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16: + { + // NOTE: Pixel normalized float value is converted to [0..255] + color.r = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[0])*255.0f); + color.g = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[1])*255.0f); + color.b = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[2])*255.0f); + color.a = 255; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + { + // NOTE: Pixel normalized float value is converted to [0..255] + color.r = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[0])*255.0f); + color.g = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[1])*255.0f); + color.b = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[2])*255.0f); + color.a = (unsigned char)(HalfToFloat(((unsigned short *)srcPtr)[3])*255.0f); + + } break; + default: break; + } + + return color; +} + +// Set pixel color formatted into destination pointer +void SetPixelColor(void *dstPtr, Color color, int format) +{ + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dstPtr)[0] = gray; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + // NOTE: Calculate grayscale equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); + + ((unsigned char *)dstPtr)[0] = gray; + ((unsigned char *)dstPtr)[1] = color.a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + // NOTE: Calculate R5G6B5 equivalent color + Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*63.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*31.0f)); + unsigned char g = (unsigned char)(round(coln.y*31.0f)); + unsigned char b = (unsigned char)(round(coln.z*31.0f)); + unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0; + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + // NOTE: Calculate R5G5B5A1 equivalent color + Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; + + unsigned char r = (unsigned char)(round(coln.x*15.0f)); + unsigned char g = (unsigned char)(round(coln.y*15.0f)); + unsigned char b = (unsigned char)(round(coln.z*15.0f)); + unsigned char a = (unsigned char)(round(coln.w*15.0f)); + + ((unsigned short *)dstPtr)[0] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + ((unsigned char *)dstPtr)[0] = color.r; + ((unsigned char *)dstPtr)[1] = color.g; + ((unsigned char *)dstPtr)[2] = color.b; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + ((unsigned char *)dstPtr)[0] = color.r; + ((unsigned char *)dstPtr)[1] = color.g; + ((unsigned char *)dstPtr)[2] = color.b; + ((unsigned char *)dstPtr)[3] = color.a; + + } break; + default: break; + } +} + +// Get pixel data size in bytes for certain format +// NOTE: Size can be requested for Image or Texture data +int GetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case PIXELFORMAT_UNCOMPRESSED_R16: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 16*3; break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 16*4; break; + case PIXELFORMAT_COMPRESSED_DXT1_RGB: + case PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case PIXELFORMAT_COMPRESSED_ETC1_RGB: + case PIXELFORMAT_COMPRESSED_ETC2_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + unsigned long long dataSizeBytes = ((unsigned long long)width*height*bpp) >> 3; // Get size in bytes (dividing by 8) + + if (dataSizeBytes < INT_MAX) + { + dataSize = (int)dataSizeBytes; + + // Most compressed formats works on 4x4 blocks, + // if texture is smaller, minimum dataSize is 8 or 16 + if ((width < 4) && (height < 4)) + { + if ((format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + } + } + + // NOTE: In case required image data larger than 2GB, no memory allocated at all (NULL) + if (dataSize == 0) TRACELOG(LOG_WARNING, "Requested image size is larger than 2GB, it can not be allocated"); + + return dataSize; +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +// Convert half-float (stored as unsigned short) to float +// REF: https://stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion/60047308#60047308 +static float HalfToFloat(unsigned short x) +{ + float result = 0.0f; + + union { + float fm; + unsigned int ui; + } uni; + + const unsigned int e = (x & 0x7c00) >> 10; // Exponent + const unsigned int m = (x & 0x03ff) << 13; // Mantissa + uni.fm = (float)m; + const unsigned int v = uni.ui >> 23; // Evil log2 bit hack to count leading zeros in denormalized format + uni.ui = (x & 0x8000) << 16 | (e != 0)*((e + 112) << 23 | m) | ((e == 0)&(m != 0))*((v - 37) << 23 | ((m << (150 - v)) & 0x007fe000)); // sign : normalized : denormalized + + result = uni.fm; + + return result; +} + +// Convert float to half-float (stored as unsigned short) +static unsigned short FloatToHalf(float x) +{ + unsigned short result = 0; + + union { + float fm; + unsigned int ui; + } uni; + uni.fm = x; + + const unsigned int b = uni.ui + 0x00001000; // Round-to-nearest-even: add last bit after truncated mantissa + const unsigned int e = (b & 0x7f800000) >> 23; // Exponent + const unsigned int m = b & 0x007fffff; // Mantissa; in line below: 0x007ff000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding + + result = (b & 0x80000000) >> 16 | (e > 112)*((((e - 112) << 10) & 0x7c00) | m >> 13) | ((e < 113) & (e > 101))*((((0x007ff000 + m) >> (125 - e)) + 1) >> 1) | (e > 143)*0x7fff; // sign : normalized : denormalized : saturate + + return result; +} + +// Get pixel data from image as Vector4 array (float normalized) +static Vector4 *LoadImageDataNormalized(Image image) +{ + Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4)); + + if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + else + { + for (int i = 0, k = 0; i < image.width*image.height; i++) + { + switch (image.format) + { + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + pixels[i].x = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[i]/255.0f; + pixels[i].w = 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].w = (float)((unsigned char *)image.data)[k + 1]/255.0f; + + k += 2; + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + pixels[i].y = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31); + pixels[i].z = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31); + pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31); + pixels[i].y = (float)((pixel & 0b0000011111100000) >> 5)*(1.0f/63); + pixels[i].z = (float)(pixel & 0b0000000000011111)*(1.0f/31); + pixels[i].w = 1.0f; + + } break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[i]; + + pixels[i].x = (float)((pixel & 0b1111000000000000) >> 12)*(1.0f/15); + pixels[i].y = (float)((pixel & 0b0000111100000000) >> 8)*(1.0f/15); + pixels[i].z = (float)((pixel & 0b0000000011110000) >> 4)*(1.0f/15); + pixels[i].w = (float)(pixel & 0b0000000000001111)*(1.0f/15); + + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; + pixels[i].w = (float)((unsigned char *)image.data)[k + 3]/255.0f; + + k += 4; + } break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; + pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; + pixels[i].z = (float)((unsigned char *)image.data)[k + 2]/255.0f; + pixels[i].w = 1.0f; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = 0.0f; + pixels[i].z = 0.0f; + pixels[i].w = 1.0f; + + k += 1; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = ((float *)image.data)[k + 1]; + pixels[i].z = ((float *)image.data)[k + 2]; + pixels[i].w = 1.0f; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + pixels[i].x = ((float *)image.data)[k]; + pixels[i].y = ((float *)image.data)[k + 1]; + pixels[i].z = ((float *)image.data)[k + 2]; + pixels[i].w = ((float *)image.data)[k + 3]; + + k += 4; + } break; + case PIXELFORMAT_UNCOMPRESSED_R16: + { + pixels[i].x = HalfToFloat(((unsigned short *)image.data)[k]); + pixels[i].y = 0.0f; + pixels[i].z = 0.0f; + pixels[i].w = 1.0f; + + k += 1; + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16: + { + pixels[i].x = HalfToFloat(((unsigned short *)image.data)[k]); + pixels[i].y = HalfToFloat(((unsigned short *)image.data)[k + 1]); + pixels[i].z = HalfToFloat(((unsigned short *)image.data)[k + 2]); + pixels[i].w = 1.0f; + + k += 3; + } break; + case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + { + pixels[i].x = HalfToFloat(((unsigned short *)image.data)[k]); + pixels[i].y = HalfToFloat(((unsigned short *)image.data)[k + 1]); + pixels[i].z = HalfToFloat(((unsigned short *)image.data)[k + 2]); + pixels[i].w = HalfToFloat(((unsigned short *)image.data)[k + 3]); + + k += 4; + } break; + default: break; + } + } + } + + return pixels; +} + +#endif // SUPPORT_MODULE_RTEXTURES diff --git a/src/libraries/raylib/raylib/src/shell.html b/src/libraries/raylib/raylib/src/shell.html new file mode 100644 index 0000000..f1bf066 --- /dev/null +++ b/src/libraries/raylib/raylib/src/shell.html @@ -0,0 +1,334 @@ + + + + + + + raylib web game + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ +
+ +
+ + + + + + + + + + {{{ SCRIPT }}} + + diff --git a/src/libraries/raylib/raymath.odin b/src/libraries/raylib/raymath.odin new file mode 100644 index 0000000..3243725 --- /dev/null +++ b/src/libraries/raylib/raymath.odin @@ -0,0 +1,873 @@ +package raylib + +import "core:math" +import "core:math/linalg" + +EPSILON :: 0.000001 + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Utils math +//---------------------------------------------------------------------------------- + + +// Clamp float value +@(require_results) +Clamp :: proc "c" (value: f32, min, max: f32) -> f32 { + return clamp(value, min, max) +} + +// Calculate linear interpolation between two floats +@(require_results) +Lerp :: proc "c" (start, end: f32, amount: f32) -> f32 { + return start*(1-amount) + end*amount +} + +// Normalize input value within input range +@(require_results) +Normalize :: proc "c" (value: f32, start, end: f32) -> f32 { + return (value - start) / (end - start) +} + +// Remap input value within input range to output range +@(require_results) +Remap :: proc "c" (value: f32, inputStart, inputEnd: f32, outputStart, outputEnd: f32) -> f32 { + return (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart +} + +// Wrap input value from min to max +@(require_results) +Wrap :: proc "c" (value: f32, min, max: f32) -> f32 { + return value - (max - min)*math.floor((value - min)/(max - min)) +} + +// Check whether two given floats are almost equal +@(require_results) +FloatEquals :: proc "c" (x, y: f32) -> bool { + return abs(x - y) <= EPSILON*fmaxf(1.0, fmaxf(abs(x), abs(y))) +} + + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector2 math +//---------------------------------------------------------------------------------- + + +// Vector with components value 0.0 +@(require_results, deprecated="Prefer Vector2(0)") +Vector2Zero :: proc "c" () -> Vector2 { + return Vector2(0) +} +// Vector with components value 1.0 +@(require_results, deprecated="Prefer Vector2(1)") +Vector2One :: proc "c" () -> Vector2 { + return Vector2(1) +} +// Add two vectors (v1 + v2) +@(require_results, deprecated="Prefer v1 + v2") +Vector2Add :: proc "c" (v1, v2: Vector2) -> Vector2 { + return v1 + v2 +} +// Add vector and float value +@(require_results, deprecated="Prefer v + value") +Vector2AddValue :: proc "c" (v: Vector2, value: f32) -> Vector2 { + return v + value +} +// Subtract two vectors (v1 - v2) +@(require_results, deprecated="Prefer a - b") +Vector2Subtract :: proc "c" (a, b: Vector2) -> Vector2 { + return a - b +} +// Subtract vector by float value +@(require_results, deprecated="Prefer v + value") +Vector2SubtractValue :: proc "c" (v: Vector2, value: f32) -> Vector2 { + return v - value +} +// Calculate vector length +@(require_results) +Vector2Length :: proc "c" (v: Vector2) -> f32 { + return linalg.length(v) +} +// Calculate vector square length +@(require_results) +Vector2LengthSqr :: proc "c" (v: Vector2) -> f32 { + return linalg.length2(v) +} +// Calculate two vectors dot product +@(require_results) +Vector2DotProduct :: proc "c" (v1, v2: Vector2) -> f32 { + return linalg.dot(v1, v2) +} +// Calculate two vectors dot product +@(require_results) +Vector2CrossProduct :: proc "c" (v1, v2: Vector2) -> f32 { + return linalg.cross(v1, v2) +} +// Calculate distance between two vectors +@(require_results) +Vector2Distance :: proc "c" (v1, v2: Vector2) -> f32 { + return linalg.distance(v1, v2) +} +// Calculate square distance between two vectors +@(require_results) +Vector2DistanceSqrt :: proc "c" (v1, v2: Vector2) -> f32 { + return linalg.length2(v2-v1) +} +// Calculate angle between two vectors +// NOTE: Angle is calculated from origin point (0, 0) +@(require_results) +Vector2Angle :: proc "c" (v1, v2: Vector2) -> f32 { + return linalg.angle_between(v1, v2) +} + +// Calculate angle defined by a two vectors line +// NOTE: Parameters need to be normalized +// Current implementation should be aligned with glm::angle +@(require_results) +Vector2LineAngle :: proc "c" (start, end: Vector2) -> f32 { + // TODO(10/9/2023): Currently angles move clockwise, determine if this is wanted behavior + return -math.atan2(end.y - start.y, end.x - start.x) +} + +// Scale vector (multiply by value) +@(require_results, deprecated="Prefer v * scale") +Vector2Scale :: proc "c" (v: Vector2, scale: f32) -> Vector2 { + return v * scale +} +// Multiply vector by vector +@(require_results, deprecated="Prefer v1 * v2") +Vector2Multiply :: proc "c" (v1, v2: Vector2) -> Vector2 { + return v1 * v2 +} +// Negate vector +@(require_results, deprecated="Prefer -v") +Vector2Negate :: proc "c" (v: Vector2) -> Vector2 { + return -v +} +// Divide vector by vector +@(require_results, deprecated="Prefer v1 / v2") +Vector2Divide :: proc "c" (v1, v2: Vector2) -> Vector2 { + return v1 / v2 +} +// Normalize provided vector +@(require_results) +Vector2Normalize :: proc "c" (v: Vector2) -> Vector2 { + return linalg.normalize0(v) +} +// Transforms a Vector2 by a given Matrix +@(require_results) +Vector2Transform :: proc "c" (v: Vector2, m: Matrix) -> Vector2 { + v4 := Vector4{v.x, v.y, 0, 1} + return (m * v4).xy +} +// Calculate linear interpolation between two vectors +@(require_results, deprecated="Prefer = linalg.lerp(v1, v2, amount)") +Vector2Lerp :: proc "c" (v1, v2: Vector2, amount: f32) -> Vector2 { + return linalg.lerp(v1, v2, Vector2(amount)) +} +// Calculate reflected vector to normal +@(require_results, deprecated="Prefer = linalg.reflect(v, normal)") +Vector2Reflect :: proc "c" (v, normal: Vector2) -> Vector2 { + return linalg.reflect(v, normal) +} +// Rotate vector by angle +@(require_results) +Vector2Rotate :: proc "c" (v: Vector2, angle: f32) -> Vector2 { + c, s := math.cos(angle), math.sin(angle) + + return Vector2{ + v.x*c - v.y*s, + v.x*s + v.y*c, + } +} + +// Move Vector towards target +@(require_results) +Vector2MoveTowards :: proc "c" (v, target: Vector2, maxDistance: f32) -> Vector2 { + dv := target - v + value := linalg.dot(dv, dv) + + if value == 0 || (maxDistance >= 0 && value <= maxDistance*maxDistance) { + return target + } + + dist := math.sqrt(value) + return v + dv/dist*maxDistance +} + +// Invert the given vector +@(require_results, deprecated="Prefer 1.0/v") +Vector2Invert :: proc "c" (v: Vector2) -> Vector2 { + return 1.0/v +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +@(require_results) +Vector2Clamp :: proc "c" (v: Vector2, min, max: Vector2) -> Vector2 { + return Vector2{ + clamp(v.x, min.x, max.x), + clamp(v.y, min.y, max.y), + } +} + +// Clamp the magnitude of the vector between two min and max values +@(require_results) +Vector2ClampValue :: proc "c" (v: Vector2, min, max: f32) -> Vector2 { + result := v + + length := linalg.dot(v, v) + if length > 0 { + length = math.sqrt(length) + scale := f32(1) + if length < min { + scale = min/length + } else if length > max { + scale = max/length + } + result = v*scale + } + return result +} + +@(require_results) +Vector2Equals :: proc "c" (p, q: Vector2) -> bool { + return FloatEquals(p.x, q.x) && + FloatEquals(p.y, q.y) +} + + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector3 math +//---------------------------------------------------------------------------------- + + +// Vector with components value 0.0 +@(require_results, deprecated="Prefer Vector3(0)") +Vector3Zero :: proc "c" () -> Vector3 { + return Vector3(0) +} +// Vector with components value 1.0 +@(require_results, deprecated="Prefer Vector3(1)") +Vector3One :: proc "c" () -> Vector3 { + return Vector3(1) +} +// Add two vectors (v1 + v2) +@(require_results, deprecated="Prefer v1 + v2") +Vector3Add :: proc "c" (v1, v2: Vector3) -> Vector3 { + return v1 + v2 +} +// Add vector and float value +@(require_results, deprecated="Prefer v + value") +Vector3AddValue :: proc "c" (v: Vector3, value: f32) -> Vector3 { + return v + value +} +// Subtract two vectors (v1 - v2) +@(require_results, deprecated="Prefer a - b") +Vector3Subtract :: proc "c" (a, b: Vector3) -> Vector3 { + return a - b +} +// Subtract vector by float value +@(require_results, deprecated="Prefer v + value") +Vector3SubtractValue :: proc "c" (v: Vector3, value: f32) -> Vector3 { + return v - value +} +// Calculate vector length +@(require_results) +Vector3Length :: proc "c" (v: Vector3) -> f32 { + return linalg.length(v) +} +// Calculate vector square length +@(require_results) +Vector3LengthSqr :: proc "c" (v: Vector3) -> f32 { + return linalg.length2(v) +} +// Calculate two vectors dot product +@(require_results) +Vector3DotProduct :: proc "c" (v1, v2: Vector3) -> f32 { + return linalg.dot(v1, v2) +} +// Calculate two vectors dot product +@(require_results) +Vector3CrossProduct :: proc "c" (v1, v2: Vector3) -> Vector3 { + return linalg.cross(v1, v2) +} +// Calculate distance between two vectors +@(require_results) +Vector3Distance :: proc "c" (v1, v2: Vector3) -> f32 { + return linalg.distance(v1, v2) +} +// Calculate square distance between two vectors +@(require_results) +Vector3DistanceSqrt :: proc "c" (v1, v2: Vector3) -> f32 { + return linalg.length2(v2-v1) +} +// Calculate angle between two vectors +// NOTE: Angle is calculated from origin point (0, 0) +@(require_results) +Vector3Angle :: proc "c" (v1, v2: Vector3) -> f32 { + return linalg.angle_between(v1, v2) +} + +// Calculate angle defined by a two vectors line +// NOTE: Parameters need to be normalized +// Current implementation should be aligned with glm::angle +@(require_results) +Vector3LineAngle :: proc "c" (start, end: Vector3) -> f32 { + // TODO(10/9/2023): Currently angles move clockwise, determine if this is wanted behavior + return -math.atan2(end.y - start.y, end.x - start.x) +} + +// Scale vector (multiply by value) +@(require_results, deprecated="Prefer v * scale") +Vector3Scale :: proc "c" (v: Vector3, scale: f32) -> Vector3 { + return v * scale +} +// Multiply vector by vector +@(require_results, deprecated="Prefer v1 * v2") +Vector3Multiply :: proc "c" (v1, v2: Vector3) -> Vector3 { + return v1 * v2 +} +// Negate vector +@(require_results, deprecated="Prefer -v") +Vector3Negate :: proc "c" (v: Vector3) -> Vector3 { + return -v +} +// Divide vector by vector +@(require_results, deprecated="Prefer v1 / v2") +Vector3Divide :: proc "c" (v1, v2: Vector3) -> Vector3 { + return v1 / v2 +} +// Normalize provided vector +@(require_results) +Vector3Normalize :: proc "c" (v: Vector3) -> Vector3 { + return linalg.normalize0(v) +} + +// Calculate the projection of the vector v1 on to v2 +@(require_results) +Vector3Project :: proc "c" (v1, v2: Vector3) -> Vector3 { + return linalg.projection(v1, v2) +} + +// Calculate the rejection of the vector v1 on to v2 +@(require_results) +Vector3Reject :: proc "c" (v1, v2: Vector3) -> Vector3 { + mag := linalg.dot(v1, v2)/linalg.dot(v2, v2) + return v1 - v2*mag +} + +// Orthonormalize provided vectors +// Makes vectors normalized and orthogonal to each other +// Gram-Schmidt function implementation +Vector3OrthoNormalize :: proc "c" (v1, v2: ^Vector3) { + v1^ = linalg.normalize0(v1^) + v3 := linalg.normalize0(linalg.cross(v1^, v2^)) + v2^ = linalg.cross(v3, v1^) +} + +// Transform a vector by quaternion rotation +@(require_results) +Vector3RotateByQuaternion :: proc "c" (v: Vector3, q: Quaternion) -> Vector3 { + return linalg.mul(q, v) +} + +// Rotates a vector around an axis +@(require_results) +Vector3RotateByAxisAngle :: proc "c" (v: Vector3, axis: Vector3, angle: f32) -> Vector3 { + axis, angle := axis, angle + + axis = linalg.normalize0(axis) + + angle *= 0.5 + a := math.sin(angle) + b := axis.x*a + c := axis.y*a + d := axis.z*a + a = math.cos(angle) + w := Vector3{b, c, d} + + wv := linalg.cross(w, v) + wwv := linalg.cross(w, wv) + + a *= 2 + wv *= a + + wwv *= 2 + + return v + wv + wwv + +} + +// Transforms a Vector3 by a given Matrix +@(require_results) +Vector3Transform :: proc "c" (v: Vector3, m: Matrix) -> Vector3 { + v4 := Vector4{v.x, v.y, v.z, 1} + return (m * v4).xyz +} +// Calculate linear interpolation between two vectors +@(require_results, deprecated="Prefer = linalg.lerp(v1, v2, amount)") +Vector3Lerp :: proc "c" (v1, v2: Vector3, amount: f32) -> Vector3 { + return linalg.lerp(v1, v2, Vector3(amount)) +} +// Calculate reflected vector to normal +@(require_results, deprecated="Prefer = linalg.reflect(v, normal)") +Vector3Reflect :: proc "c" (v, normal: Vector3) -> Vector3 { + return linalg.reflect(v, normal) +} +// Compute the direction of a refracted ray +// v: normalized direction of the incoming ray +// n: normalized normal vector of the interface of two optical media +// r: ratio of the refractive index of the medium from where the ray comes +// to the refractive index of the medium on the other side of the surface +@(require_results, deprecated="Prefer = linalg.refract(v, n, r)") +Vector3Refract :: proc "c" (v, n: Vector3, r: f32) -> Vector3 { + return linalg.refract(v, n, r) +} + +// Move Vector towards target +@(require_results) +Vector3MoveTowards :: proc "c" (v, target: Vector3, maxDistance: f32) -> Vector3 { + dv := target - v + value := linalg.dot(dv, dv) + + if value == 0 || (maxDistance >= 0 && value <= maxDistance*maxDistance) { + return target + } + + dist := math.sqrt(value) + return v + dv/dist*maxDistance +} + +// Invert the given vector +@(require_results, deprecated="Prefer 1.0/v") +Vector3Invert :: proc "c" (v: Vector3) -> Vector3 { + return 1.0/v +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +@(require_results) +Vector3Clamp :: proc "c" (v: Vector3, min, max: Vector3) -> Vector3 { + return Vector3{ + clamp(v.x, min.x, max.x), + clamp(v.y, min.y, max.y), + clamp(v.z, min.z, max.z), + } +} + +// Clamp the magnitude of the vector between two min and max values +@(require_results) +Vector3ClampValue :: proc "c" (v: Vector3, min, max: f32) -> Vector3 { + result := v + + length := linalg.dot(v, v) + if length > 0 { + length = math.sqrt(length) + scale := f32(1) + if length < min { + scale = min/length + } else if length > max { + scale = max/length + } + result = v*scale + } + return result +} + +@(require_results) +Vector3Equals :: proc "c" (p, q: Vector3) -> bool { + return FloatEquals(p.x, q.x) && + FloatEquals(p.y, q.y) && + FloatEquals(p.z, q.z) +} + + +@(require_results) +Vector3Min :: proc "c" (v1, v2: Vector3) -> Vector3 { + return linalg.min(v1, v2) +} + +@(require_results) +Vector3Max :: proc "c" (v1, v2: Vector3) -> Vector3 { + return linalg.max(v1, v2) +} + + +// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) +// NOTE: Assumes P is on the plane of the triangle +@(require_results) +Vector3Barycenter :: proc "c" (p: Vector3, a, b, c: Vector3) -> (result: Vector3) { + v0 := b - a + v1 := c - a + v2 := p - a + d00 := linalg.dot(v0, v0) + d01 := linalg.dot(v0, v1) + d11 := linalg.dot(v1, v1) + d20 := linalg.dot(v2, v0) + d21 := linalg.dot(v2, v1) + + denom := d00*d11 - d01*d01 + + result.y = (d11*d20 - d01*d21)/denom + result.z = (d00*d21 - d01*d20)/denom + result.x = 1 - (result.z + result.y) + + return result +} + + +// Projects a Vector3 from screen space into object space +@(require_results) +Vector3Unproject :: proc "c" (source: Vector3, projection: Matrix, view: Matrix) -> Vector3 { + matViewProj := view * projection + + matViewProjInv := linalg.inverse(matViewProj) + + quat: Quaternion + quat.x = source.x + quat.y = source.y + quat.z = source.z + quat.w = 1 + + qtransformed := QuaternionTransform(quat, matViewProjInv) + + return Vector3{qtransformed.x/qtransformed.w, qtransformed.y/qtransformed.w, qtransformed.z/qtransformed.w} +} + + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix math +//---------------------------------------------------------------------------------- + +// Compute matrix determinant +@(require_results) +MatrixDeterminant :: proc "c" (mat: Matrix) -> f32 { + return linalg.determinant(mat) +} + +// Get the trace of the matrix (sum of the values along the diagonal) +@(require_results) +MatrixTrace :: proc "c" (mat: Matrix) -> f32 { + return linalg.trace(mat) +} + +// Transposes provided matrix +@(require_results) +MatrixTranspose :: proc "c" (mat: Matrix) -> Matrix { + return linalg.transpose(mat) +} + +// Invert provided matrix +@(require_results) +MatrixInvert :: proc "c" (mat: Matrix) -> Matrix { + return linalg.inverse(mat) +} + +// Get identity matrix +@(require_results, deprecated="Prefer Matrix(1)") +MatrixIdentity :: proc "c" () -> Matrix { + return Matrix(1) +} + +// Add two matrices +@(require_results, deprecated="Prefer left + right") +MatrixAdd :: proc "c" (left, right: Matrix) -> Matrix { + return left + right +} + +// Subtract two matrices (left - right) +@(require_results, deprecated="Prefer left - right") +MatrixSubtract :: proc "c" (left, right: Matrix) -> Matrix { + return left - right +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +@(require_results, deprecated="Prefer left * right") +MatrixMultiply :: proc "c" (left, right: Matrix) -> Matrix { + return left * right +} + +// Multiply matrix components by value +@(require_results, deprecated="Prefer left * value") +MatrixMultiplyValue :: proc "c" (left: Matrix, value: f32) -> Matrix { + return left * Matrix(value) +} + +// Get translation matrix +@(require_results) +MatrixTranslate :: proc "c" (x, y, z: f32) -> Matrix { + return { + 1, 0, 0, x, + 0, 1, 0, y, + 0, 0, 1, z, + 0, 0, 0, 1, + } +} + +// Create rotation matrix from axis and angle +// NOTE: Angle should be provided in radians +@(require_results) +MatrixRotate :: proc "c" (axis: Vector3, angle: f32) -> Matrix { + return auto_cast linalg.matrix4_rotate(angle, axis) +} + +// Get x-rotation matrix +// NOTE: Angle must be provided in radians +@(require_results) +MatrixRotateX :: proc "c" (angle: f32) -> Matrix { + return auto_cast linalg.matrix4_rotate(angle, Vector3{1, 0, 0}) +} + +// Get y-rotation matrix +// NOTE: Angle must be provided in radians +@(require_results) +MatrixRotateY :: proc "c" (angle: f32) -> Matrix { + return auto_cast linalg.matrix4_rotate(angle, Vector3{0, 1, 0}) +} + +// Get z-rotation matrix +// NOTE: Angle must be provided in radians +@(require_results) +MatrixRotateZ :: proc "c" (angle: f32) -> Matrix { + return auto_cast linalg.matrix4_rotate(angle, Vector3{0, 0, 1}) +} + +// Get xyz-rotation matrix +// NOTE: Angle must be provided in radians +@(require_results) +MatrixRotateXYZ :: proc "c" (angle: Vector3) -> Matrix { + return auto_cast linalg.matrix4_from_euler_angles_xyz(angle.x, angle.y, angle.z) +} + +// Get zyx-rotation matrix +// NOTE: Angle must be provided in radians +@(require_results) +MatrixRotateZYX :: proc "c" (angle: Vector3) -> Matrix { + return auto_cast linalg.matrix4_from_euler_angles_zyx(angle.x, angle.y, angle.z) +} + + +// Get scaling matrix +@(require_results) +MatrixScale :: proc "c" (x, y, z: f32) -> Matrix { + return auto_cast linalg.matrix4_scale(Vector3{x, y, z}) +} + +// Get orthographic projection matrix +@(require_results) +MatrixOrtho :: proc "c" (left, right, bottom, top, near, far: f32) -> Matrix { + return auto_cast linalg.matrix_ortho3d(left, right, bottom, top, near, far) +} + +// Get perspective projection matrix +// NOTE: Fovy angle must be provided in radians +@(require_results) +MatrixPerspective :: proc "c" (fovY, aspect, nearPlane, farPlane: f32) -> Matrix { + return auto_cast linalg.matrix4_perspective(fovY, aspect, nearPlane, farPlane) +} +// Get camera look-at matrix (view matrix) +@(require_results) +MatrixLookAt :: proc "c" (eye, target, up: Vector3) -> Matrix { + return auto_cast linalg.matrix4_look_at(eye, target, up) +} + +// Get float array of matrix data +@(require_results) +MatrixToFloatV :: proc "c" (mat: Matrix) -> [16]f32 { + return transmute([16]f32)linalg.transpose(mat) +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Quaternion math +//---------------------------------------------------------------------------------- + + + +// Add two quaternions +@(require_results, deprecated="Prefer q1 + q2") +QuaternionAdd :: proc "c" (q1, q2: Quaternion) -> Quaternion { + return q1 + q2 +} +// Add quaternion and float value +@(require_results) +QuaternionAddValue :: proc "c" (q: Quaternion, add: f32) -> Quaternion { + return q + Quaternion(add) +} +// Subtract two quaternions +@(require_results, deprecated="Prefer q1 - q2") +QuaternionSubtract :: proc "c" (q1, q2: Quaternion) -> Quaternion { + return q1 - q2 +} +// Subtract quaternion and float value +@(require_results) +QuaternionSubtractValue :: proc "c" (q: Quaternion, sub: f32) -> Quaternion { + return q - Quaternion(sub) +} +// Get identity quaternion +@(require_results, deprecated="Prefer Quaternion(1)") +QuaternionIdentity :: proc "c" () -> Quaternion { + return 1 +} +// Computes the length of a quaternion +@(require_results, deprecated="Prefer abs(q)") +QuaternionLength :: proc "c" (q: Quaternion) -> f32 { + return abs(q) +} +// Normalize provided quaternion +@(require_results) +QuaternionNormalize :: proc "c" (q: Quaternion) -> Quaternion { + return linalg.normalize0(q) +} +// Invert provided quaternion +@(require_results, deprecated="Prefer 1/q") +QuaternionInvert :: proc "c" (q: Quaternion) -> Quaternion { + return 1/q +} +// Calculate two quaternion multiplication +@(require_results, deprecated="Prefer q1 * q2") +QuaternionMultiply :: proc "c" (q1, q2: Quaternion) -> Quaternion { + return q1 * q2 +} +// Scale quaternion by float value +@(require_results) +QuaternionScale :: proc "c" (q: Quaternion, mul: f32) -> Quaternion { + return q * Quaternion(mul) +} +// Divide two quaternions +@(require_results, deprecated="Prefer q1 / q2") +QuaternionDivide :: proc "c" (q1, q2: Quaternion) -> Quaternion { + return q1 / q2 +} +// Calculate linear interpolation between two quaternions +@(require_results) +QuaternionLerp :: proc "c" (q1, q2: Quaternion, amount: f32) -> (q3: Quaternion) { + q3.x = q1.x + (q2.x-q1.x)*amount + q3.y = q1.y + (q2.y-q1.y)*amount + q3.z = q1.z + (q2.z-q1.z)*amount + q3.w = q1.w + (q2.w-q1.w)*amount + return +} +// Calculate slerp-optimized interpolation between two quaternions +@(require_results) +QuaternionNlerp :: proc "c" (q1, q2: Quaternion, amount: f32) -> Quaternion { + return linalg.quaternion_nlerp(q1, q2, amount) +} +// Calculates spherical linear interpolation between two quaternions +@(require_results) +QuaternionSlerp :: proc "c" (q1, q2: Quaternion, amount: f32) -> Quaternion { + return linalg.quaternion_slerp(q1, q2, amount) +} +// Calculate quaternion based on the rotation from one vector to another +@(require_results) +QuaternionFromVector3ToVector3 :: proc "c" (from, to: Vector3) -> Quaternion { + return linalg.quaternion_between_two_vector3(from, to) +} +// Get a quaternion for a given rotation matrix +@(require_results) +QuaternionFromMatrix :: proc "c" (mat: Matrix) -> Quaternion { + return linalg.quaternion_from_matrix4(linalg.Matrix4f32(mat)) +} +// Get a matrix for a given quaternion +@(require_results) +QuaternionToMatrix :: proc "c" (q: Quaternion) -> Matrix { + return auto_cast linalg.matrix4_from_quaternion(q) +} +// Get rotation quaternion for an angle and axis NOTE: Angle must be provided in radians +@(require_results) +QuaternionFromAxisAngle :: proc "c" (axis: Vector3, angle: f32) -> Quaternion { + return linalg.quaternion_angle_axis(angle, axis) +} +// Get the rotation angle and axis for a given quaternion +@(require_results) +QuaternionToAxisAngle :: proc "c" (q: Quaternion) -> (outAxis: Vector3, outAngle: f32) { + outAngle, outAxis = linalg.angle_axis_from_quaternion(q) + return +} +// Get the quaternion equivalent to Euler angles NOTE: Rotation order is ZYX +@(require_results) +QuaternionFromEuler :: proc "c" (pitch, yaw, roll: f32) -> Quaternion { + return linalg.quaternion_from_pitch_yaw_roll(pitch, yaw, roll) +} +// Get the Euler angles equivalent to quaternion (roll, pitch, yaw) NOTE: Angles are returned in a Vector3 struct in radians +@(require_results) +QuaternionToEuler :: proc "c" (q: Quaternion) -> Vector3 { + result: Vector3 + + // Roll (x-axis rotation) + x0 := 2.0*(q.w*q.x + q.y*q.z) + x1 := 1.0 - 2.0*(q.x*q.x + q.y*q.y) + result.x = math.atan2(x0, x1) + + // Pitch (y-axis rotation) + y0 := 2.0*(q.w*q.y - q.z*q.x) + y0 = 1.0 if y0 > 1.0 else y0 + y0 = -1.0 if y0 < -1.0 else y0 + result.y = math.asin(y0) + + // Yaw (z-axis rotation) + z0 := 2.0*(q.w*q.z + q.x*q.y) + z1 := 1.0 - 2.0*(q.y*q.y + q.z*q.z) + result.z = math.atan2(z0, z1) + + return result +} +// Transform a quaternion given a transformation matrix +@(require_results) +QuaternionTransform :: proc "c" (q: Quaternion, mat: Matrix) -> Quaternion { + v := mat * transmute(Vector4)q + return transmute(Quaternion)v +} +// Check whether two given quaternions are almost equal +@(require_results) +QuaternionEquals :: proc "c" (p, q: Quaternion) -> bool { + return FloatEquals(p.x, q.x) && + FloatEquals(p.y, q.y) && + FloatEquals(p.z, q.z) && + FloatEquals(p.w, q.w) +} + +@(require_results) +MatrixCompose :: proc "c" (translation: Vector3, rotation: Quaternion, scale: Vector3) -> Matrix { + // Initialize and scale vectors + right := Vector3 {1, 0, 0} * scale.x + up := Vector3 {0, 1, 0} * scale.y + forward := Vector3 {0, 0, 1} * scale.z + + // Rotate vectors + right = Vector3RotateByQuaternion(right, rotation) + up = Vector3RotateByQuaternion(up, rotation) + forward = Vector3RotateByQuaternion(forward, rotation) + + // Set result matrix output + result := Matrix { + right.x, up.x, forward.x, translation.x, + right.y, up.y, forward.y, translation.y, + right.z, up.z, forward.z, translation.z, + 0, 0, 0, 1, + } + + return result +} + +@(private, require_results) +fmaxf :: proc "contextless" (x, y: f32) -> f32 { + if math.is_nan(x) { + return y + } + + if math.is_nan(y) { + return x + } + + if math.sign_bit(x) != math.sign_bit(y) { + return y if math.sign_bit(x) else x + } + + return y if x < y else x +} diff --git a/src/libraries/raylib/rlgl/rlgl.odin b/src/libraries/raylib/rlgl/rlgl.odin new file mode 100644 index 0000000..d1655d3 --- /dev/null +++ b/src/libraries/raylib/rlgl/rlgl.odin @@ -0,0 +1,590 @@ +/********************************************************************************************** +* +* rlgl v6.0 - A multi-OpenGL abstraction layer with an immediate-mode style API +* +* DESCRIPTION: +* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0, ES 3.0) +* that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) +* +* ADDITIONAL NOTES: +* When choosing an OpenGL backend different than OpenGL 1.1, some internal buffers are +* initialized on rlglInit() to accumulate vertex data +* +* When an internal state change is required all the stored vertex data is rendered in a batch, +* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch +* +* Some resources are also loaded for convenience, here the complete list: +* - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data +* - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 +* - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) +* +* Internal buffer (and resources) must be manually unloaded calling rlglClose() +* +* CONFIGURATION: +* #define GRAPHICS_API_OPENGL_SOFTWARE +* #define GRAPHICS_API_OPENGL_11 +* #define GRAPHICS_API_OPENGL_21 +* #define GRAPHICS_API_OPENGL_33 +* #define GRAPHICS_API_OPENGL_43 +* #define GRAPHICS_API_OPENGL_ES2 +* #define GRAPHICS_API_OPENGL_ES3 +* Use selected OpenGL graphics backend, should be supported by platform +* Those preprocessor defines are only used on the rlgl module, if OpenGL version is +* required by any other module, use rlGetVersion() to check it +* +* #define RLGL_IMPLEMENTATION +* Generates the implementation of the library into the included file +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation +* +* #if RLGL_SHOW_GL_DETAILS_INFO +* Show OpenGL extensions and capabilities detailed logs on init +* +* #if RLGL_ENABLE_OPENGL_DEBUG_CONTEXT +* Enable debug context (only available on OpenGL 4.3) +* +* rlgl capabilities could be customized defining some internal +* values before library inclusion (default values listed): +* +* #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch elements limits +* #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +* #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +* #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of texture units that can be activated on batch drawing (SetShaderValueTexture()) +* +* #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack +* #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +* #define RL_CULL_DISTANCE_NEAR 0.05 // Default projection matrix near cull distance +* #define RL_CULL_DISTANCE_FAR 4000.0 // Default projection matrix far cull distance +* +* When loading a shader, the following vertex attributes and uniform +* location names are tried to be set automatically: +* +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEINDICES "vertexBoneIndices" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEINDICES +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView))) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONEMATRICES "boneMatrices" // bone matrices +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) +* +* DEPENDENCIES: +* - OpenGL libraries (depending on platform and OpenGL version selected) +* - GLAD OpenGL extensions loading library (only for OpenGL 3.3 Core, 4.3 Core) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2026 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + + +package rlgl + +import "core:c" +import rl "../." + +VERSION :: "6.0" + +RAYLIB_SHARED :: #config(RAYLIB_SHARED, false) +RAYLIB_WASM_LIB :: #config(RAYLIB_WASM_LIB, "../wasm/libraylib.a") + +// Note: We pull in the full raylib library. If you want a truly stand-alone rlgl, then: +// - Compile a separate rlgl library and use that in the foreign import blocks below. +// - Remove the `import rl "../."` line +// - Copy the code from raylib.odin for any types we alias from that package (see PixelFormat etc) + +when ODIN_OS == .Windows { + @(extra_linker_flags="/NODEFAULTLIB:" + ("msvcrt" when RAYLIB_SHARED else "libcmt")) + foreign import lib { + "../windows/raylibdll.lib" when RAYLIB_SHARED else "../windows/raylib.lib" , + "system:Winmm.lib", + "system:Gdi32.lib", + "system:User32.lib", + "system:Shell32.lib", + } +} else when ODIN_OS == .Linux { + foreign import lib { + // Note(bumbread): I'm not sure why in `linux/` folder there are + // multiple copies of raylib.so, but since these bindings are for + // particular version of the library, I better specify it. Ideally, + // though, it's best specified in terms of major (.so.4) + "../linux/libraylib.so.550" when RAYLIB_SHARED else "../linux/libraylib.a", + "system:dl", + "system:pthread", + } +} else when ODIN_OS == .Darwin { + foreign import lib { + "../macos/libraylib.550.dylib" when RAYLIB_SHARED else "../macos/libraylib.a", + "system:Cocoa.framework", + "system:OpenGL.framework", + "system:IOKit.framework", + } +} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { + foreign import lib { + RAYLIB_WASM_LIB, + } +} else { + foreign import lib "system:raylib" +} + +GRAPHICS_API_OPENGL_11 :: false +GRAPHICS_API_OPENGL_21 :: true +GRAPHICS_API_OPENGL_33 :: GRAPHICS_API_OPENGL_21 // default currently +GRAPHICS_API_OPENGL_ES2 :: false +GRAPHICS_API_OPENGL_43 :: false +GRAPHICS_API_OPENGL_ES3 :: false + +when GRAPHICS_API_OPENGL_ES3 { + GRAPHICS_API_OPENGL_ES2 :: true +} + +when !GRAPHICS_API_OPENGL_ES2 { + // This is the maximum amount of elements (quads) per batch + // NOTE: Be careful with text, every letter maps to a quad + DEFAULT_BATCH_BUFFER_ELEMENTS :: 8192 +} else { + // We reduce memory sizes for embedded systems (RPI and HTML5) + // NOTE: On HTML5 (emscripten) this is allocated on heap, + // by default it's only 16MB!...just take care... + DEFAULT_BATCH_BUFFER_ELEMENTS :: 2048 +} + +DEFAULT_BATCH_BUFFERS :: 1 // Default number of batch buffers (multi-buffering) +DEFAULT_BATCH_DRAWCALLS :: 256 // Default number of batch draw calls (by state changes: mode, texture) +DEFAULT_BATCH_MAX_TEXTURE_UNITS :: 4 // Maximum number of additional textures that can be activated on batch drawing (SetShaderValueTexture()) + +// Internal Matrix stack +MAX_MATRIX_STACK_SIZE :: 32 // Maximum size of Matrix stack + +// Shader limits +MAX_SHADER_LOCATIONS :: 32 // Maximum number of shader locations supported + +// Projection matrix culling +CULL_DISTANCE_NEAR :: 0.01 // Default near cull distance +CULL_DISTANCE_FAR :: 1000.0 // Default far cull distance + +// Texture parameters (equivalent to OpenGL defines) +TEXTURE_WRAP_S :: 0x2802 // GL_TEXTURE_WRAP_S +TEXTURE_WRAP_T :: 0x2803 // GL_TEXTURE_WRAP_T +TEXTURE_MAG_FILTER :: 0x2800 // GL_TEXTURE_MAG_FILTER +TEXTURE_MIN_FILTER :: 0x2801 // GL_TEXTURE_MIN_FILTER + +TEXTURE_FILTER_NEAREST :: 0x2600 // GL_NEAREST +TEXTURE_FILTER_LINEAR :: 0x2601 // GL_LINEAR +TEXTURE_FILTER_MIP_NEAREST :: 0x2700 // GL_NEAREST_MIPMAP_NEAREST +TEXTURE_FILTER_NEAREST_MIP_LINEAR :: 0x2702 // GL_NEAREST_MIPMAP_LINEAR +TEXTURE_FILTER_LINEAR_MIP_NEAREST :: 0x2701 // GL_LINEAR_MIPMAP_NEAREST +TEXTURE_FILTER_MIP_LINEAR :: 0x2703 // GL_LINEAR_MIPMAP_LINEAR +TEXTURE_FILTER_ANISOTROPIC :: 0x3000 // Anisotropic filter (custom identifier) + +TEXTURE_WRAP_REPEAT :: 0x2901 // GL_REPEAT +TEXTURE_WRAP_CLAMP :: 0x812F // GL_CLAMP_TO_EDGE +TEXTURE_WRAP_MIRROR_REPEAT :: 0x8370 // GL_MIRRORED_REPEAT +TEXTURE_WRAP_MIRROR_CLAMP :: 0x8742 // GL_MIRROR_CLAMP_EXT + +// Matrix modes (equivalent to OpenGL) +MODELVIEW :: 0x1700 // GL_MODELVIEW +PROJECTION :: 0x1701 // GL_PROJECTION +TEXTURE :: 0x1702 // GL_TEXTURE + +// Primitive assembly draw modes +LINES :: 0x0001 // GL_LINES +TRIANGLES :: 0x0004 // GL_TRIANGLES +QUADS :: 0x0007 // GL_QUADS + +// GL equivalent data types +UNSIGNED_BYTE :: 0x1401 // GL_UNSIGNED_BYTE +FLOAT :: 0x1406 // GL_FLOAT + +// Buffer usage hint +STREAM_DRAW :: 0x88E0 // GL_STREAM_DRAW +STREAM_READ :: 0x88E1 // GL_STREAM_READ +STREAM_COPY :: 0x88E2 // GL_STREAM_COPY +STATIC_DRAW :: 0x88E4 // GL_STATIC_DRAW +STATIC_READ :: 0x88E5 // GL_STATIC_READ +STATIC_COPY :: 0x88E6 // GL_STATIC_COPY +DYNAMIC_DRAW :: 0x88E8 // GL_DYNAMIC_DRAW +DYNAMIC_READ :: 0x88E9 // GL_DYNAMIC_READ +DYNAMIC_COPY :: 0x88EA // GL_DYNAMIC_COPY + +// GL Shader type +FRAGMENT_SHADER :: 0x8B30 // GL_FRAGMENT_SHADER +VERTEX_SHADER :: 0x8B31 // GL_VERTEX_SHADER +COMPUTE_SHADER :: 0x91B9 // GL_COMPUTE_SHADER + +// GL blending factors +ZERO :: 0 // GL_ZERO +ONE :: 1 // GL_ONE +SRC_COLOR :: 0x0300 // GL_SRC_COLOR +ONE_MINUS_SRC_COLOR :: 0x0301 // GL_ONE_MINUS_SRC_COLOR +SRC_ALPHA :: 0x0302 // GL_SRC_ALPHA +ONE_MINUS_SRC_ALPHA :: 0x0303 // GL_ONE_MINUS_SRC_ALPHA +DST_ALPHA :: 0x0304 // GL_DST_ALPHA +ONE_MINUS_DST_ALPHA :: 0x0305 // GL_ONE_MINUS_DST_ALPHA +DST_COLOR :: 0x0306 // GL_DST_COLOR +ONE_MINUS_DST_COLOR :: 0x0307 // GL_ONE_MINUS_DST_COLOR +SRC_ALPHA_SATURATE :: 0x0308 // GL_SRC_ALPHA_SATURATE +CONSTANT_COLOR :: 0x8001 // GL_CONSTANT_COLOR +ONE_MINUS_CONSTANT_COLOR :: 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR +CONSTANT_ALPHA :: 0x8003 // GL_CONSTANT_ALPHA +ONE_MINUS_CONSTANT_ALPHA :: 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA + +// GL blending functions/equations +FUNC_ADD :: 0x8006 // GL_FUNC_ADD +MIN :: 0x8007 // GL_MIN +MAX :: 0x8008 // GL_MAX +FUNC_SUBTRACT :: 0x800A // GL_FUNC_SUBTRACT +FUNC_REVERSE_SUBTRACT :: 0x800B // GL_FUNC_REVERSE_SUBTRACT +BLEND_EQUATION :: 0x8009 // GL_BLEND_EQUATION +BLEND_EQUATION_RGB :: 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) +BLEND_EQUATION_ALPHA :: 0x883D // GL_BLEND_EQUATION_ALPHA +BLEND_DST_RGB :: 0x80C8 // GL_BLEND_DST_RGB +BLEND_SRC_RGB :: 0x80C9 // GL_BLEND_SRC_RGB +BLEND_DST_ALPHA :: 0x80CA // GL_BLEND_DST_ALPHA +BLEND_SRC_ALPHA :: 0x80CB // GL_BLEND_SRC_ALPHA +BLEND_COLOR :: 0x8005 // GL_BLEND_COLOR + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- + + +VertexBufferIndexType :: c.ushort when GRAPHICS_API_OPENGL_ES2 else c.uint + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +VertexBuffer :: struct { + elementCount: c.int, // Number of elements in the buffer (QUADS) + + vertices: [^]f32, // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + texcoords: [^]f32, // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + colors: [^]u8, // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + indices: [^]VertexBufferIndexType, // Vertex indices (in case vertex data comes indexed) (6 indices per quad) + vaoId: c.uint, // OpenGL Vertex Array Object id + vboId: [4]c.uint, // OpenGL Vertex Buffer Objects id (4 types of vertex data) +} + +// Draw call type +// NOTE: Only texture changes register a new draw, other state-change-related elements are not +// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any +// of those state-change happens (this is done in core module) +DrawCall :: struct { + mode: c.int, // Drawing mode: LINES, TRIANGLES, QUADS + vertexCount: c.int, // Number of vertex of the draw + vertexAlignment: c.int, // Number of vertex required for index alignment (LINES, TRIANGLES) + textureId: c.uint, // Texture id to be used on the draw -> Use to create new draw call if changes +} + +// RenderBatch type +RenderBatch :: struct { + bufferCount: c.int, // Number of vertex buffers (multi-buffering support) + currentBuffer: c.int, // Current buffer tracking in case of multi-buffering + vertexBuffer: [^]VertexBuffer, // Dynamic buffer(s) for vertex data + + draws: [^]DrawCall, // Draw calls array, depends on textureId + drawCounter: c.int, // Draw calls counter + currentDepth: f32, // Current depth value for next draw +} + +// OpenGL version +GlVersion :: enum c.int { + OPENGL_11 = 1, // OpenGL 1.1 + OPENGL_21, // OpenGL 2.1 (GLSL 120) + OPENGL_33, // OpenGL 3.3 (GLSL 330) + OPENGL_43, // OpenGL 4.3 (using GLSL 330) + OPENGL_ES_20, // OpenGL ES 2.0 (GLSL 100) + OPENGL_ES_30, // OpenGL ES 3.0 (GLSL 300 es) +} + +PixelFormat :: rl.PixelFormat +TextureFilter :: rl.TextureFilter +BlendMode :: rl.BlendMode +ShaderLocationIndex :: rl.ShaderLocationIndex +ShaderUniformDataType :: rl.ShaderUniformDataType + +// Shader attribute data types +ShaderAttributeDataType :: enum c.int { + FLOAT = 0, // Shader attribute type: float + VEC2, // Shader attribute type: vec2 (2 float) + VEC3, // Shader attribute type: vec3 (3 float) + VEC4, // Shader attribute type: vec4 (4 float) +} + +// Framebuffer attachment type +// NOTE: By default up to 8 color channels defined, but it can be more +FramebufferAttachType :: enum c.int { + COLOR_CHANNEL0 = 0, // Framebuffer attachment type: color 0 + COLOR_CHANNEL1 = 1, // Framebuffer attachment type: color 1 + COLOR_CHANNEL2 = 2, // Framebuffer attachment type: color 2 + COLOR_CHANNEL3 = 3, // Framebuffer attachment type: color 3 + COLOR_CHANNEL4 = 4, // Framebuffer attachment type: color 4 + COLOR_CHANNEL5 = 5, // Framebuffer attachment type: color 5 + COLOR_CHANNEL6 = 6, // Framebuffer attachment type: color 6 + COLOR_CHANNEL7 = 7, // Framebuffer attachment type: color 7 + DEPTH = 100, // Framebuffer attachment type: depth + STENCIL = 200, // Framebuffer attachment type: stencil +} + +// Framebuffer texture attachment type +FramebufferAttachTextureType :: enum c.int { + CUBEMAP_POSITIVE_X = 0, // Framebuffer texture attachment type: cubemap, +X side + CUBEMAP_NEGATIVE_X = 1, // Framebuffer texture attachment type: cubemap, -X side + CUBEMAP_POSITIVE_Y = 2, // Framebuffer texture attachment type: cubemap, +Y side + CUBEMAP_NEGATIVE_Y = 3, // Framebuffer texture attachment type: cubemap, -Y side + CUBEMAP_POSITIVE_Z = 4, // Framebuffer texture attachment type: cubemap, +Z side + CUBEMAP_NEGATIVE_Z = 5, // Framebuffer texture attachment type: cubemap, -Z side + TEXTURE2D = 100, // Framebuffer texture attachment type: texture2d + RENDERBUFFER = 200, // Framebuffer texture attachment type: renderbuffer +} + +CullMode :: enum c.int { + FRONT = 0, + BACK, +} + +Matrix :: rl.Matrix + +@(default_calling_convention="c", link_prefix="rl") +foreign lib { + //------------------------------------------------------------------------------------ + // Functions Declaration - Matrix operations + //------------------------------------------------------------------------------------ + MatrixMode :: proc(mode: c.int) --- // Choose the current matrix to be transformed + PushMatrix :: proc() --- // Push the current matrix to stack + PopMatrix :: proc() --- // Pop lattest inserted matrix from stack + LoadIdentity :: proc() --- // Reset current matrix to identity matrix + Translatef :: proc(x, y, z: f32) --- // Multiply the current matrix by a translation matrix + Rotatef :: proc(angleDeg: f32, x, y, z: f32) --- // Multiply the current matrix by a rotation matrix + Scalef :: proc(x, y, z: f32) --- // Multiply the current matrix by a scaling matrix + MultMatrixf :: proc(matf: [^]f32) --- // Multiply the current matrix by another matrix + Frustum :: proc(left, right, bottom, top, znear, zfar: f64) --- + Ortho :: proc(left, right, bottom, top, znear, zfar: f64) --- + Viewport :: proc(x, y, width, height: c.int) --- // Set the viewport area + SetClipPlanes :: proc(near, far: f64) --- // Set clip planes distances + GetCullDistanceNear :: proc() -> f64 --- // Get cull plane distance near + GetCullDistanceFar :: proc() -> f64 --- // Get cull plane distance far + + //------------------------------------------------------------------------------------ + // Functions Declaration - Vertex level operations + //------------------------------------------------------------------------------------ + Begin :: proc(mode: c.int) --- // Initialize drawing mode (how to organize vertex) + End :: proc() --- // Finish vertex providing + Vertex2i :: proc(x, y: c.int) --- // Define one vertex (position) - 2 int + Vertex2f :: proc(x, y: f32) --- // Define one vertex (position) - 2 f32 + Vertex3f :: proc(x, y, z: f32) --- // Define one vertex (position) - 3 f32 + TexCoord2f :: proc(x, y: f32) --- // Define one vertex (texture coordinate) - 2 f32 + Normal3f :: proc(x, y, z: f32) --- // Define one vertex (normal) - 3 f32 + Color4ub :: proc(r, g, b, a: u8) --- // Define one vertex (color) - 4 byte + Color3f :: proc(x, y, z: f32) --- // Define one vertex (color) - 3 f32 + Color4f :: proc(x, y, z, w: f32) --- // Define one vertex (color) - 4 f32 + + //------------------------------------------------------------------------------------ + // Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) + // NOTE: This functions are used to completely abstract raylib code from OpenGL layer, + // some of them are direct wrappers over OpenGL calls, some others are custom + //------------------------------------------------------------------------------------ + + // Vertex buffers state + EnableVertexArray :: proc(vaoId: c.uint) -> bool --- // Enable vertex array (VAO, if supported) + DisableVertexArray :: proc() --- // Disable vertex array (VAO, if supported) + EnableVertexBuffer :: proc(id: c.uint) --- // Enable vertex buffer (VBO) + DisableVertexBuffer :: proc() --- // Disable vertex buffer (VBO) + EnableVertexBufferElement :: proc(id: c.uint) --- // Enable vertex buffer element (VBO element) + DisableVertexBufferElement :: proc() --- // Disable vertex buffer element (VBO element) + EnableVertexAttribute :: proc(index: c.uint) --- // Enable vertex attribute index + DisableVertexAttribute :: proc(index: c.uint) --- // Disable vertex attribute index + when GRAPHICS_API_OPENGL_11 { + EnableStatePointer :: proc(vertexAttribType: c.int, buffer: rawptr) --- + DisableStatePointer :: proc(vertexAttribType: c.int) --- + } + + // Textures state + ActiveTextureSlot :: proc(slot: c.int) --- // Select and active a texture slot + EnableTexture :: proc(id: c.uint) --- // Enable texture + DisableTexture :: proc() --- // Disable texture + EnableTextureCubemap :: proc(id: c.uint) --- // Enable texture cubemap + DisableTextureCubemap :: proc() --- // Disable texture cubemap + TextureParameters :: proc(id: c.uint, param: c.int, value: c.int) --- // Set texture parameters (filter, wrap) + CubemapParameters :: proc(id: i32, param: c.int, value: c.int) --- // Set cubemap parameters (filter, wrap) + + // Shader state + EnableShader :: proc(id: c.uint) --- // Enable shader program + DisableShader :: proc() --- // Disable shader program + + // Framebuffer state + EnableFramebuffer :: proc(id: c.uint) --- // Enable render texture (fbo) + DisableFramebuffer :: proc() --- // Disable render texture (fbo), return to default framebuffer + ActiveDrawBuffers :: proc(count: c.int) --- // Activate multiple draw color buffers + BlitFramebuffer :: proc(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask: c.int) --- // Blit active framebuffer to main framebuffer + + // General render state + EnableColorBlend :: proc() --- // Enable color blending + DisableColorBlend :: proc() --- // Disable color blending + EnableDepthTest :: proc() --- // Enable depth test + DisableDepthTest :: proc() --- // Disable depth test + EnableDepthMask :: proc() --- // Enable depth write + DisableDepthMask :: proc() --- // Disable depth write + EnableBackfaceCulling :: proc() --- // Enable backface culling + DisableBackfaceCulling :: proc() --- // Disable backface culling + SetCullFace :: proc(mode: CullMode) --- // Set face culling mode + EnableScissorTest :: proc() --- // Enable scissor test + DisableScissorTest :: proc() --- // Disable scissor test + Scissor :: proc(x, y, width, height: c.int) --- // Scissor test + EnableWireMode :: proc() --- // Enable wire mode + EnablePointMode :: proc() --- // Enable point mode + DisableWireMode :: proc() --- // Disable wire and point modes + SetLineWidth :: proc(width: f32) --- // Set the line drawing width + GetLineWidth :: proc() -> f32 --- // Get the line drawing width + EnableSmoothLines :: proc() --- // Enable line aliasing + DisableSmoothLines :: proc() --- // Disable line aliasing + EnableStereoRender :: proc() --- // Enable stereo rendering + DisableStereoRender :: proc() --- // Disable stereo rendering + IsStereoRenderEnabled :: proc() -> bool --- // Check if stereo render is enabled + + + ClearColor :: proc(r, g, b, a: u8) --- // Clear color buffer with color + ClearScreenBuffers :: proc() --- // Clear used screen buffers (color and depth) + CheckErrors :: proc() --- // Check and log OpenGL error codes + SetBlendMode :: proc(mode: c.int) --- // Set blending mode + SetBlendFactors :: proc(glSrcFactor, glDstFactor, glEquation: c.int) --- // Set blending mode factor and equation (using OpenGL factors) + SetBlendFactorsSeparate :: proc(glSrcRGB, glDstRGB, glSrcAlpha, glDstAlpha, glEqRGB, glEqAlpha: c.int) --- // Set blending mode factors and equations separately (using OpenGL factors) + + //------------------------------------------------------------------------------------ + // Functions Declaration - rlgl functionality + //------------------------------------------------------------------------------------ + // rlgl initialization functions + @(link_prefix="rlgl") + Init :: proc(width, height: c.int) --- // Initialize rlgl (buffers, shaders, textures, states) + @(link_prefix="rlgl") + Close :: proc() --- // De-initialize rlgl (buffers, shaders, textures) + LoadExtensions :: proc(loader: rawptr) --- // Load OpenGL extensions (loader function required) + GetProcAddress :: proc(procName: cstring) -> rawptr --- // Get OpenGL procedure address + GetVersion :: proc() -> GlVersion --- // Get current OpenGL version + SetFramebufferWidth :: proc(width: c.int) --- // Set current framebuffer width + GetFramebufferWidth :: proc() -> c.int --- // Get default framebuffer width + SetFramebufferHeight :: proc(height: c.int) --- // Set current framebuffer height + GetFramebufferHeight :: proc() -> c.int --- // Get default framebuffer height + + + GetTextureIdDefault :: proc() -> c.uint --- // Get default texture id + GetShaderIdDefault :: proc() -> c.uint --- // Get default shader id + GetShaderLocsDefault :: proc() -> [^]c.int --- // Get default shader locations + + // Render batch management + // NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode + // but this render batch API is exposed in case of custom batches are required + LoadRenderBatch :: proc(numBuffers, bufferElements: c.int) -> RenderBatch --- // Load a render batch system + UnloadRenderBatch :: proc(batch: RenderBatch) --- // Unload render batch system + DrawRenderBatch :: proc(batch: ^RenderBatch) --- // Draw render batch data (Update->Draw->Reset) + SetRenderBatchActive :: proc(batch: ^RenderBatch) --- // Set the active render batch for rlgl (NULL for default internal) + DrawRenderBatchActive :: proc() --- // Update and draw internal render batch + CheckRenderBatchLimit :: proc(vCount: c.int) -> c.int --- // Check internal buffer overflow for a given number of vertex + + SetTexture :: proc(id: c.uint) --- // Set current texture for render batch and check buffers limits + + //------------------------------------------------------------------------------------------------------------------------ + + // Vertex buffers management + LoadVertexArray :: proc() -> c.uint --- // Load vertex array (vao) if supported + LoadVertexBuffer :: proc(buffer: rawptr, size: c.int, is_dynamic: bool) -> c.uint --- // Load a vertex buffer attribute + LoadVertexBufferElement :: proc(buffer: rawptr, size: c.int, is_dynamic: bool) -> c.uint --- // Load a new attributes element buffer + UpdateVertexBuffer :: proc(bufferId: c.uint, data: rawptr, dataSize: c.int, offset: c.int) --- // Update GPU buffer with new data + UpdateVertexBufferElements :: proc(id: c.uint, data: rawptr, dataSize: c.int, offset: c.int) --- // Update vertex buffer elements with new data + UnloadVertexArray :: proc(vaoId: c.uint) --- + UnloadVertexBuffer :: proc(vboId: c.uint) --- + SetVertexAttribute :: proc(index: c.uint, compSize: c.int, type: c.int, normalized: bool, stride: c.int, offset: c.int) --- + SetVertexAttributeDivisor :: proc(index: c.uint, divisor: c.int) --- + SetVertexAttributeDefault :: proc(locIndex: c.int, value: rawptr, attribType: c.int, count: c.int) --- // Set vertex attribute default value + DrawVertexArray :: proc(offset: c.int, count: c.int) --- + DrawVertexArrayElements :: proc(offset: c.int, count: c.int, buffer: rawptr) --- + DrawVertexArrayInstanced :: proc(offset: c.int, count: c.int, instances: c.int) --- + DrawVertexArrayElementsInstanced :: proc(offset: c.int, count: c.int, buffer: rawptr, instances: c.int) --- + + // Textures management + LoadTexture :: proc(data: rawptr, width, height: c.int, format: c.int, mipmapCount: c.int) -> c.uint --- // Load texture in GPU + LoadTextureDepth :: proc(width, height: c.int, useRenderBuffer: bool) -> c.uint --- // Load depth texture/renderbuffer (to be attached to fbo) + LoadTextureCubemap :: proc(data: rawptr, size: c.int, format: c.int) -> c.uint --- // Load texture cubemap + UpdateTexture :: proc(id: c.uint, offsetX, offsetY: c.int, width, height: c.int, format: c.int, data: rawptr) --- // Update GPU texture with new data + GetGlTextureFormats :: proc(format: c.int, glInternalFormat, glFormat, glType: ^c.uint) --- // Get OpenGL internal formats + GetPixelFormatName :: proc(format: c.uint) -> cstring --- // Get name string for pixel format + UnloadTexture :: proc(id: c.uint) --- // Unload texture from GPU memory + GenTextureMipmaps :: proc(id: c.uint, width, height: c.int, format: c.int, mipmaps: ^c.int) --- // Generate mipmap data for selected texture + ReadTexturePixels :: proc(id: c.uint, width, height: c.int, format: c.int) -> rawptr --- // Read texture pixel data + ReadScreenPixels :: proc(width, height: c.int) -> [^]byte --- // Read screen pixel data (color buffer) + + // Framebuffer management (fbo) + LoadFramebuffer :: proc() -> c.uint --- // Load an empty framebuffer + FramebufferAttach :: proc(fboId, texId: c.uint, attachType: c.int, texType: c.int, mipLevel: c.int) --- // Attach texture/renderbuffer to a framebuffer + FramebufferComplete :: proc(id: c.uint) -> bool --- // Verify framebuffer is complete + UnloadFramebuffer :: proc(id: c.uint) --- // Delete framebuffer from GPU + + // Shaders management + LoadShaderCode :: proc(vsCode, fsCode: cstring) -> c.uint --- // Load shader from code strings + CompileShader :: proc(shaderCode: cstring, type: c.int) -> c.uint --- // Compile custom shader and return shader id (type: VERTEX_SHADER, FRAGMENT_SHADER, COMPUTE_SHADER) + LoadShaderProgram :: proc(vShaderId, fShaderId: c.uint) -> c.uint --- // Load custom shader program + UnloadShader :: proc(id: c.uint) --- // Unload shader, loaded with rlLoadShader() + UnloadShaderProgram :: proc(id: c.uint) --- // Unload shader program + GetLocationUniform :: proc(shaderId: c.uint, uniformName: cstring) -> c.int --- // Get shader location uniform + GetLocationAttrib :: proc(shaderId: c.uint, attribName: cstring) -> c.int --- // Get shader location attribute + SetUniform :: proc(locIndex: c.int, value: rawptr, uniformType: c.int, count: c.int) --- // Set shader value uniform + SetUniformMatrix :: proc(locIndex: c.int, mat: Matrix) --- // Set shader value matrix + SetUniformSampler :: proc(locIndex: c.int, textureId: c.uint) --- // Set shader value sampler + SetShader :: proc(id: c.uint, locs: [^]c.int) --- // Set shader currently active (id and locations) + + // Compute shader management + LoadComputeShaderProgram :: proc(shaderId: c.uint) -> c.uint --- // Load compute shader program + ComputeShaderDispatch :: proc(groupX, groupY, groupZ: c.uint) --- // Dispatch compute shader (equivalent to *draw* for graphics pipeline) + + // Shader buffer storage object management (ssbo) + LoadShaderBuffer :: proc(size: c.uint, data: rawptr, usageHint: c.int) -> c.uint --- // Load shader storage buffer object (SSBO) + UnloadShaderBuffer :: proc(ssboId: c.uint) --- // Unload shader storage buffer object (SSBO) + UpdateShaderBuffer :: proc(id: c.uint, data: rawptr, dataSize: c.uint, offset: c.uint) --- // Update SSBO buffer data + BindShaderBuffer :: proc(id: c.uint, index: c.uint) --- // Bind SSBO buffer + ReadShaderBuffer :: proc(id: c.uint, dest: rawptr, count: c.uint, offset: c.uint) --- // Read SSBO buffer data (GPU->CPU) + CopyShaderBuffer :: proc(destId, srcId: c.uint, destOffset, srcOffset: c.uint, count: c.uint) --- // Copy SSBO data between buffers + GetShaderBufferSize :: proc(id: c.uint) -> c.uint --- // Get SSBO buffer size + + // Buffer management + BindImageTexture :: proc(id: c.uint, index: c.uint, format: c.int, readonly: bool) --- // Bind image texture + + // Matrix state management + GetMatrixModelview :: proc() -> Matrix --- // Get internal modelview matrix + GetMatrixProjection :: proc() -> Matrix --- // Get internal projection matrix + GetMatrixTransform :: proc() -> Matrix --- // Get internal accumulated transform matrix + GetMatrixProjectionStereo :: proc(eye: c.int) -> Matrix --- // Get internal projection matrix for stereo render (selected eye) + GetMatrixViewOffsetStereo :: proc(eye: c.int) -> Matrix --- // Get internal view offset matrix for stereo render (selected eye) + SetMatrixProjection :: proc(proj: Matrix) --- // Set a custom projection matrix (replaces internal projection matrix) + SetMatrixModelview :: proc(view: Matrix) --- // Set a custom modelview matrix (replaces internal modelview matrix) + SetMatrixProjectionStereo :: proc(right, left: Matrix) --- // Set eyes projection matrices for stereo rendering + SetMatrixViewOffsetStereo :: proc(right, left: Matrix) --- // Set eyes view offsets matrices for stereo rendering + + // Quick and dirty cube/quad buffers load->draw->unload + LoadDrawCube :: proc() --- // Load and draw a cube + LoadDrawQuad :: proc() --- // Load and draw a quad +} diff --git a/src/main.odin b/src/main.odin new file mode 100644 index 0000000..42b54ea --- /dev/null +++ b/src/main.odin @@ -0,0 +1,44 @@ +package game + +import rl "libraries/raylib" +import "base:runtime" + +WINDOW_WIDTH :: 1280 +WINDOW_HEIGHT :: 720 +WINDOW_FRAMERATE :: 60 +WINDOW_CAPTION :: "Transformation Visualizer" + +init :: proc() { + rl.SetTraceLogLevel(.WARNING) + rl.SetConfigFlags({.WINDOW_RESIZABLE, .WINDOW_HIGHDPI}) + rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION) + rl.SetTargetFPS(WINDOW_FRAMERATE) +} + +update :: proc() { + rl.BeginDrawing() + defer rl.EndDrawing() + rl.ClearBackground(rl.BLACK) + rl.DrawText("Hello world", 32, 32, 32, rl.WHITE) +} + +parent_window_size_changed :: proc(w, h: int) { + rl.SetWindowSize(i32(w), i32(h)) +} + +shutdown :: proc() { + rl.CloseWindow() +} + +should_run :: proc() -> bool { + when ODIN_OS != .JS { + if rl.WindowShouldClose() { + return false + } + } + return true +} + +delta_time_fraction :: proc() -> f32 { + return rl.GetFrameTime() / (1.0 / WINDOW_FRAMERATE) +} diff --git a/src/utils.odin b/src/utils.odin new file mode 100644 index 0000000..65d4a45 --- /dev/null +++ b/src/utils.odin @@ -0,0 +1,327 @@ +package game + +import rl "libraries/raylib" +import "base:intrinsics" +import "core:strings" +import "core:fmt" +import "core:math" +import "core:math/linalg" +import "core:mem" + +IS_ENUM :: intrinsics.type_is_enum +IS_ENUMERATED_ARRAY :: intrinsics.type_is_enumerated_array + +/// ### RAYLIB DRAWING UTILS ### /// +FontStyle :: struct { + font: rl.Font, + size, spacing: f32, +} + +@require_results measure_text :: proc(text: cstring, style: FontStyle) -> v2 { + return rl.MeasureTextEx(style.font, text, style.size, style.spacing) +} + +draw_text :: proc(text: cstring, position: v2, style: FontStyle, color: rl.Color) { + rl.DrawTextEx(style.font, text, position, style.size, style.spacing, color) +} + +draw_multiline_text_centered :: proc(text: cstring, position: v2, style: FontStyle, color: rl.Color, temp_allocator := context.temp_allocator) { + size := measure_text(text, style) + split := strings.split(strings.clone_from_cstring(text), "\n", temp_allocator) + + for line, i in split { + line := strings.clone_to_cstring(line, temp_allocator) + line_size := measure_text(line, style) + offset := v2{(size.x - line_size.x) * 0.5, line_size.y * f32(i)} + draw_text(line, position + offset, style, color) + } +} + +JustificationHalign :: enum { + Left, + Middle, + Right, +} + +JustificationValign :: enum { + Top, + Center, + Bottom, +} + +JustificationAlign :: enum { + Shallow, + Middle, + Deep, +} + +draw_text_aligned :: proc(text: cstring, position: v2, style: FontStyle, halign: JustificationHalign, valign: JustificationValign, color: rl.Color) { + size := measure_text(text, style) + draw_text(text, justify_2d(position, size, halign, valign), style, color) +} + +@require_results justify_2d :: proc(position: v2, size: v2, halign: JustificationHalign, valign: JustificationValign) -> v2 { + position := position + switch halign { + case .Left: + case .Middle: position.x -= size.x / 2.0 + case .Right: position.x -= size.x + } + switch valign { + case .Top: + case .Center: position.y -= size.y / 2.0 + case .Bottom: position.y -= size.y + } + return position +} + +draw_float_aligned :: proc(value: f32, position: v2, style: FontStyle, halign: JustificationHalign, valign: JustificationValign, color: rl.Color) { + fractional := fract(value) + text: cstring + if fractional == 0.0 { + text = fmt.ctprintf("%.0f", value) + } else if fractional * 10.0 > 1.0 { + text = fmt.ctprintf("%.1f", value) + } else { + text = fmt.ctprintf("%.2f", value) + } + draw_text_aligned(text, position, style, halign, valign, color) +} + +draw_triangle :: proc(t: tri2, color: rl.Color) { + rl.DrawTriangle(t[0], t[1], t[2], color) +} + +/// ### MATH ### /// +fract :: proc(val: f32) -> f32 { + return abs(val - math.trunc(val)) +} + +color_to_fcolor :: proc(color: rl.Color) -> [4]f32 { + return cast([4]f32)(color) / 255.0 +} + +/// ### TYPES ### /// +v1 :: [1]f32 +v2 :: [2]f32 +v3 :: [3]f32 +v4 :: [4]f32 +iv2 :: [2]i32 +iv3 :: [3]i32 +iv4 :: [4]i32 +mat2 :: matrix[2, 2]f32 +mat3 :: matrix[3, 3]f32 +mat4 :: matrix[4, 4]f32 +rect2 :: rl.Rectangle +irect2 :: struct {x, y, width, height: i32} +tri2 :: [3]v2 +Bezier :: [3]v2 + +V2_ONE :: v2{1.0, 1.0} +V2_ZERO :: v2{} +V2_LEFT :: v2{-1.0, 0.0} +V2_RIGHT :: v2{1.0, 0.0} +V2_UP :: v2{0.0, -1.0} +V2_DOWN :: v2{0.0, 1.0} + +/// ### ENUM STRINGIFICATION ### /// +enum_to_raygui_options_string :: proc($T: typeid, allocator: mem.Allocator) -> cstring where IS_ENUM(T) { + b := strings.builder_make(allocator) + for element, i in T { + option := fmt.aprint(element, allocator = allocator) + fmt.sbprint(&b, strings.to_ada_case(option, allocator), ";", sep = "") + } + strings.builder_replace_all(&b, "_", " ") + b.buf[len(b.buf) - 1] = 0 + return cstring(raw_data(b.buf[:len(b.buf)])) +} + +enum_to_raygui_options_string_hotkey :: proc($T: typeid, hotkeys: ^$N, allocator: mem.Allocator) -> cstring where IS_ENUM(T), IS_ENUMERATED_ARRAY(N) { + b := strings.builder_make(allocator) + for element, i in T { + option := fmt.aprint(element, allocator = allocator) + option_ada := strings.to_ada_case(option, allocator) + if hotkeys[element] != .KEY_NULL { + fmt.sbprint(&b, option_ada, " (", rune(hotkeys[element]), ");", sep = "") + } else { + fmt.sbprint(&b, option_ada, ";", sep = "") + } + } + strings.builder_replace_all(&b, "_", " ") + b.buf[len(b.buf) - 1] = 0 + return cstring(raw_data(b.buf[:len(b.buf)])) +} + +/// ### RECTANGLES ### /// +rect2_to_irect2 :: proc "contextless" (rect: rect2) -> irect2 { + return {i32(rect.x), i32(rect.y), i32(rect.width), i32(rect.height)} +} + +irect2_to_rect2 :: proc "contextless" (rect: irect2) -> rect2 { + return {f32(rect.x), f32(rect.y), f32(rect.width), f32(rect.height)} +} + + +rect_has_point :: proc {rect2_has_point, irect2_has_point} + +@(private="file") rect2_has_point :: proc "contextless" (rect: rect2, p: v2) -> bool { + return p.x >= rect.x && p.x < rect.x + rect.width && p.y >= rect.y && p.y < rect.y + rect.height +} + +@(private="file") irect2_has_point :: proc "contextless" (rect: irect2, p: iv2) -> bool { + return p.x >= rect.x && p.x < rect.x + rect.width && p.y >= rect.y && p.y < rect.y + rect.height +} + + +rect_translate :: proc {rect2_translate, irect2_translate} + +@(private="file") rect2_translate :: proc "contextless" (rect: rect2, translation: v2) -> rect2 { + return {rect.x + translation.x, rect.y + translation.y, rect.width, rect.height} +} + +@(private="file") irect2_translate :: proc "contextless" (rect: irect2, translation: iv2) -> irect2 { + return {rect.x + translation.x, rect.y + translation.y, rect.width, rect.height} +} + + +rect_from :: proc {rect2_from_v2, irect2_from_iv2, rect2_from_corners, irect2_from_corners} + +@(private="file") rect2_from_v2 :: proc "contextless" (position, size: v2) -> rect2 { + return {position.x, position.y, size.x, size.y} +} + +@(private="file") irect2_from_iv2 :: proc "contextless" (position, size: iv2) -> irect2 { + return {position.x, position.y, size.x, size.y} +} + + +rect_scale :: proc{rect2_scale, irect2_scale} + +@(private="file") rect2_scale :: proc "contextless" (rect: rect2, scalar: f32) -> rect2 { + return {rect.x * scalar, rect.y * scalar, rect.width * scalar, rect.height * scalar} +} + +@(private="file") irect2_scale :: proc "contextless" (rect: irect2, scalar: i32) -> irect2 { + return {rect.x * scalar, rect.y * scalar, rect.width * scalar, rect.height * scalar} +} + + +rect_grow :: proc {rect2_grow, rect2_grow_v2, irect2_grow, rect2_grow_iv2} + +@(private="file") rect2_grow :: proc "contextless" (rect: rect2, grow: f32) -> rect2 { + return {rect.x - grow, rect.y - grow, rect.width + grow * 2.0, rect.height + grow * 2.0} +} + +@(private="file") rect2_grow_v2 :: proc "contextless" (rect: rect2, grow: v2) -> rect2 { + return {rect.x - grow.x, rect.y - grow.y, rect.width + grow.x * 2.0, rect.height + grow.y * 2.0} +} + +@(private="file") irect2_grow :: proc "contextless" (rect: irect2, grow: i32) -> irect2 { + return {rect.x - grow, rect.y - grow, rect.width + grow * 2, rect.height + grow * 2} +} + +@(private="file") rect2_grow_iv2 :: proc "contextless" (rect: irect2, grow: iv2) -> irect2 { + return {rect.x - grow.x, rect.y - grow.y, rect.width + grow.x * 2.0, rect.height + grow.y * 2.0} +} + + +rect_from_area :: proc {rect_from_area_v2, rect_from_area_iv2} + +@(private="file") rect_from_area_v2 :: proc "contextless" (start, end: v2) -> rect2 { + tl := linalg.min(start, end) + return {**tl, **(linalg.max(start, end) - tl)} +} + +@(private="file") rect_from_area_iv2 :: proc "contextless" (start, end: iv2) -> irect2 { + tl := linalg.min(start, end) + return {**tl, **(linalg.max(start, end) - tl)} +} + + +rect_from_corners :: proc {rect2_from_corners, irect2_from_corners} + +@(private="file") rect2_from_corners :: proc "contextless" (tl, tr, br, bl: v2) -> rect2 { + top_left := linalg.min(linalg.min(tl, tr, br), bl) + bottom_right := linalg.max(linalg.max(tl, tr, br), bl) + return {**top_left, **(bottom_right - top_left)} +} + +@(private="file") irect2_from_corners :: proc "contextless" (tl, tr, br, bl: iv2) -> irect2 { + top_left := linalg.min(linalg.min(tl, tr, br), bl) + bottom_right := linalg.max(linalg.max(tl, tr, br), bl) + return {**top_left, **(bottom_right - top_left)} +} + + +rect_get_tl :: proc {rect2_get_tl, irect2_get_tl} + +@(private="file") rect2_get_tl :: proc(rect: rect2) -> v2 { + return {rect.x, rect.y} +} + +@(private="file") irect2_get_tl :: proc(rect: irect2) -> iv2 { + return {rect.x, rect.y} +} + + +rect_get_br :: proc {rect2_get_br, irect2_get_br} + +@(private="file") rect2_get_br :: proc(rect: rect2) -> v2 { + return {rect.x + rect.width, rect.y + rect.height} +} + +@(private="file") irect2_get_br :: proc(rect: irect2) -> iv2 { + return {rect.x + rect.width, rect.y + rect.height} +} + + +rect_get_size :: proc {rect2_get_size, irect2_get_size} + +@(private="file") rect2_get_size :: proc(rect: rect2) -> v2 { + return {rect.width, rect.height} +} + +@(private="file") irect2_get_size :: proc(rect: irect2) -> iv2 { + return {rect.width, rect.height} +} + +/// ## LINEAR ALGEBRA ## /// +matrix3_translate2 :: proc "contextless" (translation: v2) -> mat3 { + return { + 1.0, 0.0, translation.x, + 0.0, 1.0, translation.y, + 0.0, 0.0, 1.0, + } +} + +matrix3_scale2 :: proc "contextless" (scale: v2) -> mat3 { + return { + scale.x, 0.0, 0.0, + 0.0, scale.y, 0.0, + 0.0, 0.0, 1.0, + } +} + +matrix3_rotate2 :: proc "contextless" (ang: f32) -> mat3 { + a := linalg.cos(ang) + b := linalg.sin(ang) + return { + a, b, 0, + -b, a, 0, + 0, 0, 1, + } +} + +matrix3_transform_xy :: proc "contextless" (m: mat3, v: v2) -> v2 { + return (m * v3{**v, 1.0}).xy +} + +matrix3_transform_rect2 :: proc(m: mat3, r: rect2) -> rect2 { + tl := m * v3{r.x, r.y, 1.0} + br := m * v3{r.x + r.width, r.y + r.height, 1.0} + return {**tl.xy, **(br - tl).xy} +} + +euclidean_to_polar :: proc(v: v2) -> v2 { + return {linalg.atan2(-v.y, v.x), linalg.length(v)} +} diff --git a/src/web/index_template.html b/src/web/index_template.html new file mode 100644 index 0000000..f383856 --- /dev/null +++ b/src/web/index_template.html @@ -0,0 +1,110 @@ + + + + + + + Odin + Raylib on the web + + + + + + + + + + + + + {{{ SCRIPT }}} + + diff --git a/src/web/main.odin b/src/web/main.odin new file mode 100644 index 0000000..58fa6a8 --- /dev/null +++ b/src/web/main.odin @@ -0,0 +1,231 @@ +package main + +import game ".." +import "core:log" +import "core:mem" +import "core:c" +import "base:intrinsics" +import "base:runtime" +import "core:strings" +import "core:fmt" + +@(private="file") web_context: runtime.Context + +@export main_start :: proc "c" () { + context = runtime.default_context() + + // The WASM allocator doesn't seem to work properly in combination with + // emscripten. There is some kind of conflict with how the manage memory. + // So this sets up an allocator that uses emscripten's malloc. + context.allocator = emscripten_allocator() + runtime.init_global_temporary_allocator(1*mem.Megabyte) + + // TODO (synthas): Do this if it works nicely + // Since we now use js_wasm32 we should be able to remove this and use + // context.logger = log.create_console_logger(). However, that one produces + // extra newlines on web. So it's a bug in that core lib. + context.logger = create_emscripten_logger() + + web_context = context + + game.init() +} + +@export main_update :: proc "c" () -> bool { + context = web_context + game.update() + return game.should_run() +} + +@export main_end :: proc "c" () { + context = web_context + game.shutdown() +} + +@export web_window_size_changed :: proc "c" (w: c.int, h: c.int) { + context = web_context + game.parent_window_size_changed(int(w), int(h)) +} + +/////////////////////////////////////////////////////// +/// EMSCRIPTEN ALLOCATOR /// +/////////////////////////////////////////////////////// + +// This will create bindings to emscripten's implementation of libc +// memory allocation features. +@(default_calling_convention = "c") +foreign { + calloc :: proc(num, size: c.size_t) -> rawptr --- + free :: proc(ptr: rawptr) --- + malloc :: proc(size: c.size_t) -> rawptr --- + realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- +} + +emscripten_allocator :: proc "contextless" () -> mem.Allocator { + return mem.Allocator{emscripten_allocator_proc, nil} +} + +emscripten_allocator_proc :: proc(allocator_data: rawptr,mode: mem.Allocator_Mode,size, alignment: int,old_memory: rawptr,old_size: int,location := #caller_location) -> (data: []byte, err: mem.Allocator_Error) { + // These aligned alloc procs are almost indentical those in + // `_heap_allocator_proc` in `core:os`. Without the proper alignment you + // cannot use maps and simd features. + + aligned_alloc :: proc(size, alignment: int, zero_memory: bool, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { + a := max(alignment, align_of(rawptr)) + space := size + a - 1 + + allocated_mem: rawptr + if old_ptr != nil { + original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ + allocated_mem = realloc(original_old_ptr, c.size_t(space+size_of(rawptr))) + } else if zero_memory { + // calloc automatically zeros memory, but it takes a number + size + // instead of just size. + allocated_mem = calloc(c.size_t(space+size_of(rawptr)), 1) + } else { + allocated_mem = malloc(c.size_t(space+size_of(rawptr))) + } + aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) + + ptr := uintptr(aligned_mem) + aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) + diff := int(aligned_ptr - ptr) + if (size + diff) > space || allocated_mem == nil { + return nil, .Out_Of_Memory + } + + aligned_mem = rawptr(aligned_ptr) + mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem + + return mem.byte_slice(aligned_mem, size), nil + } + + aligned_free :: proc(p: rawptr) { + if p != nil { + free(mem.ptr_offset((^rawptr)(p), -1)^) + } + } + + aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> ([]byte, mem.Allocator_Error) { + if p == nil { + return nil, nil + } + return aligned_alloc(new_size, new_alignment, true, p) + } + + switch mode { + case .Alloc: + return aligned_alloc(size, alignment, true) + + case .Alloc_Non_Zeroed: + return aligned_alloc(size, alignment, false) + + case .Free: + aligned_free(old_memory) + return nil, nil + + case .Resize: + if old_memory == nil { + return aligned_alloc(size, alignment, true) + } + + bytes := aligned_resize(old_memory, old_size, size, alignment) or_return + + // realloc doesn't zero the new bytes, so we do it manually. + if size > old_size { + new_region := raw_data(bytes[old_size:]) + intrinsics.mem_zero(new_region, size - old_size) + } + + return bytes, nil + + case .Resize_Non_Zeroed: + if old_memory == nil { + return aligned_alloc(size, alignment, false) + } + + return aligned_resize(old_memory, old_size, size, alignment) + + case .Query_Features: + set := (^mem.Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Free, .Resize, .Query_Features} + } + return nil, nil + + case .Free_All, .Query_Info: + return nil, .Mode_Not_Implemented + } + return nil, .Mode_Not_Implemented +} + +/////////////////////////////////////////////////////// +/// EMSCRIPTEN LOGGER /// +/////////////////////////////////////////////////////// +Emscripten_Logger_Opts :: log.Options{.Level, .Short_File_Path, .Line} + +create_emscripten_logger :: proc (lowest := log.Level.Debug, opt := Emscripten_Logger_Opts) -> log.Logger { + return log.Logger{data = nil, procedure = logger_proc, lowest_level = lowest, options = opt} +} + +// This create's a binding to `puts` which will be linked in as part of the +// emscripten runtime. +@(default_calling_convention = "c") +foreign { + puts :: proc(buffer: cstring) -> c.int --- +} + +@(private="file") logger_proc :: proc(logger_data: rawptr,level: log.Level,text: string,options: log.Options,location := #caller_location) { + b := strings.builder_make(context.temp_allocator) + strings.write_string(&b, Level_Headers[level]) + do_location_header(options, &b, location) + fmt.sbprint(&b, text) + + if bc, bc_err := strings.to_cstring(&b); bc_err == nil { + puts(bc) + } +} + +@(private="file") Level_Headers := [?]string { + 0 ..< 10 = "[DEBUG] --- ", + 10 ..< 20 = "[INFO ] --- ", + 20 ..< 30 = "[WARN ] --- ", + 30 ..< 40 = "[ERROR] --- ", + 40 ..< 50 = "[FATAL] --- ", +} + +@(private="file") do_location_header :: proc(opts: log.Options, buf: ^strings.Builder, location := #caller_location) { + if log.Location_Header_Opts & opts == nil { + return + } + fmt.sbprint(buf, "[") + file := location.file_path + if .Short_File_Path in opts { + last := 0 + for r, i in location.file_path { + if r == '/' { + last = i + 1 + } + } + file = location.file_path[last:] + } + + if log.Location_File_Opts & opts != nil { + fmt.sbprint(buf, file) + } + if .Line in opts { + if log.Location_File_Opts & opts != nil { + fmt.sbprint(buf, ":") + } + fmt.sbprint(buf, location.line) + } + + if .Procedure in opts { + if (log.Location_File_Opts | {.Line}) & opts != nil { + fmt.sbprint(buf, ":") + } + fmt.sbprintf(buf, "%s()", location.procedure) + } + + fmt.sbprint(buf, "] ") +} diff --git a/test_web.sh b/test_web.sh new file mode 100755 index 0000000..e7c82c6 --- /dev/null +++ b/test_web.sh @@ -0,0 +1 @@ +python3 -m http.server --directory build/web