diff options
Diffstat (limited to 'src/core.c')
| -rw-r--r-- | src/core.c | 2374 |
1 files changed, 1430 insertions, 944 deletions
@@ -1,23 +1,48 @@ /********************************************************************************************** * -* raylib.core +* raylib.core - Basic functions to manage windows, OpenGL context and input on multiple platforms * -* Basic functions to manage windows, OpenGL context and input on multiple platforms +* The following platforms are supported: Windows, Linux, Mac (OSX), Android, Raspberry Pi, HTML5, Oculus Rift CV1 * -* The following platforms are supported: -* PLATFORM_DESKTOP - Windows, Linux, Mac (OSX) -* PLATFORM_ANDROID - Only OpenGL ES 2.0 devices -* PLATFORM_RPI - Rapsberry Pi (tested on Raspbian) -* PLATFORM_WEB - Emscripten, HTML5 +* CONFIGURATION: * -* On PLATFORM_DESKTOP, the external lib GLFW3 (www.glfw.com) is used to manage graphic -* device, OpenGL context and input on multiple operating systems (Windows, Linux, OSX). +* #define PLATFORM_DESKTOP +* Windowing and input system configured for desktop platforms: Windows, Linux, OSX (managed by GLFW3 library) +* NOTE: Oculus Rift CV1 requires PLATFORM_DESKTOP for mirror rendering - View [rlgl] module to enable it * -* On PLATFORM_ANDROID, graphic device is managed by EGL and input system by Android activity. +* #define PLATFORM_ANDROID +* Windowing and input system configured for Android device, app activity managed internally in this module. +* NOTE: OpenGL ES 2.0 is required and graphic device is managed by EGL * -* On PLATFORM_RPI, graphic device is managed by EGL and input system is coded in raw mode. +* #define PLATFORM_RPI +* Windowing and input system configured for Raspberry Pi (tested on Raspbian), graphic device is managed by EGL +* and inputs are processed is raw mode, reading from /dev/input/ * -* Copyright (c) 2014 Ramon Santamaria (@raysan5) +* #define PLATFORM_WEB +* Windowing and input system configured for HTML5 (run on browser), code converted from C to asm.js +* using emscripten compiler. OpenGL ES 2.0 required for direct translation to WebGL equivalent code. +* +* #define LOAD_DEFAULT_FONT (defined by default) +* Default font is loaded on window initialization to be available for the user to render simple text. +* NOTE: If enabled, uses external module functions to load default raylib font (module: text) +* +* #define INCLUDE_CAMERA_SYSTEM / SUPPORT_CAMERA_SYSTEM +* +* #define INCLUDE_GESTURES_SYSTEM / SUPPORT_GESTURES_SYSTEM +* +* #define SUPPORT_MOUSE_GESTURES +* Mouse gestures are directly mapped like touches and processed by gestures system. +* +* DEPENDENCIES: +* GLFW3 - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX) +* raymath - 3D math functionality (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) 2014-2016 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. @@ -36,36 +61,40 @@ * **********************************************************************************************/ -#include "raylib.h" // raylib main header +#include "raylib.h" + #include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 -#include "utils.h" // TraceLog() function - // NOTE: Includes Android fopen map, InitAssetManager() - +#include "utils.h" // Required for: fopen() Android mapping, TraceLog() + #define RAYMATH_IMPLEMENTATION // Use raymath as a header-only library (includes implementation) #define RAYMATH_EXTERN_INLINE // Compile raymath functions as static inline (remember, it's a compiler hint) -#include "raymath.h" // Required for Vector3 and Matrix functions +#include "raymath.h" // Required for: Vector3 and Matrix functions + +#define GESTURES_IMPLEMENTATION +#include "gestures.h" // Gestures detection functionality + +#if !defined(PLATFORM_ANDROID) + #define CAMERA_IMPLEMENTATION + #include "camera.h" // Camera system functionality +#endif #include <stdio.h> // Standard input / output lib -#include <stdlib.h> // Declares malloc() and free() for memory management, rand(), atexit() -#include <stdint.h> // Required for typedef unsigned long long int uint64_t, used by hi-res timer -#include <time.h> // Useful to initialize random seed - Android/RPI hi-res timer (NOTE: Linux only!) -#include <math.h> // Math related functions, tan() used to set perspective -#include <string.h> // String function definitions, memset() -#include <errno.h> // Macros for reporting and retrieving error conditions through error codes +#include <stdlib.h> // Required for: malloc(), free(), rand(), atexit() +#include <stdint.h> // Required for: typedef unsigned long long int uint64_t, used by hi-res timer +#include <time.h> // Required for: time() - Android/RPI hi-res timer (NOTE: Linux only!) +#include <math.h> // Required for: tan() [Used in Begin3dMode() to set perspective] +#include <string.h> // Required for: strcmp() +//#include <errno.h> // Macros for reporting and retrieving error conditions through error codes -#if defined(PLATFORM_DESKTOP) - #define GLAD_EXTENSIONS_LOADER - #if defined(GLEW_EXTENSIONS_LOADER) - #define GLEW_STATIC - #include <GL/glew.h> // GLEW extensions loading lib - #elif defined(GLAD_EXTENSIONS_LOADER) - #include "glad.h" // GLAD library: Manage OpenGL headers and extensions - #endif +#if defined __linux || defined(PLATFORM_WEB) + #include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX +#elif defined __APPLE__ + #include <unistd.h> // Required for: usleep() #endif #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) - //#define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 - #include <GLFW/glfw3.h> // GLFW3 library: Windows, OpenGL context and Input management + //#define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 + #include <GLFW/glfw3.h> // GLFW3 library: Windows, OpenGL context and Input management #ifdef __linux #define GLFW_EXPOSE_NATIVE_X11 // Linux specific definitions for getting @@ -77,8 +106,7 @@ #endif #if defined(PLATFORM_ANDROID) - #include <jni.h> // Java native interface - #include <android/sensor.h> // Android sensors functions + //#include <android/sensor.h> // Android sensors functions (accelerometer, gyroscope, light...) #include <android/window.h> // Defines AWINDOW_FLAG_FULLSCREEN and others #include <android_native_app_glue.h> // Defines basic app state struct and manages activity @@ -95,7 +123,7 @@ #include <sys/ioctl.h> // UNIX System call for device-specific input/output operations - ioctl() #include <linux/kd.h> // Linux: KDSKBMODE, K_MEDIUMRAM constants definition #include <linux/input.h> // Linux: Keycodes constants definition (KEY_A, ...) - #include <linux/joystick.h> + #include <linux/joystick.h> // Linux: Joystick support library #include "bcm_host.h" // Raspberry Pi VideoCore IV access functions @@ -116,19 +144,25 @@ #if defined(PLATFORM_RPI) // Old device inputs system - #define DEFAULT_KEYBOARD_DEV STDIN_FILENO // Standard input - #define DEFAULT_MOUSE_DEV "/dev/input/mouse0" - #define DEFAULT_GAMEPAD_DEV "/dev/input/js0" + #define DEFAULT_KEYBOARD_DEV STDIN_FILENO // Standard input + #define DEFAULT_MOUSE_DEV "/dev/input/mouse0" // Mouse input + #define DEFAULT_TOUCH_DEV "/dev/input/event4" // Touch input virtual device (created by ts_uinput) + #define DEFAULT_GAMEPAD_DEV "/dev/input/js" // Gamepad input (base dev for all gamepads: js0, js1, ...) // New device input events (evdev) (must be detected) //#define DEFAULT_KEYBOARD_DEV "/dev/input/eventN" //#define DEFAULT_MOUSE_DEV "/dev/input/eventN" //#define DEFAULT_GAMEPAD_DEV "/dev/input/eventN" - + #define MOUSE_SENSITIVITY 0.8f - #define MAX_GAMEPAD_BUTTONS 11 #endif +#define MAX_GAMEPADS 4 // Max number of gamepads supported +#define MAX_GAMEPAD_BUTTONS 32 // Max bumber of buttons supported (per gamepad) +#define MAX_GAMEPAD_AXIS 8 // Max number of axis supported (per gamepad) + +#define LOAD_DEFAULT_FONT // Load default font on window initialization (module: text) + //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- @@ -140,16 +174,20 @@ #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) static GLFWwindow *window; // Native window (graphic device) static bool windowMinimized = false; -#elif defined(PLATFORM_ANDROID) +#endif + +#if defined(PLATFORM_ANDROID) static struct android_app *app; // Android activity static struct android_poll_source *source; // Android events polling source -static int ident, events; +static int ident, events; // Android ALooper_pollAll() variables +static const char *internalDataPath; // Android internal data path to write data (/data/data/<package>/files) + static bool windowReady = false; // Used to detect display initialization static bool appEnabled = true; // Used to detec if app is active static bool contextRebindRequired = false; // Used to know context rebind required -static int previousButtonState[128] = { 1 }; // Required to check if button pressed/released once -static int currentButtonState[128] = { 1 }; // Required to check if button pressed/released once -#elif defined(PLATFORM_RPI) +#endif + +#if defined(PLATFORM_RPI) static EGL_DISPMANX_WINDOW_T nativeWindow; // Native window (graphic device) // Keyboard input variables @@ -160,67 +198,68 @@ static int defaultKeyboardMode; // Used to store default keyboar // Mouse input variables static int mouseStream = -1; // Mouse device file descriptor static bool mouseReady = false; // Flag to know if mouse is ready -pthread_t mouseThreadId; // Mouse reading thread id +static pthread_t mouseThreadId; // Mouse reading thread id -// Gamepad input variables -static int gamepadStream = -1; // Gamepad device file descriptor -static bool gamepadReady = false; // Flag to know if gamepad is ready -pthread_t gamepadThreadId; // Gamepad reading thread id +// Touch input variables +static int touchStream = -1; // Touch device file descriptor +static bool touchReady = false; // Flag to know if touch interface is ready +static pthread_t touchThreadId; // Touch reading thread id -int gamepadButtons[MAX_GAMEPAD_BUTTONS]; -int gamepadAxisX = 0; -int gamepadAxisY = 0; +// Gamepad input variables +static int gamepadStream[MAX_GAMEPADS] = { -1 };// Gamepad device file descriptor +static pthread_t gamepadThreadId; // Gamepad reading thread id +static char gamepadName[64]; // Gamepad name holder #endif #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) -static EGLDisplay display; // Native display device (physical screen connection) -static EGLSurface surface; // Surface to draw on, framebuffers (connected to context) -static EGLContext context; // Graphic context, mode in which drawing can be done -static EGLConfig config; // Graphic config -static uint64_t baseTime; // Base time measure for hi-res timer -static bool windowShouldClose = false; // Flag to set window for closing +static EGLDisplay display; // Native display device (physical screen connection) +static EGLSurface surface; // Surface to draw on, framebuffers (connected to context) +static EGLContext context; // Graphic context, mode in which drawing can be done +static EGLConfig config; // Graphic config +static uint64_t baseTime; // Base time measure for hi-res timer +static bool windowShouldClose = false; // Flag to set window for closing #endif -static unsigned int displayWidth, displayHeight; // Display width and height (monitor, device-screen, LCD, ...) +// Display size-related data +static unsigned int displayWidth, displayHeight; // Display width and height (monitor, device-screen, LCD, ...) static int screenWidth, screenHeight; // Screen width and height (used render area) -static int renderWidth, renderHeight; // Framebuffer width and height (render area) - // NOTE: Framebuffer could include black bars - +static int renderWidth, renderHeight; // Framebuffer width and height (render area, including black bars if required) static int renderOffsetX = 0; // Offset X from render area (must be divided by 2) static int renderOffsetY = 0; // Offset Y from render area (must be divided by 2) static bool fullscreen = false; // Fullscreen mode (useful only for PLATFORM_DESKTOP) static Matrix downscaleView; // Matrix to downscale view (in case screen size bigger than display size) -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) -static Vector2 touchPosition[MAX_TOUCH_POINTS]; // Touch position on screen -#endif - #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) static const char *windowTitle; // Window text title... - -static bool customCursor = false; // Tracks if custom cursor has been set static bool cursorOnScreen = false; // Tracks if cursor is inside client area -static Texture2D cursor; // Cursor texture +static bool cursorHidden = false; // Track if cursor is hidden -static Vector2 mousePosition; // Mouse position on screen +// Register mouse states +static char previousMouseState[3] = { 0 }; // Registers previous mouse button state +static char currentMouseState[3] = { 0 }; // Registers current mouse button state +static int previousMouseWheelY = 0; // Registers previous mouse wheel variation +static int currentMouseWheelY = 0; // Registers current mouse wheel variation -static char previousKeyState[512] = { 0 }; // Required to check if key pressed/released once -static char currentKeyState[512] = { 0 }; // Required to check if key pressed/released once +// Register gamepads states +static bool gamepadReady[MAX_GAMEPADS] = { false }; // Flag to know if gamepad is ready +static float gamepadAxisState[MAX_GAMEPADS][MAX_GAMEPAD_AXIS]; // Gamepad axis state +static char previousGamepadState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state +static char currentGamepadState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state -static char previousMouseState[3] = { 0 }; // Required to check if mouse btn pressed/released once -static char currentMouseState[3] = { 0 }; // Required to check if mouse btn pressed/released once - -static char previousGamepadState[32] = {0}; // Required to check if gamepad btn pressed/released once -static char currentGamepadState[32] = {0}; // Required to check if gamepad btn pressed/released once +// Keyboard configuration +static int exitKey = KEY_ESCAPE; // Default exit key (ESC) +#endif -static int previousMouseWheelY = 0; // Required to track mouse wheel variation -static int currentMouseWheelY = 0; // Required to track mouse wheel variation +// Register keyboard states +static char previousKeyState[512] = { 0 }; // Registers previous frame key state +static char currentKeyState[512] = { 0 }; // Registers current frame key state -static int exitKey = KEY_ESCAPE; // Default exit key (ESC) static int lastKeyPressed = -1; // Register last key pressed +static int lastGamepadButtonPressed = -1; // Register last gamepad button pressed +static int gamepadAxisCount = 0; // Register number of available gamepad axis -static bool cursorHidden; // Track if cursor is hidden -#endif +static Vector2 mousePosition; // Mouse position on screen +static Vector2 touchPosition[MAX_TOUCH_POINTS]; // Touch position on screen #if defined(PLATFORM_DESKTOP) static char **dropFilesPath; // Store dropped files paths as strings @@ -229,40 +268,36 @@ static int dropFilesCount = 0; // Count stored strings static double currentTime, previousTime; // Used to track timmings static double updateTime, drawTime; // Time measures for update and draw -static double frameTime; // Time measure for one frame +static double frameTime = 0.0; // Time measure for one frame static double targetTime = 0.0; // Desired time for one frame, if 0 not applied -static char configFlags = 0; // Configuration flags (bit based) +static char configFlags = 0; // Configuration flags (bit based) static bool showLogo = false; // Track if showing logo at init is enabled //---------------------------------------------------------------------------------- // Other Modules Functions Declaration (required by core) //---------------------------------------------------------------------------------- -extern void LoadDefaultFont(void); // [Module: text] Loads default font on InitWindow() -extern void UnloadDefaultFont(void); // [Module: text] Unloads default font from GPU memory +#if defined(LOAD_DEFAULT_FONT) +extern void LoadDefaultFont(void); // [Module: text] Loads default font on InitWindow() +extern void UnloadDefaultFont(void); // [Module: text] Unloads default font from GPU memory +#endif //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -static void InitDisplay(int width, int height); // Initialize display device and framebuffer -static void InitGraphics(void); // Initialize OpenGL graphics +static void InitGraphicsDevice(int width, int height); // Initialize graphics device +static void SetupFramebufferSize(int displayWidth, int displayHeight); static void InitTimer(void); // Initialize timer static double GetTime(void); // Returns time since InitTimer() was run +static void Wait(float ms); // Wait for some milliseconds (stop program execution) static bool GetKeyStatus(int key); // Returns if a key has been pressed static bool GetMouseButtonStatus(int button); // Returns if a mouse button has been pressed -static void SwapBuffers(void); // Copy back buffer to front buffers static void PollInputEvents(void); // Register user events +static void SwapBuffers(void); // Copy back buffer to front buffers static void LogoAnimation(void); // Plays raylib logo appearing animation -static void SetupFramebufferSize(int displayWidth, int displayHeight); - -#if defined(PLATFORM_RPI) -static void InitKeyboard(void); // Init raw keyboard system (standard input reading) -static void ProcessKeyboard(void); // Process keyboard events -static void RestoreKeyboard(void); // Restore keyboard system -static void InitMouse(void); // Mouse initialization (including mouse thread) -static void *MouseThread(void *arg); // Mouse reading thread -static void InitGamepad(void); // Init raw gamepad input -static void *GamepadThread(void *arg); // Mouse reading thread +static void SetupViewport(void); // Set viewport parameters +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) +static void TakeScreenshot(void); // Takes a screenshot and saves it in the same folder as executable #endif #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) @@ -281,10 +316,6 @@ static void WindowIconifyCallback(GLFWwindow *window, int iconified); static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window #endif -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) -static void TakeScreenshot(void); // Takes a screenshot and saves it in the same folder as executable -#endif - #if defined(PLATFORM_ANDROID) 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 @@ -293,6 +324,24 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) #if defined(PLATFORM_WEB) static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *e, void *userData); static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); +#endif + +#if defined(PLATFORM_RPI) +static void InitKeyboard(void); // Init raw keyboard system (standard input reading) +static void ProcessKeyboard(void); // Process keyboard events +static void RestoreKeyboard(void); // Restore keyboard system +static void InitMouse(void); // Mouse initialization (including mouse thread) +static void *MouseThread(void *arg); // Mouse reading thread +static void InitTouch(void); // Touch device initialization (including touch thread) +static void *TouchThread(void *arg); // Touch device reading thread +static void InitGamepad(void); // Init raw gamepad input +static void *GamepadThread(void *arg); // Mouse reading thread +#endif + +#if defined(_WIN32) + // NOTE: We include Sleep() function signature here to avoid windows.h inclusion + void __stdcall Sleep(unsigned long msTimeout); // Required for Wait() #endif //---------------------------------------------------------------------------------- @@ -302,20 +351,19 @@ static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent // Initialize Window and Graphics Context (OpenGL) void InitWindow(int width, int height, const char *title) { - TraceLog(INFO, "Initializing raylib (v1.4.0)"); + TraceLog(INFO, "Initializing raylib (v1.7.0)"); // Store window title (could be useful...) windowTitle = title; - // Init device display (monitor, LCD, ...) - InitDisplay(width, height); - - // Init OpenGL graphics - InitGraphics(); + // Init graphics device (display device and OpenGL context) + InitGraphicsDevice(width, height); - // Load default font for convenience +#if defined(LOAD_DEFAULT_FONT) + // Load default font // NOTE: External function (defined in module: text) LoadDefaultFont(); +#endif // Init hi-res timer InitTimer(); @@ -323,6 +371,7 @@ void InitWindow(int width, int height, const char *title) #if defined(PLATFORM_RPI) // Init raw input system InitMouse(); // Mouse init + InitTouch(); // Touch init InitKeyboard(); // Keyboard init InitGamepad(); // Gamepad init #endif @@ -337,10 +386,10 @@ void InitWindow(int width, int height, const char *title) emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenInputCallback); emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenInputCallback); emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenInputCallback); - - // TODO: Add gamepad support (not provided by GLFW3 on emscripten) - //emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenInputCallback); - //emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenInputCallback); + + // Support gamepad (not provided by GLFW3 on emscripten) + emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); + emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback); #endif mousePosition.x = (float)screenWidth/2.0f; @@ -353,19 +402,21 @@ void InitWindow(int width, int height, const char *title) LogoAnimation(); } } +#endif -#elif defined(PLATFORM_ANDROID) +#if defined(PLATFORM_ANDROID) // Android activity initialization -void InitWindow(int width, int height, struct android_app *state) +void InitWindow(int width, int height, void *state) { - TraceLog(INFO, "Initializing raylib (v1.4.0)"); + TraceLog(INFO, "Initializing raylib (v1.7.0)"); app_dummy(); screenWidth = width; screenHeight = height; - app = state; + app = (struct android_app *)state; + internalDataPath = app->activity->internalDataPath; // Set desired windows flags before initializing anything ANativeActivity_setWindowFlags(app->activity, AWINDOW_FLAG_FULLSCREEN, 0); //AWINDOW_FLAG_SCALED, AWINDOW_FLAG_DITHER @@ -396,18 +447,11 @@ void InitWindow(int width, int height, struct android_app *state) //state->userData = &engine; app->onAppCmd = AndroidCommandCallback; app->onInputEvent = AndroidInputCallback; - + InitAssetManager(app->activity->assetManager); TraceLog(INFO, "Android app initialized successfully"); - // Init button states values (default up) - for(int i = 0; i < 128; i++) - { - currentButtonState[i] = 1; - previousButtonState[i] = 1; - } - // Wait for window to be initialized (display and context) while (!windowReady) { @@ -427,14 +471,18 @@ void InitWindow(int width, int height, struct android_app *state) // Close Window and Terminate Context void CloseWindow(void) { +#if defined(LOAD_DEFAULT_FONT) UnloadDefaultFont(); +#endif rlglClose(); // De-init rlgl #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) glfwDestroyWindow(window); glfwTerminate(); -#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) // Close surface, context and display if (display != EGL_NO_DISPLAY) { @@ -457,6 +505,18 @@ void CloseWindow(void) } #endif +#if defined(PLATFORM_RPI) + // Wait for mouse and gamepad threads to finish before closing + // NOTE: Those threads should already have finished at this point + // because they are controlled by windowShouldClose variable + + windowShouldClose = true; // Added to force threads to exit when the close window is called + + pthread_join(mouseThreadId, NULL); + pthread_join(touchThreadId, NULL); + pthread_join(gamepadThreadId, NULL); +#endif + TraceLog(INFO, "Window closed successfully"); } @@ -465,10 +525,12 @@ bool WindowShouldClose(void) { #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) // While window minimized, stop loop execution - while (windowMinimized) glfwPollEvents(); + while (windowMinimized) glfwWaitEvents(); return (glfwWindowShouldClose(window)); -#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) return windowShouldClose; #endif } @@ -483,44 +545,60 @@ bool IsWindowMinimized(void) #endif } -// Fullscreen toggle -// TODO: When destroying window context is lost and resources too, take care! +// Fullscreen toggle (only PLATFORM_DESKTOP) void ToggleFullscreen(void) { #if defined(PLATFORM_DESKTOP) fullscreen = !fullscreen; // Toggle fullscreen flag - rlglClose(); // De-init rlgl - glfwDestroyWindow(window); // Destroy the current window (we will recreate it!) + // NOTE: glfwSetWindowMonitor() doesn't work properly (bugs) + if (fullscreen) glfwSetWindowMonitor(window, glfwGetPrimaryMonitor(), 0, 0, screenWidth, screenHeight, GLFW_DONT_CARE); + else glfwSetWindowMonitor(window, NULL, 0, 0, screenWidth, screenHeight, GLFW_DONT_CARE); +#endif - InitWindow(screenWidth, screenHeight, windowTitle); -#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) TraceLog(WARNING, "Could not toggle to windowed mode"); #endif } -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) -// Set a custom cursor icon/image -void SetCustomCursor(const char *cursorImage) +// Set icon for window (only PLATFORM_DESKTOP) +void SetWindowIcon(Image image) { - if (customCursor) UnloadTexture(cursor); +#if defined(PLATFORM_DESKTOP) + ImageFormat(&image, UNCOMPRESSED_R8G8B8A8); - cursor = LoadTexture(cursorImage); + GLFWimage icon[1]; + + icon[0].width = image.width; + icon[0].height = image.height; + icon[0].pixels = (unsigned char *)image.data; -#if defined(PLATFORM_DESKTOP) - // NOTE: emscripten not implemented - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + // NOTE: We only support one image icon + glfwSetWindowIcon(window, 1, icon); + + // TODO: Support multi-image icons --> image.mipmaps #endif - customCursor = true; } -// Set a custom key to exit program -// NOTE: default exitKey is ESCAPE -void SetExitKey(int key) +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) { - exitKey = key; + glfwSetWindowPos(window, x, y); +} + +// Set monitor for the current window (fullscreen mode) +void SetWindowMonitor(int monitor) +{ + int monitorCount; + GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + glfwSetWindowMonitor(window, monitors[monitor], 0, 0, screenWidth, screenHeight, GLFW_DONT_CARE); + TraceLog(INFO, "Selected fullscreen monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); + } + else TraceLog(WARNING, "Selected monitor not found"); } -#endif // Get current screen width int GetScreenWidth(void) @@ -534,6 +612,65 @@ int GetScreenHeight(void) return screenHeight; } +#if !defined(PLATFORM_ANDROID) +// Show mouse cursor +void ShowCursor() +{ +#if defined(PLATFORM_DESKTOP) + #ifdef __linux + XUndefineCursor(glfwGetX11Display(), glfwGetX11Window(window)); + #else + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + #endif +#endif + cursorHidden = false; +} + +// Hide mouse cursor +void HideCursor() +{ +#if defined(PLATFORM_DESKTOP) + #ifdef __linux + XColor col; + const char nil[] = {0}; + + Pixmap pix = XCreateBitmapFromData(glfwGetX11Display(), glfwGetX11Window(window), nil, 1, 1); + Cursor cur = XCreatePixmapCursor(glfwGetX11Display(), pix, pix, &col, &col, 0, 0); + + XDefineCursor(glfwGetX11Display(), glfwGetX11Window(window), cur); + XFreeCursor(glfwGetX11Display(), cur); + #else + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + #endif +#endif + cursorHidden = true; +} + +// Check if mouse cursor is hidden +bool IsCursorHidden() +{ + return cursorHidden; +} + +// Enable mouse cursor +void EnableCursor() +{ +#if defined(PLATFORM_DESKTOP) + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); +#endif + cursorHidden = false; +} + +// Disable mouse cursor +void DisableCursor() +{ +#if defined(PLATFORM_DESKTOP) + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); +#endif + cursorHidden = true; +} +#endif // !defined(PLATFORM_ANDROID) + // Sets Background Color void ClearBackground(Color color) { @@ -548,63 +685,75 @@ void BeginDrawing(void) updateTime = currentTime - previousTime; previousTime = currentTime; - if (IsPosproShaderEnabled()) rlEnablePostproFBO(); - - rlClearScreenBuffers(); - + rlClearScreenBuffers(); // Clear current framebuffers rlLoadIdentity(); // Reset current matrix (MODELVIEW) - rlMultMatrixf(MatrixToFloat(downscaleView)); // If downscale required, apply it here //rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 // NOTE: Not required with OpenGL 3.3+ } -// Setup drawing canvas with extended parameters -void BeginDrawingEx(int blendMode, Shader shader, Matrix transform) -{ - BeginDrawing(); - - SetBlendMode(blendMode); - SetPostproShader(shader); - - rlMultMatrixf(MatrixToFloat(transform)); -} - // End canvas drawing and Swap Buffers (Double Buffering) void EndDrawing(void) { rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) - if (IsPosproShaderEnabled()) rlglDrawPostpro(); // Draw postprocessing effect (shader) - SwapBuffers(); // Copy back buffer to front buffer - PollInputEvents(); // Poll user events - + + // Frame time control system currentTime = GetTime(); drawTime = currentTime - previousTime; previousTime = currentTime; - + frameTime = updateTime + drawTime; - double extraTime = 0.0; - - while (frameTime < targetTime) + // Wait for some milliseconds... + if (frameTime < targetTime) { - // Implement a delay + Wait((targetTime - frameTime)*1000.0f); + currentTime = GetTime(); - extraTime = currentTime - previousTime; + double extraTime = currentTime - previousTime; previousTime = currentTime; + frameTime += extraTime; } } +// Initialize 2D mode with custom camera +void Begin2dMode(Camera2D camera) +{ + rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + + rlLoadIdentity(); // Reset current matrix (MODELVIEW) + + // Camera rotation and scaling is always relative to 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.target.x, camera.offset.y + camera.target.y, 0.0f); + + Matrix matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation); + + rlMultMatrixf(MatrixToFloat(matTransform)); +} + +// Ends 2D mode custom camera usage +void End2dMode(void) +{ + rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + + rlLoadIdentity(); // Reset current matrix (MODELVIEW) +} + // Initializes 3D mode for drawing (Camera setup) void Begin3dMode(Camera camera) { rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + if (IsVrDeviceReady() || IsVrSimulator()) BeginVrDrawing(); + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection @@ -612,24 +761,28 @@ void Begin3dMode(Camera camera) // Setup perspective projection float aspect = (float)screenWidth/(float)screenHeight; - double top = 0.1*tan(45.0*PI/360.0); + double top = 0.01*tan(camera.fovy*PI/360.0); double right = top*aspect; // NOTE: zNear and zFar values are important when computing depth buffer values - rlFrustum(-right, right, -top, top, 0.1f, 1000.0f); + rlFrustum(-right, right, -top, top, 0.01, 1000.0); 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) + Matrix cameraView = MatrixLookAt(camera.position, camera.target, camera.up); + rlMultMatrixf(MatrixToFloat(cameraView)); // Multiply MODELVIEW matrix by view matrix (camera) + + rlEnableDepthTest(); // Enable DEPTH_TEST for 3D } // Ends 3D mode and returns to default 2D orthographic mode void End3dMode(void) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlglDraw(); // Process internal buffers (update + draw) + + if (IsVrDeviceReady() || IsVrSimulator()) EndVrDrawing(); rlMatrixMode(RL_PROJECTION); // Switch to projection matrix rlPopMatrix(); // Restore previous matrix (PROJECTION) from matrix stack @@ -638,31 +791,76 @@ void End3dMode(void) rlLoadIdentity(); // Reset current matrix (MODELVIEW) //rlTranslatef(0.375, 0.375, 0); // HACK to ensure pixel-perfect drawing on OpenGL (after exiting 3D mode) + + rlDisableDepthTest(); // Disable DEPTH_TEST for 2D +} + +// Initializes render texture for drawing +void BeginTextureMode(RenderTexture2D target) +{ + rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + + rlEnableRenderTexture(target.id); // Enable render target + + rlClearScreenBuffers(); // Clear render texture buffers + + // Set viewport to framebuffer size + rlViewport(0, 0, target.texture.width, 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 (?) +} + +// Ends drawing to render texture +void EndTextureMode(void) +{ + rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + + rlDisableRenderTexture(); // Disable render target + + // Set viewport to default framebuffer size (screen size) + SetupViewport(); + + 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, GetScreenWidth(), GetScreenHeight(), 0, 0.0f, 1.0f); + + rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix + rlLoadIdentity(); // Reset current matrix (MODELVIEW) } // Set target FPS for the game void SetTargetFPS(int fps) { - targetTime = 1.0/(double)fps; + if (fps < 1) targetTime = 0.0; + else targetTime = 1.0/(double)fps; TraceLog(INFO, "Target time per frame: %02.03f milliseconds", (float)targetTime*1000); } // Returns current FPS -float GetFPS(void) +int GetFPS(void) { - return (float)(1.0/frameTime); + return (int)(1.0f/GetFrameTime()); } // Returns time in seconds for one frame float GetFrameTime(void) { - // As we are operate quite a lot with frameTime, - // it could be no stable, so we round it before passing it around - // NOTE: There are still problems with high framerates (>500fps) - double roundedFrameTime = round(frameTime*10000)/10000.0; - - return (float)roundedFrameTime; // Time in seconds to run a frame + // NOTE: We round value to milliseconds + return (float)frameTime; } // Converts Color to float array and normalizes @@ -691,7 +889,7 @@ float *VectorToFloat(Vector3 vec) } // Converts Matrix to float array -// NOTE: Returned vector is a transposed version of the Matrix struct, +// NOTE: Returned vector is a transposed version of the Matrix struct, // it should be this way because, despite raymath use OpenGL column-major convention, // Matrix struct memory alignment and variables naming are not coherent float *MatrixToFloat(Matrix mat) @@ -755,7 +953,7 @@ Color Fade(Color color, float alpha) { if (alpha < 0.0f) alpha = 0.0f; else if (alpha > 1.0f) alpha = 1.0f; - + float colorAlpha = (float)color.a*alpha; return (Color){color.r, color.g, color.b, (unsigned char)colorAlpha}; @@ -797,9 +995,9 @@ void ClearDroppedFiles(void) if (dropFilesCount > 0) { for (int i = 0; i < dropFilesCount; i++) free(dropFilesPath[i]); - + free(dropFilesPath); - + dropFilesCount = 0; } } @@ -811,11 +1009,20 @@ void StorageSaveValue(int position, int value) { FILE *storageFile = NULL; + char path[128]; +#if defined(PLATFORM_ANDROID) + strcpy(path, internalDataPath); + strcat(path, "/"); + strcat(path, STORAGE_FILENAME); +#else + strcpy(path, STORAGE_FILENAME); +#endif + // Try open existing file to append data - storageFile = fopen(STORAGE_FILENAME, "rb+"); + storageFile = fopen(path, "rb+"); // If file doesn't exist, create a new storage data file - if (!storageFile) storageFile = fopen(STORAGE_FILENAME, "wb"); + if (!storageFile) storageFile = fopen(path, "wb"); if (!storageFile) TraceLog(WARNING, "Storage data file could not be created"); else @@ -824,14 +1031,14 @@ void StorageSaveValue(int position, int value) fseek(storageFile, 0, SEEK_END); int fileSize = ftell(storageFile); // Size in bytes fseek(storageFile, 0, SEEK_SET); - + if (fileSize < (position*4)) TraceLog(WARNING, "Storage position could not be found"); else { fseek(storageFile, (position*4), SEEK_SET); fwrite(&value, 1, 4, storageFile); } - + fclose(storageFile); } } @@ -841,122 +1048,131 @@ void StorageSaveValue(int position, int value) int StorageLoadValue(int position) { int value = 0; - + + char path[128]; +#if defined(PLATFORM_ANDROID) + strcpy(path, internalDataPath); + strcat(path, "/"); + strcat(path, STORAGE_FILENAME); +#else + strcpy(path, STORAGE_FILENAME); +#endif + // Try open existing file to append data - FILE *storageFile = fopen(STORAGE_FILENAME, "rb"); + FILE *storageFile = fopen(path, "rb"); if (!storageFile) TraceLog(WARNING, "Storage data file could not be found"); else { // Get file size fseek(storageFile, 0, SEEK_END); - int fileSize = ftell(storageFile); // Size in bytes + int fileSize = ftell(storageFile); // Size in bytes rewind(storageFile); - + if (fileSize < (position*4)) TraceLog(WARNING, "Storage position could not be found"); else { fseek(storageFile, (position*4), SEEK_SET); - fread(&value, 1, 4, storageFile); + fread(&value, 4, 1, storageFile); // Read 1 element of 4 bytes size } - + fclose(storageFile); } - + return value; } // Returns a ray trace from mouse position -//http://www.songho.ca/opengl/gl_transform.html -//http://www.songho.ca/opengl/gl_matrix.html -//http://www.sjbaker.org/steve/omniv/matrices_can_be_your_friends.html -//https://www.opengl.org/archives/resources/faq/technical/transformations.htm Ray GetMouseRay(Vector2 mousePosition, Camera camera) { - // Tutorial used: https://mkonrad.net/2014/08/07/simple-opengl-object-picking-in-3d.html - // Similar to http://antongerdelan.net, the problem is maybe in MatrixPerspective vs MatrixFrustum - // or matrix order (transpose it or not... that's the question) - Ray ray; - + // Calculate normalized device coordinates // NOTE: y value is negative float x = (2.0f*mousePosition.x)/(float)GetScreenWidth() - 1.0f; float y = 1.0f - (2.0f*mousePosition.y)/(float)GetScreenHeight(); float z = 1.0f; - + // Store values in a vector - Vector3 deviceCoords = {x, y, z}; - - // Device debug message - TraceLog(INFO, "device(%f, %f, %f)", deviceCoords.x, deviceCoords.y, deviceCoords.z); - - // Calculate projection matrix (from perspective instead of frustum - Matrix matProj = MatrixPerspective(45.0f, (float)((float)GetScreenWidth() / (float)GetScreenHeight()), 0.01f, 1000.0f); - + Vector3 deviceCoords = { x, y, z }; + + TraceLog(DEBUG, "Device coordinates: (%f, %f, %f)", deviceCoords.x, deviceCoords.y, deviceCoords.z); + + // Calculate projection matrix (from perspective instead of frustum) + Matrix matProj = MatrixPerspective(camera.fovy, ((double)GetScreenWidth()/(double)GetScreenHeight()), 0.01, 1000.0); + // Calculate view matrix from camera look at Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); - + // Do I need to transpose it? It seems that yes... - // NOTE: matrix order is maybe incorrect... In OpenGL to get world position from + // NOTE: matrix order may be incorrect... In OpenGL to get world position from // camera view it just needs to get inverted, but here we need to transpose it too. // For example, if you get view matrix, transpose and inverted and you transform it // to a vector, you will get its 3d world position coordinates (camera.position). // If you don't transpose, final position will be wrong. MatrixTranspose(&matView); - + +//#define USE_RLGL_UNPROJECT +#if defined(USE_RLGL_UNPROJECT) // OPTION 1: Use rlglUnproject() + + Vector3 nearPoint = rlglUnproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); + Vector3 farPoint = rlglUnproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); + +#else // OPTION 2: Compute unprojection directly here + // Calculate unproject matrix (multiply projection matrix and view matrix) and invert it Matrix matProjView = MatrixMultiply(matProj, matView); MatrixInvert(&matProjView); - + // Calculate far and near points - Quaternion near = { deviceCoords.x, deviceCoords.y, 0.0f, 1.0f}; - Quaternion far = { deviceCoords.x, deviceCoords.y, 1.0f, 1.0f}; - + Quaternion near = { deviceCoords.x, deviceCoords.y, 0.0f, 1.0f }; + Quaternion far = { deviceCoords.x, deviceCoords.y, 1.0f, 1.0f }; + // Multiply points by unproject matrix QuaternionTransform(&near, matProjView); QuaternionTransform(&far, matProjView); - + // Calculate normalized world points in vectors - Vector3 nearPoint = {near.x / near.w, near.y / near.w, near.z / near.w}; - Vector3 farPoint = {far.x / far.w, far.y / far.w, far.z / far.w}; - + Vector3 nearPoint = { near.x/near.w, near.y/near.w, near.z/near.w}; + Vector3 farPoint = { far.x/far.w, far.y/far.w, far.z/far.w}; +#endif + // Calculate normalized direction vector Vector3 direction = VectorSubtract(farPoint, nearPoint); VectorNormalize(&direction); - + // Apply calculated vectors to ray ray.position = camera.position; ray.direction = direction; - + return ray; } // Returns the screen space position from a 3d world space position -Vector2 WorldToScreen(Vector3 position, Camera camera) -{ +Vector2 GetWorldToScreen(Vector3 position, Camera camera) +{ // Calculate projection matrix (from perspective instead of frustum - Matrix matProj = MatrixPerspective(45.0f, (float)((float)GetScreenWidth() / (float)GetScreenHeight()), 0.01f, 1000.0f); - + Matrix matProj = MatrixPerspective(camera.fovy, (double)GetScreenWidth()/(double)GetScreenHeight(), 0.01, 1000.0); + // Calculate view matrix from camera look at (and transpose it) Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); MatrixTranspose(&matView); - + // Convert world position vector to quaternion Quaternion worldPos = { position.x, position.y, position.z, 1.0f }; - + // Transform world position to view QuaternionTransform(&worldPos, matView); - + // Transform result to projection (clip space position) QuaternionTransform(&worldPos, matProj); - + // Calculate normalized device coordinates (inverted y) - Vector3 ndcPos = { worldPos.x / worldPos.w, -worldPos.y / worldPos.w, worldPos.z / worldPos.z }; - + 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)GetScreenWidth(), (ndcPos.y + 1.0f)/2.0f*(float)GetScreenHeight() }; - + return screenPosition; } @@ -969,7 +1185,6 @@ Matrix GetCameraMatrix(Camera camera) //---------------------------------------------------------------------------------- // Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions //---------------------------------------------------------------------------------- -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) // Detect if a key has been pressed once bool IsKeyPressed(int key) { @@ -1012,258 +1227,245 @@ int GetKeyPressed(void) return lastKeyPressed; } -// Detect if a mouse button has been pressed once -bool IsMouseButtonPressed(int button) +// Set a custom key to exit program +// NOTE: default exitKey is ESCAPE +void SetExitKey(int key) { - bool pressed = false; - - if ((currentMouseState[button] != previousMouseState[button]) && (currentMouseState[button] == 1)) pressed = true; - else pressed = false; - - return pressed; +#if !defined(PLATFORM_ANDROID) + exitKey = key; +#endif } -// Detect if a mouse button is being pressed -bool IsMouseButtonDown(int button) -{ - if (GetMouseButtonStatus(button) == 1) return true; - else return false; -} +// NOTE: Gamepad support not implemented in emscripten GLFW3 (PLATFORM_WEB) -// Detect if a mouse button has been released once -bool IsMouseButtonReleased(int button) +// Detect if a gamepad is available +bool IsGamepadAvailable(int gamepad) { - bool released = false; + bool result = false; - if ((currentMouseState[button] != previousMouseState[button]) && (currentMouseState[button] == 0)) released = true; - else released = false; +#if !defined(PLATFORM_ANDROID) + if ((gamepad < MAX_GAMEPADS) && gamepadReady[gamepad]) result = true; +#endif - return released; + return result; } -// Detect if a mouse button is NOT being pressed -bool IsMouseButtonUp(int button) +// Check gamepad name (if available) +bool IsGamepadName(int gamepad, const char *name) { - if (GetMouseButtonStatus(button) == 0) return true; - else return false; -} + bool result = false; -// Returns mouse position X -int GetMouseX(void) -{ - return (int)mousePosition.x; -} +#if !defined(PLATFORM_ANDROID) + const char *gamepadName = NULL; -// Returns mouse position Y -int GetMouseY(void) -{ - return (int)mousePosition.y; -} + if (gamepadReady[gamepad]) gamepadName = GetGamepadName(gamepad); + if ((name != NULL) && (gamepadName != NULL)) result = (strcmp(name, gamepadName) == 0); +#endif -// Returns mouse position XY -Vector2 GetMousePosition(void) -{ - return mousePosition; + return result; } -// Set mouse position XY -void SetMousePosition(Vector2 position) +// Return gamepad internal name id +const char *GetGamepadName(int gamepad) { - mousePosition = position; -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) - // NOTE: emscripten not implemented - glfwSetCursorPos(window, position.x, position.y); +#if defined(PLATFORM_DESKTOP) + if (gamepadReady[gamepad]) return glfwGetJoystickName(gamepad); + else return NULL; +#elif defined(PLATFORM_RPI) + if (gamepadReady[gamepad]) ioctl(gamepadStream[gamepad], JSIOCGNAME(64), &gamepadName); + + return gamepadName; +#else + return NULL; #endif } -// Returns mouse wheel movement Y -int GetMouseWheelMove(void) +// Return gamepad axis count +int GetGamepadAxisCount(int gamepad) { -#if defined(PLATFORM_WEB) - return previousMouseWheelY/100; -#else - return previousMouseWheelY; +#if defined(PLATFORM_RPI) + int axisCount = 0; + if (gamepadReady[gamepad]) ioctl(gamepadStream[gamepad], JSIOCGAXES, &axisCount); + gamepadAxisCount = axisCount; #endif + return gamepadAxisCount; } -// Hide mouse cursor -void HideCursor() +// Return axis movement vector for a gamepad +float GetGamepadAxisMovement(int gamepad, int axis) { -#if defined(PLATFORM_DESKTOP) - #ifdef __linux - XColor Col; - const char Nil[] = {0}; + float value = 0; - Pixmap Pix = XCreateBitmapFromData(glfwGetX11Display(), glfwGetX11Window(window), Nil, 1, 1); - Cursor Cur = XCreatePixmapCursor(glfwGetX11Display(), Pix, Pix, &Col, &Col, 0, 0); - - XDefineCursor(glfwGetX11Display(), glfwGetX11Window(window), Cur); - XFreeCursor(glfwGetX11Display(), Cur); - #else - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); - #endif +#if !defined(PLATFORM_ANDROID) + if ((gamepad < MAX_GAMEPADS) && gamepadReady[gamepad] && (axis < MAX_GAMEPAD_AXIS)) value = gamepadAxisState[gamepad][axis]; #endif - cursorHidden = true; + + return value; } -// Show mouse cursor -void ShowCursor() +// Detect if a gamepad button has been pressed once +bool IsGamepadButtonPressed(int gamepad, int button) { -#if defined(PLATFORM_DESKTOP) - #ifdef __linux - XUndefineCursor(glfwGetX11Display(), glfwGetX11Window(window)); - #else - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); - #endif + bool pressed = false; + +#if !defined(PLATFORM_ANDROID) + if ((gamepad < MAX_GAMEPADS) && gamepadReady[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (currentGamepadState[gamepad][button] != previousGamepadState[gamepad][button]) && + (currentGamepadState[gamepad][button] == 1)) pressed = true; #endif - cursorHidden = false; + + return pressed; } -// Disable mouse cursor -void DisableCursor() +// Detect if a gamepad button is being pressed +bool IsGamepadButtonDown(int gamepad, int button) { -#if defined(PLATFORM_DESKTOP) - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + bool result = false; + +#if !defined(PLATFORM_ANDROID) + if ((gamepad < MAX_GAMEPADS) && gamepadReady[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (currentGamepadState[gamepad][button] == 1)) result = true; #endif - cursorHidden = true; + + return result; } -// Enable mouse cursor -void EnableCursor() +// Detect if a gamepad button has NOT been pressed once +bool IsGamepadButtonReleased(int gamepad, int button) { -#if defined(PLATFORM_DESKTOP) - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + bool released = false; + +#if !defined(PLATFORM_ANDROID) + if ((gamepad < MAX_GAMEPADS) && gamepadReady[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (currentGamepadState[gamepad][button] != previousGamepadState[gamepad][button]) && + (currentGamepadState[gamepad][button] == 0)) released = true; #endif - cursorHidden = false; -} -// Check if mouse cursor is hidden -bool IsCursorHidden() -{ - return cursorHidden; + return released; } -// NOTE: Gamepad support not implemented in emscripten GLFW3 (PLATFORM_WEB) - -// Detect if a gamepad is available -bool IsGamepadAvailable(int gamepad) +// Detect if a mouse button is NOT being pressed +bool IsGamepadButtonUp(int gamepad, int button) { bool result = false; - -#if defined(PLATFORM_RPI) - if (gamepadReady && (gamepad == 0)) result = true; -#else - if (glfwJoystickPresent(gamepad) == 1) result = true; + +#if !defined(PLATFORM_ANDROID) + if ((gamepad < MAX_GAMEPADS) && gamepadReady[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && + (currentGamepadState[gamepad][button] == 0)) result = true; #endif return result; } -// Return axis movement vector for a gamepad -Vector2 GetGamepadMovement(int gamepad) +// Get the last gamepad button pressed +int GetGamepadButtonPressed(void) { - Vector2 vec = { 0, 0 }; - - const float *axes; - int axisCount = 0; - -#if defined(PLATFORM_RPI) - // TODO: Get gamepad axis information - // Use gamepadAxisX, gamepadAxisY -#else - axes = glfwGetJoystickAxes(gamepad, &axisCount); -#endif - - if (axisCount >= 2) - { - vec.x = axes[0]; // Left joystick X - vec.y = axes[1]; // Left joystick Y - - //vec.x = axes[2]; // Right joystick X - //vec.x = axes[3]; // Right joystick Y - } - - return vec; + return lastGamepadButtonPressed; } -// Detect if a gamepad button has been pressed once -bool IsGamepadButtonPressed(int gamepad, int button) +// Detect if a mouse button has been pressed once +bool IsMouseButtonPressed(int button) { bool pressed = false; - currentGamepadState[button] = IsGamepadButtonDown(gamepad, button); - - if (currentGamepadState[button] != previousGamepadState[button]) - { - if (currentGamepadState[button]) pressed = true; - previousGamepadState[button] = currentGamepadState[button]; - } - else pressed = false; +#if defined(PLATFORM_ANDROID) + if (IsGestureDetected(GESTURE_TAP)) pressed = true; +#else + if ((currentMouseState[button] != previousMouseState[button]) && (currentMouseState[button] == 1)) pressed = true; +#endif return pressed; } -// Detect if a gamepad button is being pressed -bool IsGamepadButtonDown(int gamepad, int button) +// Detect if a mouse button is being pressed +bool IsMouseButtonDown(int button) { - bool result = false; - -#if defined(PLATFORM_RPI) - // Get gamepad buttons information - if ((gamepad == 0) && (gamepadButtons[button] == 1)) result = true; - else result = false; -#else - const unsigned char *buttons; - int buttonsCount; - - buttons = glfwGetJoystickButtons(gamepad, &buttonsCount); + bool down = false; - if ((buttons != NULL) && (buttons[button] == GLFW_PRESS)) result = true; - else result = false; +#if defined(PLATFORM_ANDROID) + if (IsGestureDetected(GESTURE_HOLD)) down = true; +#else + if (GetMouseButtonStatus(button) == 1) down = true; #endif - - return result; + + return down; } -// Detect if a gamepad button has NOT been pressed once -bool IsGamepadButtonReleased(int gamepad, int button) +// Detect if a mouse button has been released once +bool IsMouseButtonReleased(int button) { bool released = false; - currentGamepadState[button] = IsGamepadButtonUp(gamepad, button); - - if (currentGamepadState[button] != previousGamepadState[button]) - { - if (currentGamepadState[button]) released = true; - previousGamepadState[button] = currentGamepadState[button]; - } - else released = false; +#if !defined(PLATFORM_ANDROID) + if ((currentMouseState[button] != previousMouseState[button]) && (currentMouseState[button] == 0)) released = true; +#endif return released; } // Detect if a mouse button is NOT being pressed -bool IsGamepadButtonUp(int gamepad, int button) +bool IsMouseButtonUp(int button) { - bool result = false; + bool up = false; -#if defined(PLATFORM_RPI) - // Get gamepad buttons information - if ((gamepad == 0) && (gamepadButtons[button] == 0)) result = true; - else result = false; +#if !defined(PLATFORM_ANDROID) + if (GetMouseButtonStatus(button) == 0) up = true; +#endif + + return up; +} + +// Returns mouse position X +int GetMouseX(void) +{ +#if defined(PLATFORM_ANDROID) + return (int)touchPosition[0].x; #else - const unsigned char *buttons; - int buttonsCount; + return (int)mousePosition.x; +#endif +} - buttons = glfwGetJoystickButtons(gamepad, &buttonsCount); +// Returns mouse position Y +int GetMouseY(void) +{ +#if defined(PLATFORM_ANDROID) + return (int)touchPosition[0].x; +#else + return (int)mousePosition.y; +#endif +} - if ((buttons != NULL) && (buttons[button] == GLFW_RELEASE)) result = true; - else result = false; +// Returns mouse position XY +Vector2 GetMousePosition(void) +{ +#if defined(PLATFORM_ANDROID) + return GetTouchPosition(0); +#else + return mousePosition; #endif +} - return result; +// Set mouse position XY +void SetMousePosition(Vector2 position) +{ + mousePosition = position; +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + // NOTE: emscripten not implemented + glfwSetCursorPos(window, position.x, position.y); +#endif +} + +// Returns mouse wheel movement Y +int GetMouseWheelMove(void) +{ +#if defined(PLATFORM_ANDROID) + return 0; +#elif defined(PLATFORM_WEB) + return previousMouseWheelY/100; +#else + return previousMouseWheelY; +#endif } -#endif //defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) // Returns touch position X int GetTouchX(void) @@ -1290,7 +1492,7 @@ int GetTouchY(void) Vector2 GetTouchPosition(int index) { Vector2 position = { -1.0f, -1.0f }; - + #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) if (index < MAX_TOUCH_POINTS) position = touchPosition[index]; else TraceLog(WARNING, "Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS); @@ -1313,37 +1515,6 @@ Vector2 GetTouchPosition(int index) return position; } -#if defined(PLATFORM_ANDROID) -// Detect if a button has been pressed once -bool IsButtonPressed(int button) -{ - bool pressed = false; - - if ((currentButtonState[button] != previousButtonState[button]) && (currentButtonState[button] == 0)) pressed = true; - else pressed = false; - - return pressed; -} - -// Detect if a button is being pressed (button held down) -bool IsButtonDown(int button) -{ - if (currentButtonState[button] == 0) return true; - else return false; -} - -// Detect if a button has been released once -bool IsButtonReleased(int button) -{ - bool released = false; - - if ((currentButtonState[button] != previousButtonState[button]) && (currentButtonState[button] == 1)) released = true; - else released = false; - - return released; -} -#endif - //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- @@ -1351,7 +1522,7 @@ bool IsButtonReleased(int button) // 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 -static void InitDisplay(int width, int height) +static void InitGraphicsDevice(int width, int height) { screenWidth = width; // User desired width screenHeight = height; // User desired height @@ -1378,76 +1549,112 @@ static void InitDisplay(int width, int height) // Screen size security check if (screenWidth <= 0) screenWidth = displayWidth; if (screenHeight <= 0) screenHeight = displayHeight; -#elif defined(PLATFORM_WEB) +#endif // defined(PLATFORM_DESKTOP) + +#if defined(PLATFORM_WEB) displayWidth = screenWidth; displayHeight = screenHeight; -#endif +#endif // defined(PLATFORM_WEB) + + glfwDefaultWindowHints(); // Set default windows hints - glfwDefaultWindowHints(); // Set default windows hints + // Check some Window creation flags + if (configFlags & FLAG_WINDOW_RESIZABLE) glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); // Resizable window + else glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); // Avoid window being resizable - glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); // Avoid window being resizable - //glfwWindowHint(GLFW_DECORATED, GL_TRUE); // Border and buttons on Window - //glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits - //glfwWindowHint(GLFW_DEPTH_BITS, 16); // Depthbuffer bits (24 by default) - //glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window + if (configFlags & FLAG_WINDOW_DECORATED) glfwWindowHint(GLFW_DECORATED, GL_TRUE); // Border and buttons on Window + + if (configFlags & FLAG_WINDOW_TRANSPARENT) + { + // TODO: Enable transparent window (not ready yet on GLFW 3.2) + } + + if (configFlags & FLAG_MSAA_4X_HINT) + { + glfwWindowHint(GLFW_SAMPLES, 4); // Enables multisampling x4 (MSAA), default is 0 + TraceLog(INFO, "Trying to enable MSAA x4"); + } + + //glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits + //glfwWindowHint(GLFW_DEPTH_BITS, 16); // Depthbuffer bits (24 by default) + //glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // Default OpenGL API to use. Alternative: GLFW_OPENGL_ES_API - //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers + //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers // NOTE: When asking for an OpenGL context version, most drivers provide highest supported version // with forward compatibility to older OpenGL versions. - // For example, if using OpenGL 1.1, driver can provide a 3.3 context fordward compatible. + // For example, if using OpenGL 1.1, driver can provide a 4.3 context forward compatible. - // Check selection OpenGL version (not initialized yet!) - if (rlGetVersion() == OPENGL_33) + // Check selection OpenGL version + if (rlGetVersion() == 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() == OPENGL_33) { - if (configFlags & FLAG_MSAA_4X_HINT) - { - glfwWindowHint(GLFW_SAMPLES, 4); // Enables multisampling x4 (MSAA), default is 0 - TraceLog(INFO, "Trying to enable MSAA x4"); - } - 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! // Other values: GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE +#ifdef __APPLE__ + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // OSX Requires +#else glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_FALSE); // Fordward Compatibility Hint: Only 3.3 and above! +#endif + //glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); } if (fullscreen) { - // At this point we need to manage render size vs screen size - // NOTE: This function uses and modifies global module variables: - // screenWidth/screenHeight - renderWidth/renderHeight - downscaleView - SetupFramebufferSize(displayWidth, displayHeight); - - // TODO: SetupFramebufferSize() does not consider properly display video modes. - // It setups a renderWidth/renderHeight with black bars that could not match a valid video mode, - // and so, framebuffer is not scaled properly to some monitors. - - int count; + // Obtain recommended displayWidth/displayHeight from a valid videomode for the monitor + int count; const GLFWvidmode *modes = glfwGetVideoModes(glfwGetPrimaryMonitor(), &count); - + + // Get closest videomode to desired screenWidth/screenHeight for (int i = 0; i < count; i++) { - // TODO: Check modes[i]->width; - // TODO: Check modes[i]->height; + if (modes[i].width >= screenWidth) + { + if (modes[i].height >= screenHeight) + { + displayWidth = modes[i].width; + displayHeight = modes[i].height; + break; + } + } } - window = glfwCreateWindow(screenWidth, screenHeight, windowTitle, glfwGetPrimaryMonitor(), NULL); + TraceLog(WARNING, "Closest fullscreen videomode: %i x %i", displayWidth, displayHeight); + + // NOTE: ISSUE: Closest videomode could not match monitor aspect-ratio, for example, + // for a desired screen size of 800x450 (16:9), closest supported videomode is 800x600 (4:3), + // framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched + // by the sides to fit all monitor space... + + // At this point we need to manage render size vs screen size + // NOTE: This function uses and modifies global module variables: + // screenWidth/screenHeight - renderWidth/renderHeight - downscaleView + SetupFramebufferSize(displayWidth, displayHeight); + + window = glfwCreateWindow(displayWidth, displayHeight, windowTitle, glfwGetPrimaryMonitor(), NULL); + + // NOTE: Full-screen change, not working properly... + //glfwSetWindowMonitor(window, glfwGetPrimaryMonitor(), 0, 0, screenWidth, screenHeight, GLFW_DONT_CARE); } else { // No-fullscreen window creation window = glfwCreateWindow(screenWidth, screenHeight, windowTitle, NULL, NULL); - + #if defined(PLATFORM_DESKTOP) // Center window on screen int windowPosX = displayWidth/2 - screenWidth/2; int windowPosY = displayHeight/2 - screenHeight/2; - + if (windowPosX < 0) windowPosX = 0; if (windowPosY < 0) windowPosY = 0; - + glfwSetWindowPos(window, windowPosX, windowPosY); #endif renderWidth = screenWidth; @@ -1470,11 +1677,11 @@ static void InitDisplay(int width, int height) TraceLog(INFO, "Viewport offsets: %i, %i", renderOffsetX, renderOffsetY); } - glfwSetWindowSizeCallback(window, WindowSizeCallback); + glfwSetWindowSizeCallback(window, WindowSizeCallback); // NOTE: Resizing not allowed by default! glfwSetCursorEnterCallback(window, CursorEnterCallback); glfwSetKeyCallback(window, KeyCallback); glfwSetMouseButtonCallback(window, MouseButtonCallback); - glfwSetCursorPosCallback(window, MouseCursorPosCallback); // Track mouse position changes + glfwSetCursorPosCallback(window, MouseCursorPosCallback); // Track mouse position changes glfwSetCharCallback(window, CharCallback); glfwSetScrollCallback(window, ScrollCallback); glfwSetWindowIconifyCallback(window, WindowIconifyCallback); @@ -1483,53 +1690,27 @@ static void InitDisplay(int width, int height) #endif glfwMakeContextCurrent(window); + + // Try to disable GPU V-Sync by default, set framerate using SetTargetFPS() + // NOTE: V-Sync can be enabled by graphic driver configuration + glfwSwapInterval(0); #if defined(PLATFORM_DESKTOP) - // Extensions initialization for OpenGL 3.3 - if (rlGetVersion() == OPENGL_33) - { - #if defined(GLEW_EXTENSIONS_LOADER) - // Initialize extensions using GLEW - glewExperimental = 1; // Needed for core profile - GLenum error = glewInit(); - - if (error != GLEW_OK) TraceLog(ERROR, "Failed to initialize GLEW - Error Code: %s\n", glewGetErrorString(error)); - - if (glewIsSupported("GL_VERSION_3_3")) TraceLog(INFO, "OpenGL 3.3 Core profile supported"); - else TraceLog(ERROR, "OpenGL 3.3 Core profile not supported"); - - // With GLEW, we can check if an extension has been loaded in two ways: - //if (GLEW_ARB_vertex_array_object) { } - //if (glewIsSupported("GL_ARB_vertex_array_object")) { } - - // NOTE: GLEW is a big library that loads ALL extensions, we can use some alternative to load only required ones - // Alternatives: glLoadGen, glad, libepoxy - #elif defined(GLAD_EXTENSIONS_LOADER) - // NOTE: glad is generated and contains only required OpenGL version and Core extensions - //if (!gladLoadGL()) TraceLog(ERROR, "Failed to initialize glad\n"); - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) TraceLog(ERROR, "Failed to initialize glad\n"); // No GLFW3 in this module... - - if (GLAD_GL_VERSION_3_3) TraceLog(INFO, "OpenGL 3.3 Core profile supported"); - else TraceLog(ERROR, "OpenGL 3.3 Core profile not supported"); - - // With GLAD, we can check if an extension is supported using the GLAD_GL_xxx booleans - //if (GLAD_GL_ARB_vertex_array_object) // Use GL_ARB_vertex_array_object - #endif - } + // Load OpenGL 3.3 extensions + // NOTE: GLFW loader function is passed as parameter + rlglLoadExtensions(glfwGetProcAddress); #endif - - // Enables GPU v-sync, so frames are not limited to screen refresh rate (60Hz -> 60 FPS) - // If not set, swap interval uses GPU v-sync configuration - // Framerate can be setup using SetTargetFPS() + + // 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 (configFlags & FLAG_VSYNC_HINT) { glfwSwapInterval(1); TraceLog(INFO, "Trying to enable VSYNC"); } +#endif // defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) - //glfwGetFramebufferSize(window, &renderWidth, &renderHeight); // Get framebuffer size of current window - -#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) fullscreen = true; // Screen size security check @@ -1549,13 +1730,13 @@ static void InitDisplay(int width, int height) EGLint samples = 0; EGLint sampleBuffer = 0; - if (configFlags & FLAG_MSAA_4X_HINT) + if (configFlags & FLAG_MSAA_4X_HINT) { samples = 4; sampleBuffer = 1; TraceLog(INFO, "Trying to enable MSAA x4"); } - + const EGLint framebufferAttribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // Type of context support -> Required on RPI? @@ -1614,8 +1795,9 @@ static void InitDisplay(int width, int height) //ANativeWindow_setBuffersGeometry(app->window, 0, 0, displayFormat); // Force use of native display size surface = eglCreateWindowSurface(display, config, app->window, NULL); +#endif // defined(PLATFORM_ANDROID) -#elif defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) graphics_get_display_size(0, &displayWidth, &displayHeight); // At this point we need to manage render size vs screen size @@ -1653,7 +1835,7 @@ static void InitDisplay(int width, int height) surface = eglCreateWindowSurface(display, config, &nativeWindow, NULL); //--------------------------------------------------------------------------------- -#endif +#endif // defined(PLATFORM_RPI) // There must be at least one frame displayed before the buffers are swapped //eglSwapInterval(display, 1); @@ -1673,23 +1855,411 @@ static void InitDisplay(int width, int height) TraceLog(INFO, "Screen size: %i x %i", screenWidth, screenHeight); TraceLog(INFO, "Viewport offsets: %i, %i", renderOffsetX, renderOffsetY); } +#endif // defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + + // Initialize OpenGL context (states and resources) + // NOTE: screenWidth and screenHeight not used, just stored as globals + rlglInit(screenWidth, screenHeight); + + // Setup default viewport + SetupViewport(); + + // Initialize internal projection and modelview matrices + // NOTE: Default to orthographic projection mode with top-left corner at (0,0) + rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix + rlLoadIdentity(); // Reset current matrix (PROJECTION) + rlOrtho(0, renderWidth - renderOffsetX, renderHeight - renderOffsetY, 0, 0.0f, 1.0f); + rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix + rlLoadIdentity(); // Reset current matrix (MODELVIEW) + + ClearBackground(RAYWHITE); // Default background color for raylib games :P + +#if defined(PLATFORM_ANDROID) + windowReady = true; // IMPORTANT! +#endif +} + +// Set viewport parameters +static void SetupViewport(void) +{ +#ifdef __APPLE__ + // Get framebuffer size of current window + // NOTE: Required to handle HighDPI display correctly on OSX because framebuffer + // is automatically reasized to adapt to new DPI. + // When OS does that, it can be detected using GLFW3 callback: glfwSetFramebufferSizeCallback() + int fbWidth, fbHeight; + glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + rlViewport(renderOffsetX/2, renderOffsetY/2, fbWidth - renderOffsetX, fbHeight - renderOffsetY); +#else + // Initialize screen viewport (area of the screen that you will actually draw to) + // NOTE: Viewport must be recalculated if screen is resized + rlViewport(renderOffsetX/2, renderOffsetY/2, renderWidth - renderOffsetX, renderHeight - renderOffsetY); #endif } -// Initialize OpenGL graphics -static void InitGraphics(void) +// Compute framebuffer size relative to screen size and display size +// NOTE: Global variables renderWidth/renderHeight and renderOffsetX/renderOffsetY can be modified +static void SetupFramebufferSize(int displayWidth, int displayHeight) { - rlglInit(); // Init rlgl + // Calculate renderWidth and renderHeight, we have the display size (input params) and the desired screen size (global var) + if ((screenWidth > displayWidth) || (screenHeight > displayHeight)) + { + TraceLog(WARNING, "DOWNSCALING: Required screen size (%ix%i) is bigger than display size (%ix%i)", screenWidth, screenHeight, displayWidth, displayHeight); - rlglInitGraphics(renderOffsetX, renderOffsetY, renderWidth, renderHeight); // Init graphics (OpenGL stuff) + // Downscaling to fit display with border-bars + float widthRatio = (float)displayWidth/(float)screenWidth; + float heightRatio = (float)displayHeight/(float)screenHeight; - ClearBackground(RAYWHITE); // Default background color for raylib games :P + if (widthRatio <= heightRatio) + { + renderWidth = displayWidth; + renderHeight = (int)round((float)screenHeight*widthRatio); + renderOffsetX = 0; + renderOffsetY = (displayHeight - renderHeight); + } + else + { + renderWidth = (int)round((float)screenWidth*heightRatio); + renderHeight = displayHeight; + renderOffsetX = (displayWidth - renderWidth); + renderOffsetY = 0; + } + + // NOTE: downscale matrix required! + float scaleRatio = (float)renderWidth/(float)screenWidth; + + downscaleView = MatrixScale(scaleRatio, scaleRatio, scaleRatio); + + // NOTE: We render to full display resolution! + // We just need to calculate above parameters for downscale matrix and offsets + renderWidth = displayWidth; + renderHeight = displayHeight; + + TraceLog(WARNING, "Downscale matrix generated, content will be rendered at: %i x %i", renderWidth, renderHeight); + } + else if ((screenWidth < displayWidth) || (screenHeight < displayHeight)) + { + // Required screen size is smaller than display size + TraceLog(INFO, "UPSCALING: Required screen size: %i x %i -> Display size: %i x %i", screenWidth, screenHeight, displayWidth, displayHeight); + + // Upscaling to fit display with border-bars + float displayRatio = (float)displayWidth/(float)displayHeight; + float screenRatio = (float)screenWidth/(float)screenHeight; + + if (displayRatio <= screenRatio) + { + renderWidth = screenWidth; + renderHeight = (int)round((float)screenWidth/displayRatio); + renderOffsetX = 0; + renderOffsetY = (renderHeight - screenHeight); + } + else + { + renderWidth = (int)round((float)screenHeight*displayRatio); + renderHeight = screenHeight; + renderOffsetX = (renderWidth - screenWidth); + renderOffsetY = 0; + } + } + else // screen == display + { + renderWidth = screenWidth; + renderHeight = screenHeight; + renderOffsetX = 0; + renderOffsetY = 0; + } +} + +// Initialize hi-resolution timer +static void InitTimer(void) +{ + srand(time(NULL)); // Initialize random seed + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + struct timespec now; + + if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success + { + baseTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; + } + else TraceLog(WARNING, "No hi-resolution timer available"); +#endif + + previousTime = GetTime(); // Get time as double +} + +// Get current time measure (in seconds) since InitTimer() +static double GetTime(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + return glfwGetTime(); +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t time = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec; + + return (double)(time - baseTime)*1e-9; +#endif +} + +// Wait for some milliseconds (stop program execution) +static void Wait(float ms) +{ +#define SUPPORT_BUSY_WAIT_LOOP +#if defined(SUPPORT_BUSY_WAIT_LOOP) + double prevTime = GetTime(); + double nextTime = 0.0; + + // Busy wait loop + while ((nextTime - prevTime) < ms/1000.0f) nextTime = GetTime(); +#else + #if defined _WIN32 + Sleep(ms); + #elif defined __linux || defined(PLATFORM_WEB) + struct timespec req = { 0 }; + time_t sec = (int)(ms/1000.0f); + ms -= (sec*1000); + req.tv_sec = sec; + req.tv_nsec = ms*1000000L; + + // NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated. + while (nanosleep(&req, &req) == -1) continue; + #elif defined __APPLE__ + usleep(ms*1000.0f); + #endif +#endif +} + +// Get one key state +static bool GetKeyStatus(int key) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + return glfwGetKey(window, key); +#elif defined(PLATFORM_ANDROID) + // NOTE: Android supports up to 260 keys + if (key < 0 || key > 260) return false; + else return currentKeyState[key]; +#elif defined(PLATFORM_RPI) + // NOTE: Keys states are filled in PollInputEvents() + if (key < 0 || key > 511) return false; + else return currentKeyState[key]; +#endif +} + +// Get one mouse button state +static bool GetMouseButtonStatus(int button) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + return glfwGetMouseButton(window, button); +#elif defined(PLATFORM_ANDROID) + // TODO: Check for virtual mouse? + return false; +#elif defined(PLATFORM_RPI) + // NOTE: Mouse buttons states are filled in PollInputEvents() + return currentMouseState[button]; +#endif +} + +// Poll (store) all input events +static void PollInputEvents(void) +{ + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is just called on an event, not every frame + UpdateGestures(); + + // Reset last key pressed registered + lastKeyPressed = -1; + +#if !defined(PLATFORM_RPI) + // Reset last gamepad button/axis registered state + lastGamepadButtonPressed = -1; + gamepadAxisCount = 0; +#endif + +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + // Mouse input polling + double mouseX; + double mouseY; + + glfwGetCursorPos(window, &mouseX, &mouseY); + + mousePosition.x = (float)mouseX; + mousePosition.y = (float)mouseY; + + // Keyboard input polling (automatically managed by GLFW3 through callback) + + // Register previous keys states + for (int i = 0; i < 512; i++) previousKeyState[i] = currentKeyState[i]; + + // Register previous mouse states + for (int i = 0; i < 3; i++) previousMouseState[i] = currentMouseState[i]; + + previousMouseWheelY = currentMouseWheelY; + currentMouseWheelY = 0; +#endif + +#if defined(PLATFORM_DESKTOP) + // Check if gamepads are ready + // NOTE: We do it here in case of disconection + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (glfwJoystickPresent(i)) gamepadReady[i] = true; + else gamepadReady[i] = false; + } + + // Register gamepads buttons events + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (gamepadReady[i]) // Check if gamepad is available + { + // Register previous gamepad states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) previousGamepadState[i][k] = currentGamepadState[i][k]; + + // Get current gamepad state + // NOTE: There is no callback available, so we get it manually + const unsigned char *buttons; + int buttonsCount; + + buttons = glfwGetJoystickButtons(i, &buttonsCount); + + for (int k = 0; (buttons != NULL) && (k < buttonsCount) && (buttonsCount < MAX_GAMEPAD_BUTTONS); k++) + { + if (buttons[k] == GLFW_PRESS) + { + currentGamepadState[i][k] = 1; + lastGamepadButtonPressed = k; + } + else currentGamepadState[i][k] = 0; + } + + // Get current axis state + const float *axes; + int axisCount = 0; + + axes = glfwGetJoystickAxes(i, &axisCount); + + for (int k = 0; (axes != NULL) && (k < axisCount) && (k < MAX_GAMEPAD_AXIS); k++) + { + gamepadAxisState[i][k] = axes[k]; + } + + gamepadAxisCount = axisCount; + } + } + + glfwPollEvents(); // Register keyboard/mouse events (callbacks)... and window events! +#endif + +// Gamepad support using emscripten API +// NOTE: GLFW3 joystick functionality not available in web +#if defined(PLATFORM_WEB) + // Get number of gamepads connected + int 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++) previousGamepadState[i][k] = currentGamepadState[i][k]; + + EmscriptenGamepadEvent gamepadState; + + 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++) + { + if (gamepadState.digitalButton[j] == 1) + { + currentGamepadState[i][j] = 1; + lastGamepadButtonPressed = j; + } + else currentGamepadState[i][j] = 0; + + //printf("Gamepad %d, button %d: Digital: %d, Analog: %g\n", 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_AXIS); j++) + { + gamepadAxisState[i][j] = gamepadState.axis[j]; + } + + gamepadAxisCount = gamepadState.numAxes; + } + } +#endif #if defined(PLATFORM_ANDROID) - windowReady = true; // IMPORTANT! + // Register previous keys states + // NOTE: Android supports up to 260 keys + for (int i = 0; i < 260; i++) previousKeyState[i] = currentKeyState[i]; + + // Poll Events (registered events) + // NOTE: Activity is paused if not enabled (appEnabled) + while ((ident = ALooper_pollAll(appEnabled ? 0 : -1, NULL, &events,(void**)&source)) >= 0) + { + // Process this event + if (source != NULL) source->process(app, source); + + // NOTE: Never close window, native activity is controlled by the system! + if (app->destroyRequested != 0) + { + //TraceLog(INFO, "Closing Window..."); + //windowShouldClose = true; + //ANativeActivity_finish(app->activity); + } + } +#endif + +#if defined(PLATFORM_RPI) + // NOTE: Mouse input events polling is done asynchonously in another pthread - MouseThread() + + // NOTE: Keyboard reading could be done using input_event(s) reading or just read from stdin, + // we use method 2 (stdin) but maybe in a future we should change to method 1... + ProcessKeyboard(); + + // NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread() +#endif +} + +// Copy back buffer to front buffers +static void SwapBuffers(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + glfwSwapBuffers(window); +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + eglSwapBuffers(display, surface); #endif } +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) +// Takes a screenshot and saves it in the same folder as executable +static void TakeScreenshot(void) +{ + static int shotNum = 0; // Screenshot number, increments every screenshot take during program execution + char buffer[20]; // Buffer to store file name + + unsigned char *imgData = rlglReadScreenPixels(renderWidth, renderHeight); + + sprintf(buffer, "screenshot%03i.png", shotNum); + + // Save image as PNG + SavePNG(buffer, imgData, renderWidth, renderHeight, 4); + + free(imgData); + + shotNum++; + + TraceLog(INFO, "[%s] Screenshot taken!", buffer); +} +#endif + #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) // GLFW3 Error Callback, runs on GLFW3 error static void ErrorCallback(int error, const char *description) @@ -1713,47 +2283,45 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i // NOTE: Before closing window, while loop must be left! } #if defined(PLATFORM_DESKTOP) - else if (key == GLFW_KEY_F12 && action == GLFW_PRESS) + else if (key == GLFW_KEY_F12 && action == GLFW_PRESS) TakeScreenshot(); +#endif + else { - TakeScreenshot(); + currentKeyState[key] = action; + if (action == GLFW_PRESS) lastKeyPressed = key; } -#endif - else currentKeyState[key] = action; - - // TODO: Review (and remove) this HACK for GuiTextBox, to deteck back key - if ((key == 259) && (action == GLFW_PRESS)) lastKeyPressed = 3; } // GLFW3 Mouse Button Callback, runs on mouse button pressed static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods) { currentMouseState[button] = action; - + #define ENABLE_MOUSE_GESTURES #if defined(ENABLE_MOUSE_GESTURES) // Process mouse events as touches to be able to use mouse-gestures GestureEvent gestureEvent; - + // Register touch actions if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) gestureEvent.touchAction = TOUCH_DOWN; else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) gestureEvent.touchAction = TOUCH_UP; - + // NOTE: TOUCH_MOVE event is registered in MouseCursorPosCallback() - + // Assign a pointer ID gestureEvent.pointerId[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 screenWidth and screenHeight - gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].x /= (float)GetScreenWidth(); gestureEvent.position[0].y /= (float)GetScreenHeight(); - - // Gesture data is sent to gestures system for processing + + // Gesture data is sent to gestures system for processing ProcessGestureEvent(gestureEvent); #endif } @@ -1768,14 +2336,19 @@ static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) gestureEvent.touchAction = TOUCH_MOVE; + // Assign a pointer ID + gestureEvent.pointerId[0] = 0; + // Register touch points count gestureEvent.pointCount = 1; - + // Register touch points position, only one point registered gestureEvent.position[0] = (Vector2){ (float)x, (float)y }; - + + touchPosition[0] = gestureEvent.position[0]; + // Normalize gestureEvent.position[0] for screenWidth and screenHeight - gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].x /= (float)GetScreenWidth(); gestureEvent.position[0].y /= (float)GetScreenHeight(); // Gesture data is sent to gestures system for processing @@ -1799,19 +2372,25 @@ static void CursorEnterCallback(GLFWwindow *window, int enter) } // GLFW3 WindowSize Callback, runs when window is resized +// NOTE: Window resizing not allowed by default static void WindowSizeCallback(GLFWwindow *window, int width, int height) { - // If window is resized, graphics device is re-initialized (but only ortho mode) - rlglInitGraphics(renderOffsetX, renderOffsetY, renderWidth, renderHeight); + // If window is resized, viewport and projection matrix needs to be re-calculated + rlViewport(0, 0, width, height); // Set viewport width and height + rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix + rlLoadIdentity(); // Reset current matrix (PROJECTION) + rlOrtho(0, width, height, 0, 0.0f, 1.0f); // Orthographic projection mode with top-left corner at (0,0) + rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix + rlLoadIdentity(); // Reset current matrix (MODELVIEW) + rlClearScreenBuffers(); // Clear screen buffers (color and depth) // Window size must be updated to be used on 3D mode to get new aspect ratio (Begin3dMode()) - //screenWidth = width; - //screenHeight = height; - - // TODO: Update render size? + screenWidth = width; + screenHeight = height; + renderWidth = width; + renderHeight = height; - // Background must be also re-cleared - ClearBackground(RAYWHITE); + // NOTE: Postprocessing texture is not scaled to new size } // GLFW3 WindowIconify Callback, runs when window is minimized/restored @@ -1829,9 +2408,9 @@ static void WindowIconifyCallback(GLFWwindow* window, int iconified) static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) { ClearDroppedFiles(); - + dropFilesPath = (char **)malloc(sizeof(char *)*count); - + for (int i = 0; i < count; i++) { dropFilesPath[i] = (char *)malloc(sizeof(char)*256); // Max path length set to 256 char @@ -1878,25 +2457,24 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) } else { - // Init device display (monitor, LCD, ...) - InitDisplay(screenWidth, screenHeight); - - // Init OpenGL graphics - InitGraphics(); + // Init graphics device (display device and OpenGL context) + InitGraphicsDevice(screenWidth, screenHeight); - // Load default font for convenience + #if defined(LOAD_DEFAULT_FONT) + // Load default font // NOTE: External function (defined in module: text) LoadDefaultFont(); - + #endif + // TODO: GPU assets reload in case of lost focus (lost context) // NOTE: This problem has been solved just unbinding and rebinding context from display - /* + /* if (assetsReloadRequired) { for (int i = 0; i < assetsCount; i++) { // TODO: Unload old asset if required - + // Load texture again to pointed texture (*textureAsset + i) = LoadTexture(assetPath[i]); } @@ -1939,7 +2517,7 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) // NOTE 2: In some cases (too many context loaded), OS could unload context automatically... :( eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(display, surface); - + contextRebindRequired = true; TraceLog(INFO, "APP_CMD_TERM_WINDOW"); @@ -1976,7 +2554,7 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) { //http://developer.android.com/ndk/reference/index.html - + int type = AInputEvent_getType(event); if (type == AINPUT_EVENT_TYPE_MOTION) @@ -1984,7 +2562,7 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) // Get first touch position touchPosition[0].x = AMotionEvent_getX(event, 0); touchPosition[0].y = AMotionEvent_getY(event, 0); - + // Get second touch position touchPosition[1].x = AMotionEvent_getX(event, 1); touchPosition[1].y = AMotionEvent_getY(event, 1); @@ -1993,9 +2571,15 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) { int32_t keycode = AKeyEvent_getKeyCode(event); //int32_t AKeyEvent_getMetaState(event); - + // Save current button and its state - currentButtonState[keycode] = AKeyEvent_getAction(event); // Down = 0, Up = 1 + // NOTE: Android key action is 0 for down and 1 for up + if (AKeyEvent_getAction(event) == 0) + { + currentKeyState[keycode] = 1; // Key down + lastKeyPressed = keycode; + } + else currentKeyState[keycode] = 0; // Key up if (keycode == AKEYCODE_POWER) { @@ -2005,7 +2589,7 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) // NOTE: AndroidManifest.xml must have <activity android:configChanges="orientation|keyboardHidden|screenSize" > // 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, just do nothing... and don't let to be handled by OS! @@ -2017,36 +2601,36 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) return 0; } } - + int32_t action = AMotionEvent_getAction(event); unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; - + GestureEvent gestureEvent; - + // Register touch actions if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_DOWN; else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_UP; else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_MOVE; - + // Register touch points count gestureEvent.pointCount = AMotionEvent_getPointerCount(event); - + // Register touch points id gestureEvent.pointerId[0] = AMotionEvent_getPointerId(event, 0); gestureEvent.pointerId[1] = AMotionEvent_getPointerId(event, 1); - + // Register touch points position // NOTE: Only two points registered gestureEvent.position[0] = (Vector2){ AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0) }; gestureEvent.position[1] = (Vector2){ AMotionEvent_getX(event, 1), AMotionEvent_getY(event, 1) }; - + // Normalize gestureEvent.position[x] for screenWidth and screenHeight - gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].x /= (float)GetScreenWidth(); gestureEvent.position[0].y /= (float)GetScreenHeight(); - - gestureEvent.position[1].x /= (float)GetScreenWidth(); + + gestureEvent.position[1].x /= (float)GetScreenWidth(); gestureEvent.position[1].y /= (float)GetScreenHeight(); - + // Gesture data is sent to gestures system for processing ProcessGestureEvent(gestureEvent); @@ -2054,151 +2638,117 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) } #endif -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) -// Takes a screenshot and saves it in the same folder as executable -static void TakeScreenshot(void) -{ - static int shotNum = 0; // Screenshot number, increments every screenshot take during program execution - char buffer[20]; // Buffer to store file name - - unsigned char *imgData = rlglReadScreenPixels(renderWidth, renderHeight); +#if defined(PLATFORM_WEB) - sprintf(buffer, "screenshot%03i.png", shotNum); - - // Save image as PNG - WritePNG(buffer, imgData, renderWidth, renderHeight, 4); +// Register fullscreen change events +static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *e, void *userData) +{ + //isFullscreen: int e->isFullscreen + //fullscreenEnabled: int e->fullscreenEnabled + //fs element nodeName: (char *) e->nodeName + //fs element id: (char *) e->id + //Current element size: (int) e->elementWidth, (int) e->elementHeight + //Screen size:(int) e->screenWidth, (int) e->screenHeight - free(imgData); + if (e->isFullscreen) + { + TraceLog(INFO, "Canvas scaled to fullscreen. ElementSize: (%ix%i), ScreenSize(%ix%i)", e->elementWidth, e->elementHeight, e->screenWidth, e->screenHeight); + } + else + { + TraceLog(INFO, "Canvas scaled to windowed. ElementSize: (%ix%i), ScreenSize(%ix%i)", e->elementWidth, e->elementHeight, e->screenWidth, e->screenHeight); + } - shotNum++; + // TODO: Depending on scaling factor (screen vs element), calculate factor to scale mouse/touch input - TraceLog(INFO, "[%s] Screenshot taken!", buffer); + return 0; } -#endif -// Initialize hi-resolution timer -static void InitTimer(void) +// Register touch input events +static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) { - srand(time(NULL)); // Initialize random seed - -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) - struct timespec now; - - if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success + /* + for (int i = 0; i < touchEvent->numTouches; i++) { - baseTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; - } - else TraceLog(WARNING, "No hi-resolution timer available"); -#endif + long x, y, id; - previousTime = GetTime(); // Get time as double -} + if (!touchEvent->touches[i].isChanged) continue; -// Get current time measure (in seconds) since InitTimer() -static double GetTime(void) -{ -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) - return glfwGetTime(); -#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - uint64_t time = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec; + id = touchEvent->touches[i].identifier; + x = touchEvent->touches[i].canvasX; + y = touchEvent->touches[i].canvasY; + } - return (double)(time - baseTime)*1e-9; -#endif -} + printf("%s, numTouches: %d %s%s%s%s\n", emscripten_event_type_to_string(eventType), event->numTouches, + event->ctrlKey ? " CTRL" : "", event->shiftKey ? " SHIFT" : "", event->altKey ? " ALT" : "", event->metaKey ? " META" : ""); -// Get one key state -static bool GetKeyStatus(int key) -{ -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) - return glfwGetKey(window, key); -#elif defined(PLATFORM_ANDROID) - // TODO: Check for virtual keyboard - return false; -#elif defined(PLATFORM_RPI) - // NOTE: Keys states are filled in PollInputEvents() - if (key < 0 || key > 511) return false; - else return currentKeyState[key]; -#endif -} + for (int i = 0; i < event->numTouches; ++i) + { + const EmscriptenTouchPoint *t = &event->touches[i]; -// Get one mouse button state -static bool GetMouseButtonStatus(int button) -{ -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) - return glfwGetMouseButton(window, button); -#elif defined(PLATFORM_ANDROID) - // TODO: Check for virtual keyboard - return false; -#elif defined(PLATFORM_RPI) - // NOTE: Mouse buttons states are filled in PollInputEvents() - return currentMouseState[button]; -#endif -} + printf(" %ld: screen: (%ld,%ld), client: (%ld,%ld), page: (%ld,%ld), isChanged: %d, onTarget: %d, canvas: (%ld, %ld)\n", + t->identifier, t->screenX, t->screenY, t->clientX, t->clientY, t->pageX, t->pageY, t->isChanged, t->onTarget, t->canvasX, t->canvasY); + } + */ -// Poll (store) all input events -static void PollInputEvents(void) -{ - // NOTE: Gestures update must be called every frame to reset gestures correctly - // because ProcessGestureEvent() is just called on an event, not every frame - UpdateGestures(); - -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) - // Mouse input polling - double mouseX; - double mouseY; + GestureEvent gestureEvent; - glfwGetCursorPos(window, &mouseX, &mouseY); + // Register touch actions + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_DOWN; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_UP; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_MOVE; - mousePosition.x = (float)mouseX; - mousePosition.y = (float)mouseY; + // Register touch points count + gestureEvent.pointCount = touchEvent->numTouches; - // Keyboard input polling (automatically managed by GLFW3 through callback) - lastKeyPressed = -1; + // Register touch points id + gestureEvent.pointerId[0] = touchEvent->touches[0].identifier; + gestureEvent.pointerId[1] = touchEvent->touches[1].identifier; - // Register previous keys states - for (int i = 0; i < 512; i++) previousKeyState[i] = currentKeyState[i]; + // Register touch points position + // NOTE: Only two points registered + // TODO: Touch data should be scaled accordingly! + //gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].canvasX, touchEvent->touches[0].canvasY }; + //gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].canvasX, touchEvent->touches[1].canvasY }; + gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY }; + gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].targetX, touchEvent->touches[1].targetY }; - // Register previous mouse states - for (int i = 0; i < 3; i++) previousMouseState[i] = currentMouseState[i]; + touchPosition[0] = gestureEvent.position[0]; + touchPosition[1] = gestureEvent.position[1]; - previousMouseWheelY = currentMouseWheelY; - currentMouseWheelY = 0; + // Normalize gestureEvent.position[x] for screenWidth and screenHeight + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); - glfwPollEvents(); // Register keyboard/mouse events... and window events! -#elif defined(PLATFORM_ANDROID) + gestureEvent.position[1].x /= (float)GetScreenWidth(); + gestureEvent.position[1].y /= (float)GetScreenHeight(); - // Register previous keys states - for (int i = 0; i < 128; i++) previousButtonState[i] = currentButtonState[i]; + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); - // Poll Events (registered events) - // NOTE: Activity is paused if not enabled (appEnabled) - while ((ident = ALooper_pollAll(appEnabled ? 0 : -1, NULL, &events,(void**)&source)) >= 0) - { - // Process this event - if (source != NULL) source->process(app, source); + return 1; +} - // NOTE: Never close window, native activity is controlled by the system! - if (app->destroyRequested != 0) - { - //TraceLog(INFO, "Closing Window..."); - //windowShouldClose = true; - //ANativeActivity_finish(app->activity); - } - } -#elif defined(PLATFORM_RPI) +// Register connected/disconnected gamepads events +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) +{ + /* + printf("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"\n", + eventType != 0 ? emscripten_event_type_to_string(eventType) : "Gamepad state", + gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); - // NOTE: Mouse input events polling is done asynchonously in another pthread - MouseThread() + for(int i = 0; i < gamepadEvent->numAxes; ++i) printf("Axis %d: %g\n", i, gamepadEvent->axis[i]); + for(int i = 0; i < gamepadEvent->numButtons; ++i) printf("Button %d: Digital: %d, Analog: %g\n", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); + */ - // NOTE: Keyboard reading could be done using input_event(s) reading or just read from stdin, - // we use method 2 (stdin) but maybe in a future we should change to method 1... - ProcessKeyboard(); + if ((gamepadEvent->connected) && (gamepadEvent->index < MAX_GAMEPADS)) gamepadReady[gamepadEvent->index] = true; + else gamepadReady[gamepadEvent->index] = false; - // NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread() + // TODO: Test gamepadEvent->index -#endif + return 0; } +#endif #if defined(PLATFORM_RPI) // Initialize Keyboard system (using standard input) @@ -2236,7 +2786,7 @@ static void InitKeyboard(void) else { // We reconfigure keyboard mode to get: - // - scancodes (K_RAW) + // - scancodes (K_RAW) // - keycodes (K_MEDIUMRAW) // - ASCII chars (K_XLATE) // - UNICODE chars (K_UNICODE) @@ -2252,7 +2802,7 @@ static void InitKeyboard(void) 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]; // Max keys to be read at a time @@ -2267,7 +2817,7 @@ static void ProcessKeyboard(void) for (int i = 0; i < bufferByteCount; i++) { TraceLog(DEBUG, "Bytes on keysBuffer: %i", bufferByteCount); - + //printf("Key(s) bytes: "); //for (int i = 0; i < bufferByteCount; i++) printf("0x%02x ", keysBuffer[i]); //printf("\n"); @@ -2301,7 +2851,7 @@ static void ProcessKeyboard(void) case 0x34: 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; } @@ -2318,7 +2868,7 @@ static void ProcessKeyboard(void) i += 3; // Jump to next key } - + // NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT) } } @@ -2328,7 +2878,7 @@ static void ProcessKeyboard(void) else { TraceLog(DEBUG, "Pressed key (ASCII): 0x%02x", keysBuffer[i]); - + // Translate lowercase a-z letters to A-Z if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122)) { @@ -2337,10 +2887,10 @@ static void ProcessKeyboard(void) else currentKeyState[(int)keysBuffer[i]] = 1; } } - + // Check exit key (same functionality as GLFW3 KeyCallback()) if (currentKeyState[exitKey] == 1) windowShouldClose = true; - + // Check screen capture key if (currentKeyState[301] == 1) TakeScreenshot(); // raylib key: KEY_F12 (GLFW_KEY_F12) } @@ -2350,7 +2900,7 @@ static void RestoreKeyboard(void) { // Reset to default keyboard settings tcsetattr(STDIN_FILENO, TCSANOW, &defaultKeyboardSettings); - + // Reconfigure keyboard to default mode ioctl(STDIN_FILENO, KDSKBMODE, defaultKeyboardMode); } @@ -2379,24 +2929,25 @@ static void InitMouse(void) // if too much time passes between reads, queue gets full and new events override older ones... static void *MouseThread(void *arg) { - const unsigned char XSIGN = 1<<4, YSIGN = 1<<5; - - typedef struct { + const unsigned char XSIGN = (1 << 4); + const unsigned char YSIGN = (1 << 5); + + typedef struct { char buttons; - char dx, dy; + char dx, dy; } MouseEvent; - + MouseEvent mouse; - + int mouseRelX = 0; int mouseRelY = 0; - while(1) + while (!windowShouldClose) { if (read(mouseStream, &mouse, sizeof(MouseEvent)) == (int)sizeof(MouseEvent)) { if ((mouse.buttons & 0x08) == 0) break; // This bit should always be set - + // Check Left button pressed if ((mouse.buttons & 0x01) > 0) currentMouseState[0] = 1; else currentMouseState[0] = 0; @@ -2404,27 +2955,27 @@ static void *MouseThread(void *arg) // Check Right button pressed if ((mouse.buttons & 0x02) > 0) currentMouseState[1] = 1; else currentMouseState[1] = 0; - + // Check Middle button pressed if ((mouse.buttons & 0x04) > 0) currentMouseState[2] = 1; else currentMouseState[2] = 0; - + mouseRelX = (int)mouse.dx; mouseRelY = (int)mouse.dy; - + if ((mouse.buttons & XSIGN) > 0) mouseRelX = -1*(255 - mouseRelX); if ((mouse.buttons & YSIGN) > 0) mouseRelY = -1*(255 - mouseRelY); - + // NOTE: Mouse movement is normalized to not be screen resolution dependant // We suppose 2*255 (max relative movement) is equivalent to screenWidth (max pixels width) // Result after normalization is multiplied by MOUSE_SENSITIVITY factor mousePosition.x += (float)mouseRelX*((float)screenWidth/(2*255))*MOUSE_SENSITIVITY; mousePosition.y -= (float)mouseRelY*((float)screenHeight/(2*255))*MOUSE_SENSITIVITY; - + if (mousePosition.x < 0) mousePosition.x = 0; if (mousePosition.y < 0) mousePosition.y = 0; - + if (mousePosition.x > screenWidth) mousePosition.x = screenWidth; if (mousePosition.y > screenHeight) mousePosition.y = screenHeight; } @@ -2434,261 +2985,196 @@ static void *MouseThread(void *arg) return NULL; } -// Init gamepad system -static void InitGamepad(void) +// Touch initialization (including touch thread) +static void InitTouch(void) { - if ((gamepadStream = open(DEFAULT_GAMEPAD_DEV, O_RDONLY|O_NONBLOCK)) < 0) + if ((touchStream = open(DEFAULT_TOUCH_DEV, O_RDONLY|O_NONBLOCK)) < 0) { - TraceLog(WARNING, "Gamepad device could not be opened, no gamepad available"); + TraceLog(WARNING, "Touch device could not be opened, no touchscreen available"); } else { - gamepadReady = true; + touchReady = true; - int error = pthread_create(&gamepadThreadId, NULL, &GamepadThread, NULL); + int error = pthread_create(&touchThreadId, NULL, &TouchThread, NULL); - if (error != 0) TraceLog(WARNING, "Error creating gamepad input event thread"); - else TraceLog(INFO, "Gamepad device initialized successfully"); + if (error != 0) TraceLog(WARNING, "Error creating touch input event thread"); + else TraceLog(INFO, "Touch device initialized successfully"); } } -// Process Gamepad (/dev/input/js0) -static void *GamepadThread(void *arg) +// Touch reading thread. +// This reads from a Virtual Input Event /dev/input/event4 which is +// created by the ts_uinput daemon. This takes, filters and scales +// raw input from the Touchscreen (which appears in /dev/input/event3) +// based on the Calibration data referenced by tslib. +static void *TouchThread(void *arg) { - #define JS_EVENT_BUTTON 0x01 // Button pressed/released - #define JS_EVENT_AXIS 0x02 // Joystick axis moved - #define JS_EVENT_INIT 0x80 // Initial state of device - - struct js_event { - unsigned int time; // event timestamp in milliseconds - short value; // event value - unsigned char type; // event type - unsigned char number; // event axis/button number - }; - - // These values are sensible on Logitech Dual Action Rumble and Xbox360 controller - const int joystickAxisX = 0; - const int joystickAxisY = 1; + struct input_event ev; + GestureEvent gestureEvent; - // Read gamepad event - struct js_event gamepadEvent; - - while (1) + while (!windowShouldClose) { - if (read(gamepadStream, &gamepadEvent, sizeof(struct js_event)) == (int)sizeof(struct js_event)) + if (read(touchStream, &ev, sizeof(ev)) == (int)sizeof(ev)) { - gamepadEvent.type &= ~JS_EVENT_INIT; // Ignore synthetic events - - // Process gamepad events by type - if (gamepadEvent.type == JS_EVENT_BUTTON) + // if pressure > 0 then simulate left mouse button click + if (ev.type == EV_ABS && ev.code == 24 && ev.value == 0 && currentMouseState[0] == 1) { - TraceLog(DEBUG, "Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value); - - if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS) - { - // 1 - button pressed, 0 - button released - gamepadButtons[gamepadEvent.number] = (int)gamepadEvent.value; - } + currentMouseState[0] = 0; + gestureEvent.touchAction = TOUCH_UP; + gestureEvent.pointCount = 1; + gestureEvent.pointerId[0] = 0; + gestureEvent.pointerId[1] = 1; + gestureEvent.position[0] = (Vector2){ mousePosition.x, mousePosition.y }; + gestureEvent.position[1] = (Vector2){ mousePosition.x, mousePosition.y }; + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + gestureEvent.position[1].x /= (float)GetScreenWidth(); + gestureEvent.position[1].y /= (float)GetScreenHeight(); + ProcessGestureEvent(gestureEvent); } - else if (gamepadEvent.type == JS_EVENT_AXIS) + if (ev.type == EV_ABS && ev.code == 24 && ev.value > 0 && currentMouseState[0] == 0) { - TraceLog(DEBUG, "Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value); - - if (gamepadEvent.number == joystickAxisX) gamepadAxisX = (int)gamepadEvent.value; - if (gamepadEvent.number == joystickAxisY) gamepadAxisY = (int)gamepadEvent.value; - /* - switch (gamepadEvent.number) - { - case 0: // 1st Axis X - case 1: // 1st Axis Y - case 2: // 2st Axis X - case 3: // 2st Axis Y - } - */ + currentMouseState[0] = 1; + gestureEvent.touchAction = TOUCH_DOWN; + gestureEvent.pointCount = 1; + gestureEvent.pointerId[0] = 0; + gestureEvent.pointerId[1] = 1; + gestureEvent.position[0] = (Vector2){ mousePosition.x, mousePosition.y }; + gestureEvent.position[1] = (Vector2){ mousePosition.x, mousePosition.y }; + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + gestureEvent.position[1].x /= (float)GetScreenWidth(); + gestureEvent.position[1].y /= (float)GetScreenHeight(); + ProcessGestureEvent(gestureEvent); + } + // x & y values supplied by event4 have been scaled & de-jittered using tslib calibration data + if (ev.type == EV_ABS && ev.code == 0) + { + mousePosition.x = ev.value; + if (mousePosition.x < 0) mousePosition.x = 0; + if (mousePosition.x > screenWidth) mousePosition.x = screenWidth; + gestureEvent.touchAction = TOUCH_MOVE; + gestureEvent.pointCount = 1; + gestureEvent.pointerId[0] = 0; + gestureEvent.pointerId[1] = 1; + gestureEvent.position[0] = (Vector2){ mousePosition.x, mousePosition.y }; + gestureEvent.position[1] = (Vector2){ mousePosition.x, mousePosition.y }; + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + gestureEvent.position[1].x /= (float)GetScreenWidth(); + gestureEvent.position[1].y /= (float)GetScreenHeight(); + ProcessGestureEvent(gestureEvent); + } + if (ev.type == EV_ABS && ev.code == 1) + { + mousePosition.y = ev.value; + if (mousePosition.y < 0) mousePosition.y = 0; + if (mousePosition.y > screenHeight) mousePosition.y = screenHeight; + gestureEvent.touchAction = TOUCH_MOVE; + gestureEvent.pointCount = 1; + gestureEvent.pointerId[0] = 0; + gestureEvent.pointerId[1] = 1; + gestureEvent.position[0] = (Vector2){ mousePosition.x, mousePosition.y }; + gestureEvent.position[1] = (Vector2){ mousePosition.x, mousePosition.y }; + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + gestureEvent.position[1].x /= (float)GetScreenWidth(); + gestureEvent.position[1].y /= (float)GetScreenHeight(); + ProcessGestureEvent(gestureEvent); } + } - } - + } return NULL; } -#endif -// Copy back buffer to front buffers -static void SwapBuffers(void) +// Init gamepad system +static void InitGamepad(void) { -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) - glfwSwapBuffers(window); -#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) - eglSwapBuffers(display, surface); -#endif -} + char gamepadDev[128] = ""; -// Compute framebuffer size relative to screen size and display size -// NOTE: Global variables renderWidth/renderHeight can be modified -static void SetupFramebufferSize(int displayWidth, int displayHeight) -{ - // TODO: SetupFramebufferSize() does not consider properly display video modes. - // It setups a renderWidth/renderHeight with black bars that could not match a valid video mode, - // and so, framebuffer is not scaled properly to some monitors. - - // Calculate renderWidth and renderHeight, we have the display size (input params) and the desired screen size (global var) - if ((screenWidth > displayWidth) || (screenHeight > displayHeight)) + for (int i = 0; i < MAX_GAMEPADS; i++) { - TraceLog(WARNING, "DOWNSCALING: Required screen size (%ix%i) is bigger than display size (%ix%i)", screenWidth, screenHeight, displayWidth, displayHeight); + sprintf(gamepadDev, "%s%i", DEFAULT_GAMEPAD_DEV, i); - // Downscaling to fit display with border-bars - float widthRatio = (float)displayWidth/(float)screenWidth; - float heightRatio = (float)displayHeight/(float)screenHeight; - - if (widthRatio <= heightRatio) + if ((gamepadStream[i] = open(gamepadDev, O_RDONLY|O_NONBLOCK)) < 0) { - renderWidth = displayWidth; - renderHeight = (int)round((float)screenHeight*widthRatio); - renderOffsetX = 0; - renderOffsetY = (displayHeight - renderHeight); + // NOTE: Only show message for first gamepad + if (i == 0) TraceLog(WARNING, "Gamepad device could not be opened, no gamepad available"); } else { - renderWidth = (int)round((float)screenWidth*heightRatio); - renderHeight = displayHeight; - renderOffsetX = (displayWidth - renderWidth); - renderOffsetY = 0; - } + gamepadReady[i] = true; - // NOTE: downscale matrix required! - float scaleRatio = (float)renderWidth/(float)screenWidth; - - downscaleView = MatrixScale(scaleRatio, scaleRatio, scaleRatio); - - // NOTE: We render to full display resolution! - // We just need to calculate above parameters for downscale matrix and offsets - renderWidth = displayWidth; - renderHeight = displayHeight; - - TraceLog(WARNING, "Downscale matrix generated, content will be rendered at: %i x %i", renderWidth, renderHeight); - } - else if ((screenWidth < displayWidth) || (screenHeight < displayHeight)) - { - // Required screen size is smaller than display size - TraceLog(INFO, "UPSCALING: Required screen size: %i x %i -> Display size: %i x %i", screenWidth, screenHeight, displayWidth, displayHeight); - - // Upscaling to fit display with border-bars - float displayRatio = (float)displayWidth/(float)displayHeight; - float screenRatio = (float)screenWidth/(float)screenHeight; + // NOTE: Only create one thread + if (i == 0) + { + int error = pthread_create(&gamepadThreadId, NULL, &GamepadThread, NULL); - if (displayRatio <= screenRatio) - { - renderWidth = screenWidth; - renderHeight = (int)round((float)screenWidth/displayRatio); - renderOffsetX = 0; - renderOffsetY = (renderHeight - screenHeight); - } - else - { - renderWidth = (int)round((float)screenHeight*displayRatio); - renderHeight = screenHeight; - renderOffsetX = (renderWidth - screenWidth); - renderOffsetY = 0; + if (error != 0) TraceLog(WARNING, "Error creating gamepad input event thread"); + else TraceLog(INFO, "Gamepad device initialized successfully"); + } } } - else // screen == display - { - renderWidth = screenWidth; - renderHeight = screenHeight; - renderOffsetX = 0; - renderOffsetY = 0; - } } -#if defined(PLATFORM_WEB) -static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *e, void *userData) -{ - //isFullscreen: int e->isFullscreen - //fullscreenEnabled: int e->fullscreenEnabled - //fs element nodeName: (char *) e->nodeName - //fs element id: (char *) e->id - //Current element size: (int) e->elementWidth, (int) e->elementHeight - //Screen size:(int) e->screenWidth, (int) e->screenHeight - - if (e->isFullscreen) - { - TraceLog(INFO, "Canvas scaled to fullscreen. ElementSize: (%ix%i), ScreenSize(%ix%i)", e->elementWidth, e->elementHeight, e->screenWidth, e->screenHeight); - } - else - { - TraceLog(INFO, "Canvas scaled to windowed. ElementSize: (%ix%i), ScreenSize(%ix%i)", e->elementWidth, e->elementHeight, e->screenWidth, e->screenHeight); - } - - // TODO: Depending on scaling factor (screen vs element), calculate factor to scale mouse/touch input - - return 0; -} - -// Web: Get input events -static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) +// Process Gamepad (/dev/input/js0) +static void *GamepadThread(void *arg) { - /* - for (int i = 0; i < touchEvent->numTouches; i++) - { - long x, y, id; + #define JS_EVENT_BUTTON 0x01 // Button pressed/released + #define JS_EVENT_AXIS 0x02 // Joystick axis moved + #define JS_EVENT_INIT 0x80 // Initial state of device - if (!touchEvent->touches[i].isChanged) continue; + struct js_event { + unsigned int time; // event timestamp in milliseconds + short value; // event value + unsigned char type; // event type + unsigned char number; // event axis/button number + }; - id = touchEvent->touches[i].identifier; - x = touchEvent->touches[i].canvasX; - y = touchEvent->touches[i].canvasY; - } - - printf("%s, numTouches: %d %s%s%s%s\n", emscripten_event_type_to_string(eventType), event->numTouches, - event->ctrlKey ? " CTRL" : "", event->shiftKey ? " SHIFT" : "", event->altKey ? " ALT" : "", event->metaKey ? " META" : ""); + // Read gamepad event + struct js_event gamepadEvent; - for(int i = 0; i < event->numTouches; ++i) + while (!windowShouldClose) { - const EmscriptenTouchPoint *t = &event->touches[i]; - - printf(" %ld: screen: (%ld,%ld), client: (%ld,%ld), page: (%ld,%ld), isChanged: %d, onTarget: %d, canvas: (%ld, %ld)\n", - t->identifier, t->screenX, t->screenY, t->clientX, t->clientY, t->pageX, t->pageY, t->isChanged, t->onTarget, t->canvasX, t->canvasY); - } - */ - - GestureEvent gestureEvent; + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (read(gamepadStream[i], &gamepadEvent, sizeof(struct js_event)) == (int)sizeof(struct js_event)) + { + gamepadEvent.type &= ~JS_EVENT_INIT; // Ignore synthetic events - // Register touch actions - if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_DOWN; - else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_UP; - else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_MOVE; - - // Register touch points count - gestureEvent.pointCount = touchEvent->numTouches; - - // Register touch points id - gestureEvent.pointerId[0] = touchEvent->touches[0].identifier; - gestureEvent.pointerId[1] = touchEvent->touches[1].identifier; - - // Register touch points position - // NOTE: Only two points registered - // TODO: Touch data should be scaled accordingly! - //gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].canvasX, touchEvent->touches[0].canvasY }; - //gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].canvasX, touchEvent->touches[1].canvasY }; - gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY }; - gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].targetX, touchEvent->touches[1].targetY }; + // Process gamepad events by type + if (gamepadEvent.type == JS_EVENT_BUTTON) + { + TraceLog(DEBUG, "Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value); - touchPosition[0] = gestureEvent.position[0]; - touchPosition[1] = gestureEvent.position[1]; - - // Normalize gestureEvent.position[x] for screenWidth and screenHeight - gestureEvent.position[0].x /= (float)GetScreenWidth(); - gestureEvent.position[0].y /= (float)GetScreenHeight(); - - gestureEvent.position[1].x /= (float)GetScreenWidth(); - gestureEvent.position[1].y /= (float)GetScreenHeight(); + if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS) + { + // 1 - button pressed, 0 - button released + currentGamepadState[i][gamepadEvent.number] = (int)gamepadEvent.value; - // Gesture data is sent to gestures system for processing - ProcessGestureEvent(gestureEvent); // Process obtained gestures data + if ((int)gamepadEvent.value == 1) lastGamepadButtonPressed = gamepadEvent.number; + else lastGamepadButtonPressed = -1; + } + } + else if (gamepadEvent.type == JS_EVENT_AXIS) + { + TraceLog(DEBUG, "Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value); - return 1; + if (gamepadEvent.number < MAX_GAMEPAD_AXIS) + { + // NOTE: Scaling of gamepadEvent.value to get values between -1..1 + gamepadAxisState[i][gamepadEvent.number] = (float)gamepadEvent.value/32768; + } + } + } + } + } + + return NULL; } -#endif +#endif // PLATFORM_RPI // Plays raylib logo appearing animation static void LogoAnimation(void) |
