diff --git a/.gitignore b/.gitignore index 73c7b709e..d06636f68 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,6 @@ # Ignore generated files # ... -# Ignore compiled binaries -src/*.o -src/*.exe -examples/*.o -examples/*.exe -templates/*.exe - # Ignore Android generated files and folders src_android/obj/ templates/android_project/bin/ @@ -48,6 +41,9 @@ _ReSharper*/ [Tt]est[Rr]esult* ipch/ *.opensdf + +# Ignore compiled binaries +*.o *.exe !tools/rrem.exe diff --git a/examples/makefile b/examples/makefile index b4d7bfa36..9b0c9d4c2 100644 --- a/examples/makefile +++ b/examples/makefile @@ -23,7 +23,7 @@ # define raylib platform to compile for # possible platforms: PLATFORM_DESKTOP PLATFORM_RPI PLATFORM_WEB -# WARNING: To compile examples to HTML5, they must be redesigned to use emscripten.h and emscripten_set_main_loop() +# WARNING: To compile to HTML5, code must be redesigned to use emscripten.h and emscripten_set_main_loop() PLATFORM ?= PLATFORM_DESKTOP # determine PLATFORM_OS in case PLATFORM_DESKTOP selected @@ -113,20 +113,20 @@ ifeq ($(PLATFORM),PLATFORM_DESKTOP) ifeq ($(PLATFORM_OS),LINUX) # libraries for Debian GNU/Linux desktop compiling # requires the following packages: - # libglfw3-dev libopenal-dev libglew-dev libegl1-mesa-dev - LIBS = -lraylib -lglfw -lGLEW -lGL -lopenal - endif + # libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw3 -lGLEW -lGL -lopenal -lX11 -lXrandr -lXinerama -lXi -lXxf86vm -lXcursor -lm -pthread + else ifeq ($(PLATFORM_OS),OSX) - # libraries for OS X 10.9 desktop compiling - # requires the following packages: - # libglfw3-dev libopenal-dev libglew-dev libegl1-mesa-dev - LIBS = -lraylib -lglfw -framework OpenGL -framework OpenAl -framework Cocoa - + # libraries for OS X 10.9 desktop compiling + # requires the following packages: + # libglfw3-dev libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw -framework OpenGL -framework OpenAl -framework Cocoa else # libraries for Windows desktop compiling # NOTE: GLFW3 and OpenAL Soft libraries should be installed LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 endif + endif endif ifeq ($(PLATFORM),PLATFORM_RPI) # libraries for Raspberry Pi compiling @@ -327,8 +327,7 @@ ifeq ($(PLATFORM),PLATFORM_DESKTOP) rm -f *.o else ifeq ($(PLATFORM_OS),LINUX) - find . -type f -executable -delete - rm -f *.o + find -type f -executable | xargs file -i | grep -E 'x-object|x-archive|x-sharedlib|x-executable' | rev | cut -d ':' -f 2- | rev | xargs rm -f else del *.o *.exe endif diff --git a/examples/models_custom_shader.c b/examples/models_custom_shader.c new file mode 100644 index 000000000..5430ed65a --- /dev/null +++ b/examples/models_custom_shader.c @@ -0,0 +1,86 @@ +/******************************************************************************************* +* +* raylib [models] example - Load and draw a 3d model (OBJ) +* +* This example has been created using raylib 1.0 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +********************************************************************************************/ + +#include "raylib.h" + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 1280; + int screenHeight = 720; + + InitWindow(screenWidth, screenHeight, "raylib [models] example - custom shader"); + + // Define the camera to look into our 3d world + Camera camera = {{ 10.0, 8.0, 10.0 }, { 0.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 }}; + + Texture2D texture = LoadTexture("resources/catsham.png"); // Load model texture + Shader shader = LoadShader("resources/shaders/custom.vs", "resources/shaders/custom.fs"); + //Shader poste = LoadShader("resources/shaders/custom.vs", "resources/shaders/pixel.fs"); + + Model cat = LoadModel("resources/cat.obj"); // Load OBJ model + + SetModelTexture(&cat, texture); // Bind texture to model + //SetModelShader(&cat, poste); + + Vector3 catPosition = { 0.0, 0.0, 0.0 }; // Set model position + + SetPostproShader(shader); + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (IsKeyDown(KEY_LEFT)) catPosition.x -= 0.2; + if (IsKeyDown(KEY_RIGHT)) catPosition.x += 0.2; + if (IsKeyDown(KEY_UP)) catPosition.z -= 0.2; + if (IsKeyDown(KEY_DOWN)) catPosition.z += 0.2; + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + Begin3dMode(camera); + + DrawModel(cat, catPosition, 0.1f, WHITE); // Draw 3d model with texture + + DrawGrid(10.0, 1.0); // Draw a grid + + DrawGizmo(catPosition); // Draw gizmo + + End3dMode(); + + DrawFPS(10, 10); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadTexture(texture); // Unload texture + UnloadModel(cat); // Unload model + UnloadShader(shader); + //UnloadShader(poste); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/resources/shaders/base.vs b/examples/resources/shaders/base.vs new file mode 100644 index 000000000..78e543b71 --- /dev/null +++ b/examples/resources/shaders/base.vs @@ -0,0 +1,19 @@ +#version 110 + +attribute vec3 vertexPosition; +attribute vec2 vertexTexCoord; +attribute vec4 vertexColor; + +uniform mat4 projectionMatrix; +uniform mat4 modelviewMatrix; + +varying vec2 fragTexCoord; +varying vec4 fragColor; + +void main() +{ + fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + + gl_Position = projectionMatrix*modelviewMatrix*vec4(vertexPosition, 1.0); +} \ No newline at end of file diff --git a/examples/resources/shaders/custom.fs b/examples/resources/shaders/custom.fs new file mode 100644 index 000000000..1e53933b8 --- /dev/null +++ b/examples/resources/shaders/custom.fs @@ -0,0 +1,16 @@ +#version 330 + +uniform sampler2D texture0; +varying vec2 fragTexCoord; + +uniform vec4 tintColor; + +void main() +{ + vec4 base = texture2D(texture0, fragTexCoord)*tintColor; + + // Convert to grayscale using NTSC conversion weights + float gray = dot(base.rgb, vec3(0.299, 0.587, 0.114)); + + gl_FragColor = vec4(gray, gray, gray, tintColor.a); +} \ No newline at end of file diff --git a/examples/resources/shaders/custom.vs b/examples/resources/shaders/custom.vs new file mode 100644 index 000000000..629c954d4 --- /dev/null +++ b/examples/resources/shaders/custom.vs @@ -0,0 +1,16 @@ +#version 330 + +attribute vec3 vertexPosition; +attribute vec2 vertexTexCoord; +attribute vec3 vertexNormal; + +uniform mat4 projectionMatrix; +uniform mat4 modelviewMatrix; + +varying vec2 fragTexCoord; + +void main() +{ + fragTexCoord = vertexTexCoord; + gl_Position = projectionMatrix*modelviewMatrix*vec4(vertexPosition, 1.0); +} \ No newline at end of file diff --git a/examples/resources/shaders/grayscale.fs b/examples/resources/shaders/grayscale.fs new file mode 100644 index 000000000..1b7788717 --- /dev/null +++ b/examples/resources/shaders/grayscale.fs @@ -0,0 +1,15 @@ +#version 110 + +uniform sampler2D texture0; +varying vec2 fragTexCoord; +varying vec4 fragColor; + +void main() +{ + vec4 base = texture2D(texture0, fragTexCoord)*fragColor; + + // Convert to grayscale using NTSC conversion weights + float gray = dot(base.rgb, vec3(0.299, 0.587, 0.114)); + + gl_FragColor = vec4(gray, gray, gray, base.a); +} \ No newline at end of file diff --git a/external/glfw3/glfw3.dll b/external/glfw3/glfw3.dll index 5941d1a0a..b5457bde3 100644 Binary files a/external/glfw3/glfw3.dll and b/external/glfw3/glfw3.dll differ diff --git a/external/glfw3/include/GLFW/glfw3.h b/external/glfw3/include/GLFW/glfw3.h index c54abde84..89414491e 100644 --- a/external/glfw3/include/GLFW/glfw3.h +++ b/external/glfw3/include/GLFW/glfw3.h @@ -1,7 +1,6 @@ /************************************************************************* - * GLFW - An OpenGL library - * API version: 3.0 - * WWW: http://www.glfw.org/ + * GLFW 3.1 - www.glfw.org + * A library for OpenGL, window and input *------------------------------------------------------------------------ * Copyright (c) 2002-2006 Marcus Geelnard * Copyright (c) 2006-2010 Camilla Berglund @@ -39,27 +38,32 @@ extern "C" { * Doxygen documentation *************************************************************************/ -/*! @defgroup clipboard Clipboard support - */ /*! @defgroup context Context handling + * + * This is the reference documentation for context related functions. For more + * information, see the @ref context. */ -/*! @defgroup error Error handling - */ -/*! @defgroup gamma Gamma ramp support - */ -/*! @defgroup init Initialization and version information +/*! @defgroup init Initialization, version and errors + * + * This is the reference documentation for initialization and termination of + * the library, version management and error handling. For more information, + * see the @ref intro. */ /*! @defgroup input Input handling + * + * This is the reference documentation for input related functions and types. + * For more information, see the @ref input. */ /*! @defgroup monitor Monitor handling - */ -/*! @defgroup time Time input + * + * This is the reference documentation for monitor related functions and types. + * For more information, see the @ref monitor. */ /*! @defgroup window Window handling * - * This is the reference documentation for the window handling API, including - * creation, deletion and event polling. For more information, see the - * [article on window handling](@ref window). + * This is the reference documentation for window related functions and types, + * including creation, deletion and event polling. For more information, see + * the @ref window. */ @@ -131,74 +135,94 @@ extern "C" { #define GLFW_CALLBACK_DEFINED #endif /* CALLBACK */ -/* Most variants on Windows need wchar_t */ -#if defined(_WIN32) +/* Most GL/glu.h variants on Windows need wchar_t + * OpenGL/gl.h blocks the definition of ptrdiff_t by glext.h on OS X */ +#if !defined(GLFW_INCLUDE_NONE) #include #endif - -/* ---------------- GLFW related system specific defines ----------------- */ +/* Include the chosen client API headers. + */ +#if defined(__APPLE_CC__) + #if defined(GLFW_INCLUDE_GLCOREARB) + #include + #if defined(GLFW_INCLUDE_GLEXT) + #include + #endif + #elif !defined(GLFW_INCLUDE_NONE) + #if !defined(GLFW_INCLUDE_GLEXT) + #define GL_GLEXT_LEGACY + #endif + #include + #endif + #if defined(GLFW_INCLUDE_GLU) + #include + #endif +#else + #if defined(GLFW_INCLUDE_GLCOREARB) + #include + #elif defined(GLFW_INCLUDE_ES1) + #include + #if defined(GLFW_INCLUDE_GLEXT) + #include + #endif + #elif defined(GLFW_INCLUDE_ES2) + #include + #if defined(GLFW_INCLUDE_GLEXT) + #include + #endif + #elif defined(GLFW_INCLUDE_ES3) + #include + #if defined(GLFW_INCLUDE_GLEXT) + #include + #endif + #elif defined(GLFW_INCLUDE_ES31) + #include + #if defined(GLFW_INCLUDE_GLEXT) + #include + #endif + #elif !defined(GLFW_INCLUDE_NONE) + #include + #if defined(GLFW_INCLUDE_GLEXT) + #include + #endif + #endif + #if defined(GLFW_INCLUDE_GLU) + #include + #endif +#endif #if defined(GLFW_DLL) && defined(_GLFW_BUILD_DLL) + /* GLFW_DLL must be defined by applications that are linking against the DLL + * version of the GLFW library. _GLFW_BUILD_DLL is defined by the GLFW + * configuration header when compiling the DLL version of the library. + */ #error "You must not have both GLFW_DLL and _GLFW_BUILD_DLL defined" #endif +/* GLFWAPI is used to declare public API functions for export + * from the DLL / shared library / dynamic library. + */ #if defined(_WIN32) && defined(_GLFW_BUILD_DLL) - - /* We are building a Win32 DLL */ + /* We are building GLFW as a Win32 DLL */ #define GLFWAPI __declspec(dllexport) - #elif defined(_WIN32) && defined(GLFW_DLL) - - /* We are calling a Win32 DLL */ + /* We are calling GLFW as a Win32 DLL */ #if defined(__LCC__) #define GLFWAPI extern #else #define GLFWAPI __declspec(dllimport) #endif - #elif defined(__GNUC__) && defined(_GLFW_BUILD_DLL) - + /* We are building GLFW as a shared / dynamic library */ #define GLFWAPI __attribute__((visibility("default"))) - #else - - /* We are either building/calling a static lib or we are non-win32 */ + /* We are building or calling GLFW as a static library */ #define GLFWAPI - #endif /* -------------------- END SYSTEM/COMPILER SPECIFIC --------------------- */ -/* Include the chosen client API headers. - */ -#if defined(__APPLE_CC__) - #if defined(GLFW_INCLUDE_GLCOREARB) - #include - #elif !defined(GLFW_INCLUDE_NONE) - #define GL_GLEXT_LEGACY - #include - #endif - #if defined(GLFW_INCLUDE_GLU) - #include - #endif -#else - #if defined(GLFW_INCLUDE_GLCOREARB) - #include - #elif defined(GLFW_INCLUDE_ES1) - #include - #elif defined(GLFW_INCLUDE_ES2) - #include - #elif defined(GLFW_INCLUDE_ES3) - #include - #elif !defined(GLFW_INCLUDE_NONE) - #include - #endif - #if defined(GLFW_INCLUDE_GLU) - #include - #endif -#endif - /************************************************************************* * GLFW API tokens @@ -218,27 +242,36 @@ extern "C" { * backward-compatible. * @ingroup init */ -#define GLFW_VERSION_MINOR 0 +#define GLFW_VERSION_MINOR 1 /*! @brief The revision number of the GLFW library. * * This is incremented when a bug fix release is made that does not contain any * API changes. * @ingroup init */ -#define GLFW_VERSION_REVISION 1 +#define GLFW_VERSION_REVISION 0 /*! @} */ /*! @name Key and button actions * @{ */ -/*! @brief The key or button was released. +/*! @brief The key or mouse button was released. + * + * The key or mouse button was released. + * * @ingroup input */ #define GLFW_RELEASE 0 -/*! @brief The key or button was pressed. +/*! @brief The key or mouse button was pressed. + * + * The key or mouse button was pressed. + * * @ingroup input */ #define GLFW_PRESS 1 /*! @brief The key was held down until it repeated. + * + * The key was held down until it repeated. + * * @ingroup input */ #define GLFW_REPEAT 2 @@ -246,20 +279,22 @@ extern "C" { /*! @defgroup keys Keyboard keys * - * These key codes are inspired by the *USB HID Usage Tables v1.12* (p. 53-60), - * but re-arranged to map to 7-bit ASCII for printable keys (function keys are - * put in the 256+ range). + * See [key input](@ref input_key) for how these are used. * - * The naming of the key codes follow these rules: - * - The US keyboard layout is used - * - Names of printable alpha-numeric characters are used (e.g. "A", "R", - * "3", etc.) - * - For non-alphanumeric characters, Unicode:ish names are used (e.g. - * "COMMA", "LEFT_SQUARE_BRACKET", etc.). Note that some names do not - * correspond to the Unicode standard (usually for brevity) - * - Keys that lack a clear US mapping are named "WORLD_x" - * - For non-printable keys, custom names are used (e.g. "F4", - * "BACKSPACE", etc.) + * These key codes are inspired by the _USB HID Usage Tables v1.12_ (p. 53-60), + * but re-arranged to map to 7-bit ASCII for printable keys (function keys are + * put in the 256+ range). + * + * The naming of the key codes follow these rules: + * - The US keyboard layout is used + * - Names of printable alpha-numeric characters are used (e.g. "A", "R", + * "3", etc.) + * - For non-alphanumeric characters, Unicode:ish names are used (e.g. + * "COMMA", "LEFT_SQUARE_BRACKET", etc.). Note that some names do not + * correspond to the Unicode standard (usually for brevity) + * - Keys that lack a clear US mapping are named "WORLD_x" + * - For non-printable keys, custom names are used (e.g. "F4", + * "BACKSPACE", etc.) * * @ingroup input * @{ @@ -396,6 +431,9 @@ extern "C" { /*! @} */ /*! @defgroup mods Modifier key flags + * + * See [key input](@ref input_key) for how these are used. + * * @ingroup input * @{ */ @@ -415,6 +453,9 @@ extern "C" { /*! @} */ /*! @defgroup buttons Mouse buttons + * + * See [mouse button input](@ref input_mouse_button) for how these are used. + * * @ingroup input * @{ */ #define GLFW_MOUSE_BUTTON_1 0 @@ -432,6 +473,9 @@ extern "C" { /*! @} */ /*! @defgroup joysticks Joysticks + * + * See [joystick input](@ref joystick) for how these are used. + * * @ingroup input * @{ */ #define GLFW_JOYSTICK_1 0 @@ -454,36 +498,128 @@ extern "C" { /*! @} */ /*! @defgroup errors Error codes - * @ingroup error + * + * See [error handling](@ref error_handling) for how these are used. + * + * @ingroup init * @{ */ /*! @brief GLFW has not been initialized. + * + * This occurs if a GLFW function was called that may not be called unless the + * library is [initialized](@ref intro_init). + * + * @par Analysis + * Application programmer error. Initialize GLFW before calling any function + * that requires initialization. */ #define GLFW_NOT_INITIALIZED 0x00010001 /*! @brief No context is current for this thread. + * + * This occurs if a GLFW function was called that needs and operates on the + * current OpenGL or OpenGL ES context but no context is current on the calling + * thread. One such function is @ref glfwSwapInterval. + * + * @par Analysis + * Application programmer error. Ensure a context is current before calling + * functions that require a current context. */ #define GLFW_NO_CURRENT_CONTEXT 0x00010002 -/*! @brief One of the enum parameters for the function was given an invalid - * enum. +/*! @brief One of the arguments to the function was an invalid enum value. + * + * One of the arguments to the function was an invalid enum value, for example + * requesting [GLFW_RED_BITS](@ref window_hints_fb) with @ref + * glfwGetWindowAttrib. + * + * @par Analysis + * Application programmer error. Fix the offending call. */ #define GLFW_INVALID_ENUM 0x00010003 -/*! @brief One of the parameters for the function was given an invalid value. +/*! @brief One of the arguments to the function was an invalid value. + * + * One of the arguments to the function was an invalid value, for example + * requesting a non-existent OpenGL or OpenGL ES version like 2.7. + * + * Requesting a valid but unavailable OpenGL or OpenGL ES version will instead + * result in a @ref GLFW_VERSION_UNAVAILABLE error. + * + * @par Analysis + * Application programmer error. Fix the offending call. */ #define GLFW_INVALID_VALUE 0x00010004 /*! @brief A memory allocation failed. + * + * A memory allocation failed. + * + * @par Analysis + * A bug in GLFW or the underlying operating system. Report the bug to our + * [issue tracker](https://github.com/glfw/glfw/issues). */ #define GLFW_OUT_OF_MEMORY 0x00010005 /*! @brief GLFW could not find support for the requested client API on the * system. + * + * GLFW could not find support for the requested client API on the system. + * + * @par Analysis + * The installed graphics driver does not support the requested client API, or + * does not support it via the chosen context creation backend. Below are + * a few examples. + * + * @par + * Some pre-installed Windows graphics drivers do not support OpenGL. AMD only + * supports OpenGL ES via EGL, while Nvidia and Intel only supports it via + * a WGL or GLX extension. OS X does not provide OpenGL ES at all. The Mesa + * EGL, OpenGL and OpenGL ES libraries do not interface with the Nvidia binary + * driver. */ #define GLFW_API_UNAVAILABLE 0x00010006 -/*! @brief The requested client API version is not available. +/*! @brief The requested OpenGL or OpenGL ES version is not available. + * + * The requested OpenGL or OpenGL ES version (including any requested profile + * or context option) is not available on this machine. + * + * @par Analysis + * The machine does not support your requirements. If your application is + * sufficiently flexible, downgrade your requirements and try again. + * Otherwise, inform the user that their machine does not match your + * requirements. + * + * @par + * Future invalid OpenGL and OpenGL ES versions, for example OpenGL 4.8 if 5.0 + * comes out before the 4.x series gets that far, also fail with this error and + * not @ref GLFW_INVALID_VALUE, because GLFW cannot know what future versions + * will exist. */ #define GLFW_VERSION_UNAVAILABLE 0x00010007 /*! @brief A platform-specific error occurred that does not match any of the * more specific categories. + * + * A platform-specific error occurred that does not match any of the more + * specific categories. + * + * @par Analysis + * A bug in GLFW or the underlying operating system. Report the bug to our + * [issue tracker](https://github.com/glfw/glfw/issues). */ #define GLFW_PLATFORM_ERROR 0x00010008 -/*! @brief The clipboard did not contain data in the requested format. +/*! @brief The requested format is not supported or available. + * + * If emitted during window creation, the requested pixel format is not + * supported. + * + * If emitted when querying the clipboard, the contents of the clipboard could + * not be converted to the requested format. + * + * @par Analysis + * If emitted during window creation, one or more + * [hard constraints](@ref window_hints_hard) did not match any of the + * available pixel formats. If your application is sufficiently flexible, + * downgrade your requirements and try again. Otherwise, inform the user that + * their machine does not match your requirements. + * + * @par + * If emitted when querying the clipboard, ignore the error or report it to + * the user, as appropriate. */ #define GLFW_FORMAT_UNAVAILABLE 0x00010009 /*! @} */ @@ -493,6 +629,8 @@ extern "C" { #define GLFW_RESIZABLE 0x00020003 #define GLFW_VISIBLE 0x00020004 #define GLFW_DECORATED 0x00020005 +#define GLFW_AUTO_ICONIFY 0x00020006 +#define GLFW_FLOATING 0x00020007 #define GLFW_RED_BITS 0x00021001 #define GLFW_GREEN_BITS 0x00021002 @@ -509,6 +647,7 @@ extern "C" { #define GLFW_SAMPLES 0x0002100D #define GLFW_SRGB_CAPABLE 0x0002100E #define GLFW_REFRESH_RATE 0x0002100F +#define GLFW_DOUBLEBUFFER 0x00021010 #define GLFW_CLIENT_API 0x00022001 #define GLFW_CONTEXT_VERSION_MAJOR 0x00022002 @@ -518,6 +657,7 @@ extern "C" { #define GLFW_OPENGL_FORWARD_COMPAT 0x00022006 #define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007 #define GLFW_OPENGL_PROFILE 0x00022008 +#define GLFW_CONTEXT_RELEASE_BEHAVIOR 0x00022009 #define GLFW_OPENGL_API 0x00030001 #define GLFW_OPENGL_ES_API 0x00030002 @@ -538,9 +678,54 @@ extern "C" { #define GLFW_CURSOR_HIDDEN 0x00034002 #define GLFW_CURSOR_DISABLED 0x00034003 +#define GLFW_ANY_RELEASE_BEHAVIOR 0 +#define GLFW_RELEASE_BEHAVIOR_FLUSH 0x00035001 +#define GLFW_RELEASE_BEHAVIOR_NONE 0x00035002 + +/*! @defgroup shapes Standard cursor shapes + * + * See [standard cursor creation](@ref cursor_standard) for how these are used. + * + * @ingroup input + * @{ */ + +/*! @brief The regular arrow cursor shape. + * + * The regular arrow cursor. + */ +#define GLFW_ARROW_CURSOR 0x00036001 +/*! @brief The text input I-beam cursor shape. + * + * The text input I-beam cursor shape. + */ +#define GLFW_IBEAM_CURSOR 0x00036002 +/*! @brief The crosshair shape. + * + * The crosshair shape. + */ +#define GLFW_CROSSHAIR_CURSOR 0x00036003 +/*! @brief The hand shape. + * + * The hand shape. + */ +#define GLFW_HAND_CURSOR 0x00036004 +/*! @brief The horizontal resize arrow shape. + * + * The horizontal resize arrow shape. + */ +#define GLFW_HRESIZE_CURSOR 0x00036005 +/*! @brief The vertical resize arrow shape. + * + * The vertical resize arrow shape. + */ +#define GLFW_VRESIZE_CURSOR 0x00036006 +/*! @} */ + #define GLFW_CONNECTED 0x00040001 #define GLFW_DISCONNECTED 0x00040002 +#define GLFW_DONT_CARE -1 + /************************************************************************* * GLFW API types @@ -571,6 +756,14 @@ typedef struct GLFWmonitor GLFWmonitor; */ typedef struct GLFWwindow GLFWwindow; +/*! @brief Opaque cursor object. + * + * Opaque cursor object. + * + * @ingroup cursor + */ +typedef struct GLFWcursor GLFWcursor; + /*! @brief The function signature for error callbacks. * * This is the function signature for error callback functions. @@ -580,7 +773,7 @@ typedef struct GLFWwindow GLFWwindow; * * @sa glfwSetErrorCallback * - * @ingroup error + * @ingroup init */ typedef void (* GLFWerrorfun)(int,const char*); @@ -588,7 +781,7 @@ typedef void (* GLFWerrorfun)(int,const char*); * * This is the function signature for window position callback functions. * - * @param[in] window The window that the user moved. + * @param[in] window The window that was moved. * @param[in] xpos The new x-coordinate, in screen coordinates, of the * upper-left corner of the client area of the window. * @param[in] ypos The new y-coordinate, in screen coordinates, of the @@ -604,7 +797,7 @@ typedef void (* GLFWwindowposfun)(GLFWwindow*,int,int); * * This is the function signature for window size callback functions. * - * @param[in] window The window that the user resized. + * @param[in] window The window that was resized. * @param[in] width The new width, in screen coordinates, of the window. * @param[in] height The new height, in screen coordinates, of the window. * @@ -642,9 +835,9 @@ typedef void (* GLFWwindowrefreshfun)(GLFWwindow*); * * This is the function signature for window focus callback functions. * - * @param[in] window The window that was focused or defocused. - * @param[in] focused `GL_TRUE` if the window was focused, or `GL_FALSE` if - * it was defocused. + * @param[in] window The window that gained or lost input focus. + * @param[in] focused `GL_TRUE` if the window was given input focus, or + * `GL_FALSE` if it lost it. * * @sa glfwSetWindowFocusCallback * @@ -704,8 +897,8 @@ typedef void (* GLFWmousebuttonfun)(GLFWwindow*,int,int,int); * This is the function signature for cursor position callback functions. * * @param[in] window The window that received the event. - * @param[in] xpos The new x-coordinate of the cursor. - * @param[in] ypos The new y-coordinate of the cursor. + * @param[in] xpos The new x-coordinate, in screen coordinates, of the cursor. + * @param[in] ypos The new y-coordinate, in screen coordinates, of the cursor. * * @sa glfwSetCursorPosCallback * @@ -748,7 +941,7 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double); * @param[in] window The window that received the event. * @param[in] key The [keyboard key](@ref keys) that was pressed or released. * @param[in] scancode The system-specific scancode of the key. - * @param[in] action @ref GLFW_PRESS, @ref GLFW_RELEASE or @ref GLFW_REPEAT. + * @param[in] action `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`. * @param[in] mods Bit field describing which [modifier keys](@ref mods) were * held down. * @@ -763,7 +956,7 @@ typedef void (* GLFWkeyfun)(GLFWwindow*,int,int,int,int); * This is the function signature for Unicode character callback functions. * * @param[in] window The window that received the event. - * @param[in] character The Unicode code point of the character. + * @param[in] codepoint The Unicode code point of the character. * * @sa glfwSetCharCallback * @@ -771,6 +964,38 @@ typedef void (* GLFWkeyfun)(GLFWwindow*,int,int,int,int); */ typedef void (* GLFWcharfun)(GLFWwindow*,unsigned int); +/*! @brief The function signature for Unicode character with modifiers + * callbacks. + * + * This is the function signature for Unicode character with modifiers callback + * functions. It is called for each input character, regardless of what + * modifier keys are held down. + * + * @param[in] window The window that received the event. + * @param[in] codepoint The Unicode code point of the character. + * @param[in] mods Bit field describing which [modifier keys](@ref mods) were + * held down. + * + * @sa glfwSetCharModsCallback + * + * @ingroup input + */ +typedef void (* GLFWcharmodsfun)(GLFWwindow*,unsigned int,int); + +/*! @brief The function signature for file drop callbacks. + * + * This is the function signature for file drop callbacks. + * + * @param[in] window The window that received the event. + * @param[in] count The number of dropped files. + * @param[in] names The UTF-8 encoded path names of the dropped files. + * + * @sa glfwSetDropCallback + * + * @ingroup input + */ +typedef void (* GLFWdropfun)(GLFWwindow*,int,const char**); + /*! @brief The function signature for monitor configuration callbacks. * * This is the function signature for monitor configuration callback functions. @@ -790,7 +1015,7 @@ typedef void (* GLFWmonitorfun)(GLFWmonitor*,int); * * @ingroup monitor */ -typedef struct +typedef struct GLFWvidmode { /*! The width, in screen coordinates, of the video mode. */ @@ -818,9 +1043,9 @@ typedef struct * * @sa glfwGetGammaRamp glfwSetGammaRamp * - * @ingroup gamma + * @ingroup monitor */ -typedef struct +typedef struct GLFWgammaramp { /*! An array of value describing the response of the red channel. */ @@ -836,6 +1061,21 @@ typedef struct unsigned int size; } GLFWgammaramp; +/*! @brief Image data. + */ +typedef struct GLFWimage +{ + /*! The width, in pixels, of this image. + */ + int width; + /*! The height, in pixels, of this image. + */ + int height; + /*! The pixel data of this image, arranged left-to-right, top-to-bottom. + */ + unsigned char* pixels; +} GLFWimage; + /************************************************************************* * GLFW API functions @@ -844,56 +1084,70 @@ typedef struct /*! @brief Initializes the GLFW library. * * This function initializes the GLFW library. Before most GLFW functions can - * be used, GLFW must be initialized, and before a program terminates GLFW + * be used, GLFW must be initialized, and before an application terminates GLFW * should be terminated in order to free any resources allocated during or * after initialization. * * If this function fails, it calls @ref glfwTerminate before returning. If it - * succeeds, you should call @ref glfwTerminate before the program exits. + * succeeds, you should call @ref glfwTerminate before the application exits. * * Additional calls to this function after successful initialization but before - * termination will succeed but will do nothing. + * termination will return `GL_TRUE` immediately. * - * @return `GL_TRUE` if successful, or `GL_FALSE` if an error occurred. + * @return `GL_TRUE` if successful, or `GL_FALSE` if an + * [error](@ref error_handling) occurred. * - * @par New in GLFW 3 - * This function no longer registers @ref glfwTerminate with `atexit`. - * - * @note This function may only be called from the main thread. - * - * @note This function may take several seconds to complete on some systems, - * while on other systems it may take only a fraction of a second to complete. - * - * @note **Mac OS X:** This function will change the current directory of the + * @remarks __OS X:__ This function will change the current directory of the * application to the `Contents/Resources` subdirectory of the application's - * bundle, if present. + * bundle, if present. This can be disabled with a + * [compile-time option](@ref compile_options_osx). * + * @remarks __X11:__ If the `LC_CTYPE` category of the current locale is set to + * `"C"` then the environment's locale will be applied to that category. This + * is done because character input will not function when `LC_CTYPE` is set to + * `"C"`. If another locale was set before this function was called, it will + * be left untouched. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref intro_init * @sa glfwTerminate * + * @since Added in GLFW 1.0. + * * @ingroup init */ GLFWAPI int glfwInit(void); /*! @brief Terminates the GLFW library. * - * This function destroys all remaining windows, frees any allocated resources - * and sets the library to an uninitialized state. Once this is called, you - * must again call @ref glfwInit successfully before you will be able to use - * most GLFW functions. + * This function destroys all remaining windows and cursors, restores any + * modified gamma ramps and frees any other allocated resources. Once this + * function is called, you must again call @ref glfwInit successfully before + * you will be able to use most GLFW functions. * * If GLFW has been successfully initialized, this function should be called - * before the program exits. If initialization fails, there is no need to call - * this function, as it is called by @ref glfwInit before it returns failure. + * before the application exits. If initialization fails, there is no need to + * call this function, as it is called by @ref glfwInit before it returns + * failure. * * @remarks This function may be called before @ref glfwInit. * - * @note This function may only be called from the main thread. - * * @warning No window's context may be current on another thread when this * function is called. * + * @par Reentrancy + * This function may not be called from a callback. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref intro_init * @sa glfwInit * + * @since Added in GLFW 1.0. + * * @ingroup init */ GLFWAPI void glfwTerminate(void); @@ -904,46 +1158,55 @@ GLFWAPI void glfwTerminate(void); * library. It is intended for when you are using GLFW as a shared library and * want to ensure that you are using the minimum required version. * + * Any or all of the version arguments may be `NULL`. This function always + * succeeds. + * * @param[out] major Where to store the major version number, or `NULL`. * @param[out] minor Where to store the minor version number, or `NULL`. * @param[out] rev Where to store the revision number, or `NULL`. * * @remarks This function may be called before @ref glfwInit. * - * @remarks This function may be called from any thread. + * @par Thread Safety + * This function may be called from any thread. * + * @sa @ref intro_version * @sa glfwGetVersionString * + * @since Added in GLFW 1.0. + * * @ingroup init */ GLFWAPI void glfwGetVersion(int* major, int* minor, int* rev); /*! @brief Returns a string describing the compile-time configuration. * - * This function returns a static string generated at compile-time according to - * which configuration macros were defined. This is intended for use when - * submitting bug reports, to allow developers to see which code paths are - * enabled in a binary. + * This function returns the compile-time generated + * [version string](@ref intro_version_string) of the GLFW library binary. It + * describes the version, platform, compiler and any platform-specific + * compile-time options. * - * The format of the string is as follows: - * - The version of GLFW - * - The name of the window system API - * - The name of the context creation API - * - Any additional options or APIs + * __Do not use the version string__ to parse the GLFW library version. The + * @ref glfwGetVersion function already provides the version of the running + * library binary. * - * For example, when compiling GLFW 3.0 with MinGW using the Win32 and WGL - * back ends, the version string may look something like this: - * - * 3.0.0 Win32 WGL MinGW + * This function always succeeds. * * @return The GLFW version string. * * @remarks This function may be called before @ref glfwInit. * - * @remarks This function may be called from any thread. + * @par Pointer Lifetime + * The returned string is static and compile-time generated. * + * @par Thread Safety + * This function may be called from any thread. + * + * @sa @ref intro_version * @sa glfwGetVersion * + * @since Added in GLFW 3.0. + * * @ingroup init */ GLFWAPI const char* glfwGetVersionString(void); @@ -953,23 +1216,31 @@ GLFWAPI const char* glfwGetVersionString(void); * This function sets the error callback, which is called with an error code * and a human-readable description each time a GLFW error occurs. * + * The error callback is called on the thread where the error occurred. If you + * are using GLFW from multiple threads, your error callback needs to be + * written accordingly. + * + * Because the description string may have been generated specifically for that + * error, it is not guaranteed to be valid after the callback has returned. If + * you wish to use it after the callback returns, you need to make a copy. + * + * Once set, the error callback remains set even after the library has been + * terminated. + * * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set. * * @remarks This function may be called before @ref glfwInit. * - * @note The error callback is called by the thread where the error was - * generated. If you are using GLFW from multiple threads, your error callback - * needs to be written accordingly. + * @par Thread Safety + * This function may only be called from the main thread. * - * @note Because the description string provided to the callback may have been - * generated specifically for that error, it is not guaranteed to be valid - * after the callback has returned. If you wish to use it after that, you need - * to make your own copy of it before returning. + * @sa @ref error_handling * - * @ingroup error + * @since Added in GLFW 3.0. + * + * @ingroup init */ GLFWAPI GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun cbfun); @@ -978,19 +1249,25 @@ GLFWAPI GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun cbfun); * This function returns an array of handles for all currently connected * monitors. * - * @param[out] count Where to store the size of the returned array. This is - * set to zero if an error occurred. - * @return An array of monitor handles, or `NULL` if an error occurred. + * @param[out] count Where to store the number of monitors in the returned + * array. This is set to zero if an error occurred. + * @return An array of monitor handles, or `NULL` if an + * [error](@ref error_handling) occurred. * - * @note The returned array is allocated and freed by GLFW. You should not - * free it yourself. + * @par Pointer Lifetime + * The returned array is allocated and freed by GLFW. You should not free it + * yourself. It is guaranteed to be valid only until the monitor configuration + * changes or the library is terminated. * - * @note The returned array is valid only until the monitor configuration - * changes. See @ref glfwSetMonitorCallback to receive notifications of - * configuration changes. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref monitor_monitors + * @sa @ref monitor_event * @sa glfwGetPrimaryMonitor * + * @since Added in GLFW 3.0. + * * @ingroup monitor */ GLFWAPI GLFWmonitor** glfwGetMonitors(int* count); @@ -1000,10 +1277,17 @@ GLFWAPI GLFWmonitor** glfwGetMonitors(int* count); * This function returns the primary monitor. This is usually the monitor * where elements like the Windows task bar or the OS X menu bar is located. * - * @return The primary monitor, or `NULL` if an error occurred. + * @return The primary monitor, or `NULL` if an [error](@ref error_handling) + * occurred. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref monitor_monitors * @sa glfwGetMonitors * + * @since Added in GLFW 3.0. + * * @ingroup monitor */ GLFWAPI GLFWmonitor* glfwGetPrimaryMonitor(void); @@ -1013,10 +1297,20 @@ GLFWAPI GLFWmonitor* glfwGetPrimaryMonitor(void); * This function returns the position, in screen coordinates, of the upper-left * corner of the specified monitor. * + * Any or all of the position arguments may be `NULL`. If an error occurs, all + * non-`NULL` position arguments will be set to zero. + * * @param[in] monitor The monitor to query. * @param[out] xpos Where to store the monitor x-coordinate, or `NULL`. * @param[out] ypos Where to store the monitor y-coordinate, or `NULL`. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref monitor_properties + * + * @since Added in GLFW 3.0. + * * @ingroup monitor */ GLFWAPI void glfwGetMonitorPos(GLFWmonitor* monitor, int* xpos, int* ypos); @@ -1026,31 +1320,55 @@ GLFWAPI void glfwGetMonitorPos(GLFWmonitor* monitor, int* xpos, int* ypos); * This function returns the size, in millimetres, of the display area of the * specified monitor. * - * @param[in] monitor The monitor to query. - * @param[out] width Where to store the width, in mm, of the monitor's display - * area, or `NULL`. - * @param[out] height Where to store the height, in mm, of the monitor's - * display area, or `NULL`. + * Some systems do not provide accurate monitor size information, either + * because the monitor + * [EDID](https://en.wikipedia.org/wiki/Extended_display_identification_data) + * data is incorrect or because the driver does not report it accurately. * - * @note Some operating systems do not provide accurate information, either - * because the monitor's EDID data is incorrect, or because the driver does not - * report it accurately. + * Any or all of the size arguments may be `NULL`. If an error occurs, all + * non-`NULL` size arguments will be set to zero. + * + * @param[in] monitor The monitor to query. + * @param[out] widthMM Where to store the width, in millimetres, of the + * monitor's display area, or `NULL`. + * @param[out] heightMM Where to store the height, in millimetres, of the + * monitor's display area, or `NULL`. + * + * @remarks __Windows:__ The OS calculates the returned physical size from the + * current resolution and system DPI instead of querying the monitor EDID data. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref monitor_properties + * + * @since Added in GLFW 3.0. * * @ingroup monitor */ -GLFWAPI void glfwGetMonitorPhysicalSize(GLFWmonitor* monitor, int* width, int* height); +GLFWAPI void glfwGetMonitorPhysicalSize(GLFWmonitor* monitor, int* widthMM, int* heightMM); /*! @brief Returns the name of the specified monitor. * * This function returns a human-readable name, encoded as UTF-8, of the - * specified monitor. + * specified monitor. The name typically reflects the make and model of the + * monitor and is not guaranteed to be unique among the connected monitors. * * @param[in] monitor The monitor to query. - * @return The UTF-8 encoded name of the monitor, or `NULL` if an error - * occurred. + * @return The UTF-8 encoded name of the monitor, or `NULL` if an + * [error](@ref error_handling) occurred. * - * @note The returned string is allocated and freed by GLFW. You should not - * free it yourself. + * @par Pointer Lifetime + * The returned string is allocated and freed by GLFW. You should not free it + * yourself. It is valid until the specified monitor is disconnected or the + * library is terminated. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref monitor_properties + * + * @since Added in GLFW 3.0. * * @ingroup monitor */ @@ -1064,12 +1382,19 @@ GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* monitor); * * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). * - * @bug **X11:** This callback is not yet called on monitor configuration + * @bug __X11:__ This callback is not yet called on monitor configuration * changes. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref monitor_event + * + * @since Added in GLFW 3.0. + * * @ingroup monitor */ GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun cbfun); @@ -1084,16 +1409,25 @@ GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun cbfun); * @param[in] monitor The monitor to query. * @param[out] count Where to store the number of video modes in the returned * array. This is set to zero if an error occurred. - * @return An array of video modes, or `NULL` if an error occurred. + * @return An array of video modes, or `NULL` if an + * [error](@ref error_handling) occurred. * - * @note The returned array is allocated and freed by GLFW. You should not - * free it yourself. + * @par Pointer Lifetime + * The returned array is allocated and freed by GLFW. You should not free it + * yourself. It is valid until the specified monitor is disconnected, this + * function is called again for that monitor or the library is terminated. * - * @note The returned array is valid only until this function is called again - * for the specified monitor. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref monitor_modes * @sa glfwGetVideoMode * + * @since Added in GLFW 1.0. + * + * @par + * __GLFW 3:__ Changed to return an array of modes for a specific monitor. + * * @ingroup monitor */ GLFWAPI const GLFWvidmode* glfwGetVideoModes(GLFWmonitor* monitor, int* count); @@ -1101,17 +1435,26 @@ GLFWAPI const GLFWvidmode* glfwGetVideoModes(GLFWmonitor* monitor, int* count); /*! @brief Returns the current mode of the specified monitor. * * This function returns the current video mode of the specified monitor. If - * you are using a full screen window, the return value will therefore depend - * on whether it is focused. + * you have created a full screen window for that monitor, the return value + * will depend on whether that window is iconified. * * @param[in] monitor The monitor to query. - * @return The current mode of the monitor, or `NULL` if an error occurred. + * @return The current mode of the monitor, or `NULL` if an + * [error](@ref error_handling) occurred. * - * @note The returned struct is allocated and freed by GLFW. You should not - * free it yourself. + * @par Pointer Lifetime + * The returned array is allocated and freed by GLFW. You should not free it + * yourself. It is valid until the specified monitor is disconnected or the + * library is terminated. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref monitor_modes * @sa glfwGetVideoModes * + * @since Added in GLFW 3.0. Replaces `glfwGetDesktopMode`. + * * @ingroup monitor */ GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* monitor); @@ -1124,34 +1467,64 @@ GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* monitor); * @param[in] monitor The monitor whose gamma ramp to set. * @param[in] gamma The desired exponent. * - * @ingroup gamma + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref monitor_gamma + * + * @since Added in GLFW 3.0. + * + * @ingroup monitor */ GLFWAPI void glfwSetGamma(GLFWmonitor* monitor, float gamma); -/*! @brief Retrieves the current gamma ramp for the specified monitor. +/*! @brief Returns the current gamma ramp for the specified monitor. * - * This function retrieves the current gamma ramp of the specified monitor. + * This function returns the current gamma ramp of the specified monitor. * * @param[in] monitor The monitor to query. - * @return The current gamma ramp, or `NULL` if an error occurred. + * @return The current gamma ramp, or `NULL` if an + * [error](@ref error_handling) occurred. * - * @note The value arrays of the returned ramp are allocated and freed by GLFW. - * You should not free them yourself. + * @par Pointer Lifetime + * The returned structure and its arrays are allocated and freed by GLFW. You + * should not free them yourself. They are valid until the specified monitor + * is disconnected, this function is called again for that monitor or the + * library is terminated. * - * @ingroup gamma + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref monitor_gamma + * + * @since Added in GLFW 3.0. + * + * @ingroup monitor */ GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* monitor); /*! @brief Sets the current gamma ramp for the specified monitor. * - * This function sets the current gamma ramp for the specified monitor. + * This function sets the current gamma ramp for the specified monitor. The + * original gamma ramp for that monitor is saved by GLFW the first time this + * function is called and is restored by @ref glfwTerminate. * * @param[in] monitor The monitor whose gamma ramp to set. * @param[in] ramp The gamma ramp to use. * * @note Gamma ramp sizes other than 256 are not supported by all hardware. * - * @ingroup gamma + * @par Pointer Lifetime + * The specified gamma ramp is copied before this function returns. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref monitor_gamma + * + * @since Added in GLFW 3.0. + * + * @ingroup monitor */ GLFWAPI void glfwSetGammaRamp(GLFWmonitor* monitor, const GLFWgammaramp* ramp); @@ -1160,10 +1533,14 @@ GLFWAPI void glfwSetGammaRamp(GLFWmonitor* monitor, const GLFWgammaramp* ramp); * This function resets all window hints to their * [default values](@ref window_hints_values). * - * @note This function may only be called from the main thread. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref window_hints * @sa glfwWindowHint * + * @since Added in GLFW 3.0. + * * @ingroup window */ GLFWAPI void glfwDefaultWindowHints(void); @@ -1173,46 +1550,70 @@ GLFWAPI void glfwDefaultWindowHints(void); * This function sets hints for the next call to @ref glfwCreateWindow. The * hints, once set, retain their values until changed by a call to @ref * glfwWindowHint or @ref glfwDefaultWindowHints, or until the library is - * terminated with @ref glfwTerminate. + * terminated. * * @param[in] target The [window hint](@ref window_hints) to set. * @param[in] hint The new value of the window hint. * - * @par New in GLFW 3 - * Hints are no longer reset to their default values on window creation. To - * set default hint values, use @ref glfwDefaultWindowHints. - * - * @note This function may only be called from the main thread. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref window_hints * @sa glfwDefaultWindowHints * + * @since Added in GLFW 3.0. Replaces `glfwOpenWindowHint`. + * * @ingroup window */ GLFWAPI void glfwWindowHint(int target, int hint); /*! @brief Creates a window and its associated context. * - * This function creates a window and its associated context. Most of the - * options controlling how the window and its context should be created are - * specified through @ref glfwWindowHint. + * This function creates a window and its associated OpenGL or OpenGL ES + * context. Most of the options controlling how the window and its context + * should be created are specified with [window hints](@ref window_hints). * * Successful creation does not change which context is current. Before you - * can use the newly created context, you need to make it current using @ref - * glfwMakeContextCurrent. + * can use the newly created context, you need to + * [make it current](@ref context_current). For information about the `share` + * parameter, see @ref context_sharing. * - * Note that the created window and context may differ from what you requested, - * as not all parameters and hints are + * The created window, framebuffer and context may differ from what you + * requested, as not all parameters and hints are * [hard constraints](@ref window_hints_hard). This includes the size of the - * window, especially for full screen windows. To retrieve the actual - * attributes of the created window and context, use queries like @ref + * window, especially for full screen windows. To query the actual attributes + * of the created window, framebuffer and context, use queries like @ref * glfwGetWindowAttrib and @ref glfwGetWindowSize. * - * To create the window at a specific position, make it initially invisible - * using the `GLFW_VISIBLE` window hint, set its position and then show it. + * To create a full screen window, you need to specify the monitor the window + * will cover. If no monitor is specified, windowed mode will be used. Unless + * you have a way for the user to choose a specific monitor, it is recommended + * that you pick the primary monitor. For more information on how to query + * connected monitors, see @ref monitor_monitors. * - * If a fullscreen window is active, the screensaver is prohibited from + * For full screen windows, the specified size becomes the resolution of the + * window's _desired video mode_. As long as a full screen window has input + * focus, the supported video mode most closely matching the desired video mode + * is set for the specified monitor. For more information about full screen + * windows, including the creation of so called _windowed full screen_ or + * _borderless full screen_ windows, see @ref window_windowed_full_screen. + * + * By default, newly created windows use the placement recommended by the + * window system. To create the window at a specific position, make it + * initially invisible using the [GLFW_VISIBLE](@ref window_hints_wnd) window + * hint, set its [position](@ref window_pos) and then [show](@ref window_hide) + * it. + * + * If a full screen window has input focus, the screensaver is prohibited from * starting. * + * Window systems put limits on window sizes. Very large or very small window + * dimensions may be overridden by the window system on creation. Check the + * actual [size](@ref window_size) after creation. + * + * The [swap interval](@ref buffer_swap) is not set during window creation and + * the initial value may vary depending on driver settings and defaults. + * * @param[in] width The desired width, in screen coordinates, of the window. * This must be greater than zero. * @param[in] height The desired height, in screen coordinates, of the window. @@ -1222,23 +1623,47 @@ GLFWAPI void glfwWindowHint(int target, int hint); * windowed mode. * @param[in] share The window whose context to share resources with, or `NULL` * to not share resources. - * @return The handle of the created window, or `NULL` if an error occurred. + * @return The handle of the created window, or `NULL` if an + * [error](@ref error_handling) occurred. * - * @remarks **Windows:** If the executable has an icon resource named + * @remarks __Windows:__ Window creation will fail if the Microsoft GDI + * software OpenGL implementation is the only one available. + * + * @remarks __Windows:__ If the executable has an icon resource named * `GLFW_ICON,` it will be set as the icon for the window. If no such icon is * present, the `IDI_WINLOGO` icon will be used instead. * - * @remarks **Mac OS X:** The GLFW window has no icon, as it is not a document + * @remarks __Windows:__ The context to share resources with may not be current + * on any other thread. + * + * @remarks __OS X:__ The GLFW window has no icon, as it is not a document * window, but the dock icon will be the same as the application bundle's icon. - * Also, the first time a window is opened the menu bar is populated with - * common commands like Hide, Quit and About. The (minimal) about dialog uses - * information from the application's bundle. For more information on bundles, - * see the Bundle Programming Guide provided by Apple. + * For more information on bundles, see the + * [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) + * in the Mac Developer Library. * - * @note This function may only be called from the main thread. + * @remarks __OS X:__ The first time a window is created the menu bar is + * populated with common commands like Hide, Quit and About. The About entry + * opens a minimal about dialog with information from the application's bundle. + * The menu bar can be disabled with a + * [compile-time option](@ref compile_options_osx). * + * @remarks __X11:__ There is no mechanism for setting the window icon yet. + * + * @remarks __X11:__ Some window managers will not respect the placement of + * initially hidden windows. + * + * @par Reentrancy + * This function may not be called from a callback. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_creation * @sa glfwDestroyWindow * + * @since Added in GLFW 3.0. Replaces `glfwOpenWindow`. + * * @ingroup window */ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share); @@ -1248,19 +1673,25 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, G * This function destroys the specified window and its context. On calling * this function, no further callbacks will be called for that window. * - * @param[in] window The window to destroy. - * - * @note This function may only be called from the main thread. - * - * @note This function may not be called from a callback. - * - * @note If the window's context is current on the main thread, it is + * If the context of the specified window is current on the main thread, it is * detached before being destroyed. * - * @warning The window's context must not be current on any other thread. + * @param[in] window The window to destroy. * + * @note The context of the specified window must not be current on any other + * thread when this function is called. + * + * @par Reentrancy + * This function may not be called from a callback. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_creation * @sa glfwCreateWindow * + * @since Added in GLFW 3.0. Replaces `glfwCloseWindow`. + * * @ingroup window */ GLFWAPI void glfwDestroyWindow(GLFWwindow* window); @@ -1272,6 +1703,13 @@ GLFWAPI void glfwDestroyWindow(GLFWwindow* window); * @param[in] window The window to query. * @return The value of the close flag. * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @sa @ref window_close + * + * @since Added in GLFW 3.0. + * * @ingroup window */ GLFWAPI int glfwWindowShouldClose(GLFWwindow* window); @@ -1285,6 +1723,13 @@ GLFWAPI int glfwWindowShouldClose(GLFWwindow* window); * @param[in] window The window whose flag to change. * @param[in] value The new value. * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @sa @ref window_close + * + * @since Added in GLFW 3.0. + * * @ingroup window */ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* window, int value); @@ -1297,7 +1742,15 @@ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* window, int value); * @param[in] window The window whose title to change. * @param[in] title The UTF-8 encoded window title. * - * @note This function may only be called from the main thread. + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_title + * + * @since Added in GLFW 1.0. + * + * @par + * __GLFW 3:__ Added window handle parameter. * * @ingroup window */ @@ -1308,14 +1761,23 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title); * This function retrieves the position, in screen coordinates, of the * upper-left corner of the client area of the specified window. * + * Any or all of the position arguments may be `NULL`. If an error occurs, all + * non-`NULL` position arguments will be set to zero. + * * @param[in] window The window to query. * @param[out] xpos Where to store the x-coordinate of the upper-left corner of * the client area, or `NULL`. * @param[out] ypos Where to store the y-coordinate of the upper-left corner of * the client area, or `NULL`. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_pos * @sa glfwSetWindowPos * + * @since Added in GLFW 3.0. + * * @ingroup window */ GLFWAPI void glfwGetWindowPos(GLFWwindow* window, int* xpos, int* ypos); @@ -1323,31 +1785,30 @@ GLFWAPI void glfwGetWindowPos(GLFWwindow* window, int* xpos, int* ypos); /*! @brief Sets the position of the client area of the specified window. * * This function sets the position, in screen coordinates, of the upper-left - * corner of the client area of the window. + * corner of the client area of the specified windowed mode window. If the + * window is a full screen window, this function does nothing. * - * If the specified window is a full screen window, this function does nothing. + * __Do not use this function__ to move an already visible window unless you + * have very good reasons for doing so, as it will confuse and annoy the user. * - * If you wish to set an initial window position you should create a hidden - * window (using @ref glfwWindowHint and `GLFW_VISIBLE`), set its position and - * then show it. + * The window manager may put limits on what positions are allowed. GLFW + * cannot and should not override these limits. * * @param[in] window The window to query. * @param[in] xpos The x-coordinate of the upper-left corner of the client area. * @param[in] ypos The y-coordinate of the upper-left corner of the client area. * - * @note It is very rarely a good idea to move an already visible window, as it - * will confuse and annoy the user. - * - * @note This function may only be called from the main thread. - * - * @note The window manager may put limits on what positions are allowed. - * - * @bug **X11:** Some window managers ignore the set position of hidden (i.e. - * unmapped) windows, instead placing them where it thinks is appropriate once - * they are shown. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref window_pos * @sa glfwGetWindowPos * + * @since Added in GLFW 1.0. + * + * @par + * __GLFW 3:__ Added window handle parameter. + * * @ingroup window */ GLFWAPI void glfwSetWindowPos(GLFWwindow* window, int xpos, int ypos); @@ -1355,7 +1816,11 @@ GLFWAPI void glfwSetWindowPos(GLFWwindow* window, int xpos, int ypos); /*! @brief Retrieves the size of the client area of the specified window. * * This function retrieves the size, in screen coordinates, of the client area - * of the specified window. + * of the specified window. If you wish to retrieve the size of the + * framebuffer of the window in pixels, see @ref glfwGetFramebufferSize. + * + * Any or all of the size arguments may be `NULL`. If an error occurs, all + * non-`NULL` size arguments will be set to zero. * * @param[in] window The window whose size to retrieve. * @param[out] width Where to store the width, in screen coordinates, of the @@ -1363,8 +1828,17 @@ GLFWAPI void glfwSetWindowPos(GLFWwindow* window, int xpos, int ypos); * @param[out] height Where to store the height, in screen coordinates, of the * client area, or `NULL`. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_size * @sa glfwSetWindowSize * + * @since Added in GLFW 1.0. + * + * @par + * __GLFW 3:__ Added window handle parameter. + * * @ingroup window */ GLFWAPI void glfwGetWindowSize(GLFWwindow* window, int* width, int* height); @@ -1379,16 +1853,24 @@ GLFWAPI void glfwGetWindowSize(GLFWwindow* window, int* width, int* height); * the context is unaffected, the bit depths of the framebuffer remain * unchanged. * + * The window manager may put limits on what sizes are allowed. GLFW cannot + * and should not override these limits. + * * @param[in] window The window to resize. * @param[in] width The desired width of the specified window. * @param[in] height The desired height of the specified window. * - * @note This function may only be called from the main thread. - * - * @note The window manager may put limits on what window sizes are allowed. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref window_size * @sa glfwGetWindowSize * + * @since Added in GLFW 1.0. + * + * @par + * __GLFW 3:__ Added window handle parameter. + * * @ingroup window */ GLFWAPI void glfwSetWindowSize(GLFWwindow* window, int width, int height); @@ -1396,7 +1878,11 @@ GLFWAPI void glfwSetWindowSize(GLFWwindow* window, int width, int height); /*! @brief Retrieves the size of the framebuffer of the specified window. * * This function retrieves the size, in pixels, of the framebuffer of the - * specified window. + * specified window. If you wish to retrieve the size of the window in screen + * coordinates, see @ref glfwGetWindowSize. + * + * Any or all of the size arguments may be `NULL`. If an error occurs, all + * non-`NULL` size arguments will be set to zero. * * @param[in] window The window whose framebuffer to query. * @param[out] width Where to store the width, in pixels, of the framebuffer, @@ -1404,74 +1890,140 @@ GLFWAPI void glfwSetWindowSize(GLFWwindow* window, int width, int height); * @param[out] height Where to store the height, in pixels, of the framebuffer, * or `NULL`. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_fbsize * @sa glfwSetFramebufferSizeCallback * + * @since Added in GLFW 3.0. + * * @ingroup window */ GLFWAPI void glfwGetFramebufferSize(GLFWwindow* window, int* width, int* height); +/*! @brief Retrieves the size of the frame of the window. + * + * This function retrieves the size, in screen coordinates, of each edge of the + * frame of the specified window. This size includes the title bar, if the + * window has one. The size of the frame may vary depending on the + * [window-related hints](@ref window_hints_wnd) used to create it. + * + * Because this function retrieves the size of each window frame edge and not + * the offset along a particular coordinate axis, the retrieved values will + * always be zero or positive. + * + * Any or all of the size arguments may be `NULL`. If an error occurs, all + * non-`NULL` size arguments will be set to zero. + * + * @param[in] window The window whose frame size to query. + * @param[out] left Where to store the size, in screen coordinates, of the left + * edge of the window frame, or `NULL`. + * @param[out] top Where to store the size, in screen coordinates, of the top + * edge of the window frame, or `NULL`. + * @param[out] right Where to store the size, in screen coordinates, of the + * right edge of the window frame, or `NULL`. + * @param[out] bottom Where to store the size, in screen coordinates, of the + * bottom edge of the window frame, or `NULL`. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_size + * + * @since Added in GLFW 3.1. + * + * @ingroup window + */ +GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int* right, int* bottom); + /*! @brief Iconifies the specified window. * - * This function iconifies/minimizes the specified window, if it was previously - * restored. If it is a full screen window, the original monitor resolution is - * restored until the window is restored. If the window is already iconified, - * this function does nothing. + * This function iconifies (minimizes) the specified window if it was + * previously restored. If the window is already iconified, this function does + * nothing. + * + * If the specified window is a full screen window, the original monitor + * resolution is restored until the window is restored. * * @param[in] window The window to iconify. * - * @note This function may only be called from the main thread. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref window_iconify * @sa glfwRestoreWindow * + * @since Added in GLFW 2.1. + * + * @par + * __GLFW 3:__ Added window handle parameter. + * * @ingroup window */ GLFWAPI void glfwIconifyWindow(GLFWwindow* window); /*! @brief Restores the specified window. * - * This function restores the specified window, if it was previously - * iconified/minimized. If it is a full screen window, the resolution chosen - * for the window is restored on the selected monitor. If the window is - * already restored, this function does nothing. + * This function restores the specified window if it was previously iconified + * (minimized). If the window is already restored, this function does nothing. + * + * If the specified window is a full screen window, the resolution chosen for + * the window is restored on the selected monitor. * * @param[in] window The window to restore. * - * @note This function may only be called from the main thread. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref window_iconify * @sa glfwIconifyWindow * + * @since Added in GLFW 2.1. + * + * @par + * __GLFW 3:__ Added window handle parameter. + * * @ingroup window */ GLFWAPI void glfwRestoreWindow(GLFWwindow* window); /*! @brief Makes the specified window visible. * - * This function makes the specified window visible, if it was previously + * This function makes the specified window visible if it was previously * hidden. If the window is already visible or is in full screen mode, this * function does nothing. * * @param[in] window The window to make visible. * - * @note This function may only be called from the main thread. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref window_hide * @sa glfwHideWindow * + * @since Added in GLFW 3.0. + * * @ingroup window */ GLFWAPI void glfwShowWindow(GLFWwindow* window); /*! @brief Hides the specified window. * - * This function hides the specified window, if it was previously visible. If + * This function hides the specified window if it was previously visible. If * the window is already hidden or is in full screen mode, this function does * nothing. * * @param[in] window The window to hide. * - * @note This function may only be called from the main thread. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref window_hide * @sa glfwShowWindow * + * @since Added in GLFW 3.0. + * * @ingroup window */ GLFWAPI void glfwHideWindow(GLFWwindow* window); @@ -1482,7 +2034,15 @@ GLFWAPI void glfwHideWindow(GLFWwindow* window); * in full screen on. * * @param[in] window The window to query. - * @return The monitor, or `NULL` if the window is in windowed mode. + * @return The monitor, or `NULL` if the window is in windowed mode or an error + * occurred. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_monitor + * + * @since Added in GLFW 3.0. * * @ingroup window */ @@ -1490,13 +2050,22 @@ GLFWAPI GLFWmonitor* glfwGetWindowMonitor(GLFWwindow* window); /*! @brief Returns an attribute of the specified window. * - * This function returns an attribute of the specified window. There are many - * attributes, some related to the window and others to its context. + * This function returns the value of an attribute of the specified window or + * its OpenGL or OpenGL ES context. * * @param[in] window The window to query. * @param[in] attrib The [window attribute](@ref window_attribs) whose value to * return. - * @return The value of the attribute, or zero if an error occurred. + * @return The value of the attribute, or zero if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_attribs + * + * @since Added in GLFW 3.0. Replaces `glfwGetWindowParam` and + * `glfwGetGLVersion`. * * @ingroup window */ @@ -1511,8 +2080,14 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib); * @param[in] window The window whose pointer to set. * @param[in] pointer The new value. * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @sa @ref window_userptr * @sa glfwGetWindowUserPointer * + * @since Added in GLFW 3.0. + * * @ingroup window */ GLFWAPI void glfwSetWindowUserPointer(GLFWwindow* window, void* pointer); @@ -1524,8 +2099,14 @@ GLFWAPI void glfwSetWindowUserPointer(GLFWwindow* window, void* pointer); * * @param[in] window The window whose pointer to return. * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @sa @ref window_userptr * @sa glfwSetWindowUserPointer * + * @since Added in GLFW 3.0. + * * @ingroup window */ GLFWAPI void* glfwGetWindowUserPointer(GLFWwindow* window); @@ -1539,8 +2120,15 @@ GLFWAPI void* glfwGetWindowUserPointer(GLFWwindow* window); * @param[in] window The window whose callback to set. * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_pos + * + * @since Added in GLFW 3.0. * * @ingroup window */ @@ -1555,8 +2143,18 @@ GLFWAPI GLFWwindowposfun glfwSetWindowPosCallback(GLFWwindow* window, GLFWwindow * @param[in] window The window whose callback to set. * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_size + * + * @since Added in GLFW 1.0. + * + * @par + * __GLFW 3:__ Added window handle parameter. Updated callback signature. * * @ingroup window */ @@ -1576,12 +2174,22 @@ GLFWAPI GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwind * @param[in] window The window whose callback to set. * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). * - * @remarks **Mac OS X:** Selecting Quit from the application menu will + * @remarks __OS X:__ Selecting Quit from the application menu will * trigger the close callback for all windows. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_close + * + * @since Added in GLFW 2.5. + * + * @par + * __GLFW 3:__ Added window handle parameter. Updated callback signature. + * * @ingroup window */ GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwindowclosefun cbfun); @@ -1599,12 +2207,18 @@ GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwi * @param[in] window The window whose callback to set. * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). * - * @note On compositing window systems such as Aero, Compiz or Aqua, where the - * window contents are saved off-screen, this callback may be called only very - * infrequently or never at all. + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_refresh + * + * @since Added in GLFW 2.5. + * + * @par + * __GLFW 3:__ Added window handle parameter. Updated callback signature. * * @ingroup window */ @@ -1613,18 +2227,25 @@ GLFWAPI GLFWwindowrefreshfun glfwSetWindowRefreshCallback(GLFWwindow* window, GL /*! @brief Sets the focus callback for the specified window. * * This function sets the focus callback of the specified window, which is - * called when the window gains or loses focus. + * called when the window gains or loses input focus. * - * After the focus callback is called for a window that lost focus, synthetic - * key and mouse button release events will be generated for all such that had - * been pressed. For more information, see @ref glfwSetKeyCallback and @ref - * glfwSetMouseButtonCallback. + * After the focus callback is called for a window that lost input focus, + * synthetic key and mouse button release events will be generated for all such + * that had been pressed. For more information, see @ref glfwSetKeyCallback + * and @ref glfwSetMouseButtonCallback. * * @param[in] window The window whose callback to set. * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_focus + * + * @since Added in GLFW 3.0. * * @ingroup window */ @@ -1638,8 +2259,15 @@ GLFWAPI GLFWwindowfocusfun glfwSetWindowFocusCallback(GLFWwindow* window, GLFWwi * @param[in] window The window whose callback to set. * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_iconify + * + * @since Added in GLFW 3.0. * * @ingroup window */ @@ -1653,8 +2281,15 @@ GLFWAPI GLFWwindowiconifyfun glfwSetWindowIconifyCallback(GLFWwindow* window, GL * @param[in] window The window whose callback to set. * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_fbsize + * + * @since Added in GLFW 3.0. * * @ingroup window */ @@ -1662,99 +2297,164 @@ GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window /*! @brief Processes all pending events. * - * This function processes only those events that have already been received - * and then returns immediately. Processing events will cause the window and - * input callbacks associated with those events to be called. + * This function processes only those events that are already in the event + * queue and then returns immediately. Processing events will cause the window + * and input callbacks associated with those events to be called. * - * This function is not required for joystick input to work. + * On some platforms, a window move, resize or menu operation will cause event + * processing to block. This is due to how event processing is designed on + * those platforms. You can use the + * [window refresh callback](@ref window_refresh) to redraw the contents of + * your window when necessary during such operations. * - * @par New in GLFW 3 - * This function is no longer called by @ref glfwSwapBuffers. You need to call - * it or @ref glfwWaitEvents yourself. + * On some platforms, certain events are sent directly to the application + * without going through the event queue, causing callbacks to be called + * outside of a call to one of the event processing functions. * - * @note This function may only be called from the main thread. + * Event processing is not required for joystick input to work. * - * @note This function may not be called from a callback. + * @par Reentrancy + * This function may not be called from a callback. * - * @note On some platforms, certain callbacks may be called outside of a call - * to one of the event processing functions. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref events * @sa glfwWaitEvents * + * @since Added in GLFW 1.0. + * * @ingroup window */ GLFWAPI void glfwPollEvents(void); -/*! @brief Waits until events are pending and processes them. +/*! @brief Waits until events are queued and processes them. * - * This function puts the calling thread to sleep until at least one event has - * been received. Once one or more events have been recevied, it behaves as if - * @ref glfwPollEvents was called, i.e. the events are processed and the - * function then returns immediately. Processing events will cause the window - * and input callbacks associated with those events to be called. + * This function puts the calling thread to sleep until at least one event is + * available in the event queue. Once one or more events are available, + * it behaves exactly like @ref glfwPollEvents, i.e. the events in the queue + * are processed and the function then returns immediately. Processing events + * will cause the window and input callbacks associated with those events to be + * called. * * Since not all events are associated with callbacks, this function may return * without a callback having been called even if you are monitoring all * callbacks. * - * This function is not required for joystick input to work. + * On some platforms, a window move, resize or menu operation will cause event + * processing to block. This is due to how event processing is designed on + * those platforms. You can use the + * [window refresh callback](@ref window_refresh) to redraw the contents of + * your window when necessary during such operations. * - * @note This function may only be called from the main thread. + * On some platforms, certain callbacks may be called outside of a call to one + * of the event processing functions. * - * @note This function may not be called from a callback. + * If no windows exist, this function returns immediately. For synchronization + * of threads in applications that do not create windows, use your threading + * library of choice. * - * @note On some platforms, certain callbacks may be called outside of a call - * to one of the event processing functions. + * Event processing is not required for joystick input to work. * + * @par Reentrancy + * This function may not be called from a callback. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref events * @sa glfwPollEvents * + * @since Added in GLFW 2.5. + * * @ingroup window */ GLFWAPI void glfwWaitEvents(void); +/*! @brief Posts an empty event to the event queue. + * + * This function posts an empty event from the current thread to the event + * queue, causing @ref glfwWaitEvents to return. + * + * If no windows exist, this function returns immediately. For synchronization + * of threads in applications that do not create windows, use your threading + * library of choice. + * + * @par Thread Safety + * This function may be called from any thread. + * + * @sa @ref events + * @sa glfwWaitEvents + * + * @since Added in GLFW 3.1. + * + * @ingroup window + */ +GLFWAPI void glfwPostEmptyEvent(void); + /*! @brief Returns the value of an input option for the specified window. + * + * This function returns the value of an input option for the specified window. + * The mode must be one of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or + * `GLFW_STICKY_MOUSE_BUTTONS`. * * @param[in] window The window to query. * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or * `GLFW_STICKY_MOUSE_BUTTONS`. * + * @par Thread Safety + * This function may only be called from the main thread. + * * @sa glfwSetInputMode * + * @since Added in GLFW 3.0. + * * @ingroup input */ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); /*! @brief Sets an input option for the specified window. + * + * This function sets an input mode option for the specified window. The mode + * must be one of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or + * `GLFW_STICKY_MOUSE_BUTTONS`. + * + * If the mode is `GLFW_CURSOR`, the value must be one of the following cursor + * modes: + * - `GLFW_CURSOR_NORMAL` makes the cursor visible and behaving normally. + * - `GLFW_CURSOR_HIDDEN` makes the cursor invisible when it is over the client + * area of the window but does not restrict the cursor from leaving. + * - `GLFW_CURSOR_DISABLED` hides and grabs the cursor, providing virtual + * and unlimited cursor movement. This is useful for implementing for + * example 3D camera controls. + * + * If the mode is `GLFW_STICKY_KEYS`, the value must be either `GL_TRUE` to + * enable sticky keys, or `GL_FALSE` to disable it. If sticky keys are + * enabled, a key press will ensure that @ref glfwGetKey returns `GLFW_PRESS` + * the next time it is called even if the key had been released before the + * call. This is useful when you are only interested in whether keys have been + * pressed but not when or in which order. + * + * If the mode is `GLFW_STICKY_MOUSE_BUTTONS`, the value must be either + * `GL_TRUE` to enable sticky mouse buttons, or `GL_FALSE` to disable it. If + * sticky mouse buttons are enabled, a mouse button press will ensure that @ref + * glfwGetMouseButton returns `GLFW_PRESS` the next time it is called even if + * the mouse button had been released before the call. This is useful when you + * are only interested in whether mouse buttons have been pressed but not when + * or in which order. + * * @param[in] window The window whose input mode to set. * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or * `GLFW_STICKY_MOUSE_BUTTONS`. * @param[in] value The new value of the specified input mode. * - * If `mode` is `GLFW_CURSOR`, the value must be one of the supported input - * modes: - * - `GLFW_CURSOR_NORMAL` makes the cursor visible and behaving normally. - * - `GLFW_CURSOR_HIDDEN` makes the cursor invisible when it is over the client - * area of the window. - * - `GLFW_CURSOR_DISABLED` disables the cursor and removes any limitations on - * cursor movement. - * - * If `mode` is `GLFW_STICKY_KEYS`, the value must be either `GL_TRUE` to - * enable sticky keys, or `GL_FALSE` to disable it. If sticky keys are - * enabled, a key press will ensure that @ref glfwGetKey returns @ref - * GLFW_PRESS the next time it is called even if the key had been released - * before the call. This is useful when you are only interested in whether - * keys have been pressed but not when or in which order. - * - * If `mode` is `GLFW_STICKY_MOUSE_BUTTONS`, the value must be either `GL_TRUE` - * to enable sticky mouse buttons, or `GL_FALSE` to disable it. If sticky - * mouse buttons are enabled, a mouse button press will ensure that @ref - * glfwGetMouseButton returns @ref GLFW_PRESS the next time it is called even - * if the mouse button had been released before the call. This is useful when - * you are only interested in whether mouse buttons have been pressed but not - * when or in which order. + * @par Thread Safety + * This function may only be called from the main thread. * * @sa glfwGetInputMode * + * @since Added in GLFW 3.0. Replaces `glfwEnable` and `glfwDisable`. + * * @ingroup input */ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value); @@ -1764,22 +2464,34 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value); * * This function returns the last state reported for the specified key to the * specified window. The returned state is one of `GLFW_PRESS` or - * `GLFW_RELEASE`. The higher-level state `GLFW_REPEAT` is only reported to + * `GLFW_RELEASE`. The higher-level action `GLFW_REPEAT` is only reported to * the key callback. * * If the `GLFW_STICKY_KEYS` input mode is enabled, this function returns - * `GLFW_PRESS` the first time you call this function after a key has been - * pressed, even if the key has already been released. + * `GLFW_PRESS` the first time you call it for a key that was pressed, even if + * that key has already been released. * * The key functions deal with physical keys, with [key tokens](@ref keys) * named after their use on the standard US keyboard layout. If you want to * input text, use the Unicode character callback instead. * + * The [modifier key bit masks](@ref mods) are not key tokens and cannot be + * used with this function. + * * @param[in] window The desired window. - * @param[in] key The desired [keyboard key](@ref keys). + * @param[in] key The desired [keyboard key](@ref keys). `GLFW_KEY_UNKNOWN` is + * not a valid key for this function. * @return One of `GLFW_PRESS` or `GLFW_RELEASE`. * - * @note `GLFW_KEY_UNKNOWN` is not a valid key for this function. + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref input_key + * + * @since Added in GLFW 1.0. + * + * @par + * __GLFW 3:__ Added window handle parameter. * * @ingroup input */ @@ -1789,25 +2501,37 @@ GLFWAPI int glfwGetKey(GLFWwindow* window, int key); * window. * * This function returns the last state reported for the specified mouse button - * to the specified window. + * to the specified window. The returned state is one of `GLFW_PRESS` or + * `GLFW_RELEASE`. * * If the `GLFW_STICKY_MOUSE_BUTTONS` input mode is enabled, this function - * returns `GLFW_PRESS` the first time you call this function after a mouse - * button has been pressed, even if the mouse button has already been released. + * `GLFW_PRESS` the first time you call it for a mouse button that was pressed, + * even if that mouse button has already been released. * * @param[in] window The desired window. * @param[in] button The desired [mouse button](@ref buttons). * @return One of `GLFW_PRESS` or `GLFW_RELEASE`. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref input_mouse_button + * + * @since Added in GLFW 1.0. + * + * @par + * __GLFW 3:__ Added window handle parameter. + * * @ingroup input */ GLFWAPI int glfwGetMouseButton(GLFWwindow* window, int button); -/*! @brief Retrieves the last reported cursor position, relative to the client - * area of the window. +/*! @brief Retrieves the position of the cursor relative to the client area of + * the window. * - * This function returns the last reported position of the cursor to the - * specified window. + * This function returns the position of the cursor, in screen coordinates, + * relative to the upper-left corner of the client area of the specified + * window. * * If the cursor is disabled (with `GLFW_CURSOR_DISABLED`) then the cursor * position is unbounded and limited only by the minimum and maximum values of @@ -1817,43 +2541,182 @@ GLFWAPI int glfwGetMouseButton(GLFWwindow* window, int button); * `floor` function. Casting directly to an integer type works for positive * coordinates, but fails for negative ones. * + * Any or all of the position arguments may be `NULL`. If an error occurs, all + * non-`NULL` position arguments will be set to zero. + * * @param[in] window The desired window. * @param[out] xpos Where to store the cursor x-coordinate, relative to the * left edge of the client area, or `NULL`. * @param[out] ypos Where to store the cursor y-coordinate, relative to the to * top edge of the client area, or `NULL`. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref cursor_pos * @sa glfwSetCursorPos * + * @since Added in GLFW 3.0. Replaces `glfwGetMousePos`. + * * @ingroup input */ GLFWAPI void glfwGetCursorPos(GLFWwindow* window, double* xpos, double* ypos); -/*! @brief Sets the position of the cursor, relative to the client area of the window. +/*! @brief Sets the position of the cursor, relative to the client area of the + * window. * - * This function sets the position of the cursor. The specified window must be - * focused. If the window does not have focus when this function is called, it - * fails silently. + * This function sets the position, in screen coordinates, of the cursor + * relative to the upper-left corner of the client area of the specified + * window. The window must have input focus. If the window does not have + * input focus when this function is called, it fails silently. * - * If the cursor is disabled (with `GLFW_CURSOR_DISABLED`) then the cursor - * position is unbounded and limited only by the minimum and maximum values of + * __Do not use this function__ to implement things like camera controls. GLFW + * already provides the `GLFW_CURSOR_DISABLED` cursor mode that hides the + * cursor, transparently re-centers it and provides unconstrained cursor + * motion. See @ref glfwSetInputMode for more information. + * + * If the cursor mode is `GLFW_CURSOR_DISABLED` then the cursor position is + * unconstrained and limited only by the minimum and maximum values of * a `double`. * * @param[in] window The desired window. * @param[in] xpos The desired x-coordinate, relative to the left edge of the - * client area, or `NULL`. + * client area. * @param[in] ypos The desired y-coordinate, relative to the top edge of the - * client area, or `NULL`. + * client area. * + * @remarks __X11:__ Due to the asynchronous nature of a modern X desktop, it + * may take a moment for the window focus event to arrive. This means you will + * not be able to set the cursor position directly after window creation. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref cursor_pos * @sa glfwGetCursorPos * + * @since Added in GLFW 3.0. Replaces `glfwSetMousePos`. + * * @ingroup input */ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos); +/*! @brief Creates a custom cursor. + * + * Creates a new custom cursor image that can be set for a window with @ref + * glfwSetCursor. The cursor can be destroyed with @ref glfwDestroyCursor. + * Any remaining cursors are destroyed by @ref glfwTerminate. + * + * The pixels are 32-bit little-endian RGBA, i.e. eight bits per channel. They + * are arranged canonically as packed sequential rows, starting from the + * top-left corner. + * + * The cursor hotspot is specified in pixels, relative to the upper-left corner + * of the cursor image. Like all other coordinate systems in GLFW, the X-axis + * points to the right and the Y-axis points down. + * + * @param[in] image The desired cursor image. + * @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot. + * @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot. + * + * @return The handle of the created cursor, or `NULL` if an + * [error](@ref error_handling) occurred. + * + * @par Pointer Lifetime + * The specified image data is copied before this function returns. + * + * @par Reentrancy + * This function may not be called from a callback. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref cursor_object + * @sa glfwDestroyCursor + * @sa glfwCreateStandardCursor + * + * @since Added in GLFW 3.1. + * + * @ingroup input + */ +GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot); + +/*! @brief Creates a cursor with a standard shape. + * + * Returns a cursor with a [standard shape](@ref shapes), that can be set for + * a window with @ref glfwSetCursor. + * + * @param[in] shape One of the [standard shapes](@ref shapes). + * + * @return A new cursor ready to use or `NULL` if an + * [error](@ref error_handling) occurred. + * + * @par Reentrancy + * This function may not be called from a callback. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref cursor_object + * @sa glfwCreateCursor + * + * @since Added in GLFW 3.1. + * + * @ingroup input + */ +GLFWAPI GLFWcursor* glfwCreateStandardCursor(int shape); + +/*! @brief Destroys a cursor. + * + * This function destroys a cursor previously created with @ref + * glfwCreateCursor. Any remaining cursors will be destroyed by @ref + * glfwTerminate. + * + * @param[in] cursor The cursor object to destroy. + * + * @par Reentrancy + * This function may not be called from a callback. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref cursor_object + * @sa glfwCreateCursor + * + * @since Added in GLFW 3.1. + * + * @ingroup input + */ +GLFWAPI void glfwDestroyCursor(GLFWcursor* cursor); + +/*! @brief Sets the cursor for the window. + * + * This function sets the cursor image to be used when the cursor is over the + * client area of the specified window. The set cursor will only be visible + * when the [cursor mode](@ref cursor_mode) of the window is + * `GLFW_CURSOR_NORMAL`. + * + * On some platforms, the set cursor may not be visible unless the window also + * has input focus. + * + * @param[in] window The window to set the cursor for. + * @param[in] cursor The cursor to set, or `NULL` to switch back to the default + * arrow cursor. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref cursor_object + * + * @since Added in GLFW 3.1. + * + * @ingroup input + */ +GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor); + /*! @brief Sets the key callback. * - * This function sets the key callback of the specific window, which is called + * This function sets the key callback of the specified window, which is called * when a key is pressed, repeated or released. * * The key functions deal with physical keys, with layout independent @@ -1861,16 +2724,16 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos); * layout. If you want to input text, use the * [character callback](@ref glfwSetCharCallback) instead. * - * When a window loses focus, it will generate synthetic key release events - * for all pressed keys. You can tell these events from user-generated events - * by the fact that the synthetic ones are generated after the window has lost - * focus, i.e. `GLFW_FOCUSED` will be false and the focus callback will have - * already been called. + * When a window loses input focus, it will generate synthetic key release + * events for all pressed keys. You can tell these events from user-generated + * events by the fact that the synthetic ones are generated after the focus + * loss event has been processed, i.e. after the + * [window focus callback](@ref glfwSetWindowFocusCallback) has been called. * * The scancode of a key is specific to that platform or sometimes even to that * machine. Scancodes are intended to allow users to bind keys that don't have * a GLFW key token. Such keys have `key` set to `GLFW_KEY_UNKNOWN`, their - * state is not saved and so it cannot be retrieved with @ref glfwGetKey. + * state is not saved and so it cannot be queried with @ref glfwGetKey. * * Sometimes GLFW needs to generate synthetic key events, in which case the * scancode may be zero. @@ -1878,8 +2741,18 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos); * @param[in] window The window whose callback to set. * @param[in] cbfun The new key callback, or `NULL` to remove the currently * set callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref input_key + * + * @since Added in GLFW 1.0. + * + * @par + * __GLFW 3:__ Added window handle parameter. Updated callback signature. * * @ingroup input */ @@ -1887,11 +2760,56 @@ GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* window, GLFWkeyfun cbfun); /*! @brief Sets the Unicode character callback. * - * This function sets the character callback of the specific window, which is + * This function sets the character callback of the specified window, which is * called when a Unicode character is input. * - * The character callback is intended for text input. If you want to know - * whether a specific key was pressed or released, use the + * The character callback is intended for Unicode text input. As it deals with + * characters, it is keyboard layout dependent, whereas the + * [key callback](@ref glfwSetKeyCallback) is not. Characters do not map 1:1 + * to physical keys, as a key may produce zero, one or more characters. If you + * want to know whether a specific physical key was pressed or released, see + * the key callback instead. + * + * The character callback behaves as system text input normally does and will + * not be called if modifier keys are held down that would prevent normal text + * input on that platform, for example a Super (Command) key on OS X or Alt key + * on Windows. There is a + * [character with modifiers callback](@ref glfwSetCharModsCallback) that + * receives these events. + * + * @param[in] window The window whose callback to set. + * @param[in] cbfun The new callback, or `NULL` to remove the currently set + * callback. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref input_char + * + * @since Added in GLFW 2.4. + * + * @par + * __GLFW 3:__ Added window handle parameter. Updated callback signature. + * + * @ingroup input + */ +GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* window, GLFWcharfun cbfun); + +/*! @brief Sets the Unicode character with modifiers callback. + * + * This function sets the character with modifiers callback of the specified + * window, which is called when a Unicode character is input regardless of what + * modifier keys are used. + * + * The character with modifiers callback is intended for implementing custom + * Unicode character input. For regular Unicode text input, see the + * [character callback](@ref glfwSetCharCallback). Like the character + * callback, the character with modifiers callback deals with characters and is + * keyboard layout dependent. Characters do not map 1:1 to physical keys, as + * a key may produce zero, one or more characters. If you want to know whether + * a specific physical key was pressed or released, see the * [key callback](@ref glfwSetKeyCallback) instead. * * @param[in] window The window whose callback to set. @@ -1900,26 +2818,43 @@ GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* window, GLFWkeyfun cbfun); * @return The previously set callback, or `NULL` if no callback was set or an * error occurred. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref input_char + * + * @since Added in GLFW 3.1. + * * @ingroup input */ -GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* window, GLFWcharfun cbfun); +GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* window, GLFWcharmodsfun cbfun); /*! @brief Sets the mouse button callback. * * This function sets the mouse button callback of the specified window, which * is called when a mouse button is pressed or released. * - * When a window loses focus, it will generate synthetic mouse button release - * events for all pressed mouse buttons. You can tell these events from - * user-generated events by the fact that the synthetic ones are generated - * after the window has lost focus, i.e. `GLFW_FOCUSED` will be false and the - * focus callback will have already been called. + * When a window loses input focus, it will generate synthetic mouse button + * release events for all pressed mouse buttons. You can tell these events + * from user-generated events by the fact that the synthetic ones are generated + * after the focus loss event has been processed, i.e. after the + * [window focus callback](@ref glfwSetWindowFocusCallback) has been called. * * @param[in] window The window whose callback to set. * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref input_mouse_button + * + * @since Added in GLFW 1.0. + * + * @par + * __GLFW 3:__ Added window handle parameter. Updated callback signature. * * @ingroup input */ @@ -1929,13 +2864,21 @@ GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* window, GLFWmo * * This function sets the cursor position callback of the specified window, * which is called when the cursor is moved. The callback is provided with the - * position relative to the upper-left corner of the client area of the window. + * position, in screen coordinates, relative to the upper-left corner of the + * client area of the window. * * @param[in] window The window whose callback to set. * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref cursor_pos + * + * @since Added in GLFW 3.0. Replaces `glfwSetMousePosCallback`. * * @ingroup input */ @@ -1950,8 +2893,15 @@ GLFWAPI GLFWcursorposfun glfwSetCursorPosCallback(GLFWwindow* window, GLFWcursor * @param[in] window The window whose callback to set. * @param[in] cbfun The new callback, or `NULL` to remove the currently set * callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref cursor_enter + * + * @since Added in GLFW 3.0. * * @ingroup input */ @@ -1969,20 +2919,61 @@ GLFWAPI GLFWcursorenterfun glfwSetCursorEnterCallback(GLFWwindow* window, GLFWcu * @param[in] window The window whose callback to set. * @param[in] cbfun The new scroll callback, or `NULL` to remove the currently * set callback. - * @return The previously set callback, or `NULL` if no callback was set or an - * error occurred. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref scrolling + * + * @since Added in GLFW 3.0. Replaces `glfwSetMouseWheelCallback`. * * @ingroup input */ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun cbfun); +/*! @brief Sets the file drop callback. + * + * This function sets the file drop callback of the specified window, which is + * called when one or more dragged files are dropped on the window. + * + * Because the path array and its strings may have been generated specifically + * for that event, they are not guaranteed to be valid after the callback has + * returned. If you wish to use them after the callback returns, you need to + * make a deep copy. + * + * @param[in] window The window whose callback to set. + * @param[in] cbfun The new file drop callback, or `NULL` to remove the + * currently set callback. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref path_drop + * + * @since Added in GLFW 3.1. + * + * @ingroup input + */ +GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* window, GLFWdropfun cbfun); + /*! @brief Returns whether the specified joystick is present. * * This function returns whether the specified joystick is present. * - * @param[in] joy The joystick to query. + * @param[in] joy The [joystick](@ref joysticks) to query. * @return `GL_TRUE` if the joystick is present, or `GL_FALSE` otherwise. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref joystick + * + * @since Added in GLFW 3.0. Replaces `glfwGetJoystickParam`. + * * @ingroup input */ GLFWAPI int glfwJoystickPresent(int joy); @@ -1990,17 +2981,24 @@ GLFWAPI int glfwJoystickPresent(int joy); /*! @brief Returns the values of all axes of the specified joystick. * * This function returns the values of all axes of the specified joystick. + * Each element in the array is a value between -1.0 and 1.0. * - * @param[in] joy The joystick to query. - * @param[out] count Where to store the size of the returned array. This is - * set to zero if an error occurred. + * @param[in] joy The [joystick](@ref joysticks) to query. + * @param[out] count Where to store the number of axis values in the returned + * array. This is set to zero if an error occurred. * @return An array of axis values, or `NULL` if the joystick is not present. * - * @note The returned array is allocated and freed by GLFW. You should not - * free it yourself. + * @par Pointer Lifetime + * The returned array is allocated and freed by GLFW. You should not free it + * yourself. It is valid until the specified joystick is disconnected, this + * function is called again for that joystick or the library is terminated. * - * @note The returned array is valid only until the next call to @ref - * glfwGetJoystickAxes for that joystick. + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref joystick_axis + * + * @since Added in GLFW 3.0. Replaces `glfwGetJoystickPos`. * * @ingroup input */ @@ -2009,17 +3007,27 @@ GLFWAPI const float* glfwGetJoystickAxes(int joy, int* count); /*! @brief Returns the state of all buttons of the specified joystick. * * This function returns the state of all buttons of the specified joystick. + * Each element in the array is either `GLFW_PRESS` or `GLFW_RELEASE`. * - * @param[in] joy The joystick to query. - * @param[out] count Where to store the size of the returned array. This is - * set to zero if an error occurred. + * @param[in] joy The [joystick](@ref joysticks) to query. + * @param[out] count Where to store the number of button states in the returned + * array. This is set to zero if an error occurred. * @return An array of button states, or `NULL` if the joystick is not present. * - * @note The returned array is allocated and freed by GLFW. You should not - * free it yourself. + * @par Pointer Lifetime + * The returned array is allocated and freed by GLFW. You should not free it + * yourself. It is valid until the specified joystick is disconnected, this + * function is called again for that joystick or the library is terminated. * - * @note The returned array is valid only until the next call to @ref - * glfwGetJoystickButtons for that joystick. + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref joystick_button + * + * @since Added in GLFW 2.2. + * + * @par + * __GLFW 3:__ Changed to return a dynamic array. * * @ingroup input */ @@ -2028,16 +3036,24 @@ GLFWAPI const unsigned char* glfwGetJoystickButtons(int joy, int* count); /*! @brief Returns the name of the specified joystick. * * This function returns the name, encoded as UTF-8, of the specified joystick. + * The returned string is allocated and freed by GLFW. You should not free it + * yourself. * - * @param[in] joy The joystick to query. + * @param[in] joy The [joystick](@ref joysticks) to query. * @return The UTF-8 encoded name of the joystick, or `NULL` if the joystick * is not present. * - * @note The returned string is allocated and freed by GLFW. You should not - * free it yourself. + * @par Pointer Lifetime + * The returned string is allocated and freed by GLFW. You should not free it + * yourself. It is valid until the specified joystick is disconnected, this + * function is called again for that joystick or the library is terminated. * - * @note The returned string is valid only until the next call to @ref - * glfwGetJoystickName for that joystick. + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref joystick_name + * + * @since Added in GLFW 3.0. * * @ingroup input */ @@ -2046,40 +3062,50 @@ GLFWAPI const char* glfwGetJoystickName(int joy); /*! @brief Sets the clipboard to the specified string. * * This function sets the system clipboard to the specified, UTF-8 encoded - * string. The string is copied before returning, so you don't have to retain - * it afterwards. + * string. * * @param[in] window The window that will own the clipboard contents. * @param[in] string A UTF-8 encoded string. * - * @note This function may only be called from the main thread. + * @par Pointer Lifetime + * The specified string is copied before this function returns. * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref clipboard * @sa glfwGetClipboardString * - * @ingroup clipboard + * @since Added in GLFW 3.0. + * + * @ingroup input */ GLFWAPI void glfwSetClipboardString(GLFWwindow* window, const char* string); -/*! @brief Retrieves the contents of the clipboard as a string. +/*! @brief Returns the contents of the clipboard as a string. * * This function returns the contents of the system clipboard, if it contains * or is convertible to a UTF-8 encoded string. * * @param[in] window The window that will request the clipboard contents. * @return The contents of the clipboard as a UTF-8 encoded string, or `NULL` - * if an error occurred. + * if an [error](@ref error_handling) occurred. * - * @note This function may only be called from the main thread. + * @par Pointer Lifetime + * The returned string is allocated and freed by GLFW. You should not free it + * yourself. It is valid until the next call to @ref + * glfwGetClipboardString or @ref glfwSetClipboardString, or until the library + * is terminated. * - * @note The returned string is allocated and freed by GLFW. You should not - * free it yourself. - * - * @note The returned string is valid only until the next call to @ref - * glfwGetClipboardString or @ref glfwSetClipboardString. + * @par Thread Safety + * This function may only be called from the main thread. * + * @sa @ref clipboard * @sa glfwSetClipboardString * - * @ingroup clipboard + * @since Added in GLFW 3.0. + * + * @ingroup input */ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window); @@ -2089,15 +3115,21 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window); * been set using @ref glfwSetTime, the timer measures time elapsed since GLFW * was initialized. * - * @return The current value, in seconds, or zero if an error occurred. + * The resolution of the timer is system dependent, but is usually on the order + * of a few micro- or nanoseconds. It uses the highest-resolution monotonic + * time source on each supported platform. * - * @remarks This function may be called from secondary threads. + * @return The current value, in seconds, or zero if an + * [error](@ref error_handling) occurred. * - * @note The resolution of the timer is system dependent, but is usually on the - * order of a few micro- or nanoseconds. It uses the highest-resolution - * monotonic time source on each supported platform. + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. * - * @ingroup time + * @sa @ref time + * + * @since Added in GLFW 1.0. + * + * @ingroup input */ GLFWAPI double glfwGetTime(void); @@ -2108,44 +3140,61 @@ GLFWAPI double glfwGetTime(void); * * @param[in] time The new value, in seconds. * - * @note The resolution of the timer is system dependent, but is usually on the - * order of a few micro- or nanoseconds. It uses the highest-resolution - * monotonic time source on each supported platform. + * @par Thread Safety + * This function may only be called from the main thread. * - * @ingroup time + * @sa @ref time + * + * @since Added in GLFW 2.2. + * + * @ingroup input */ GLFWAPI void glfwSetTime(double time); /*! @brief Makes the context of the specified window current for the calling * thread. * - * This function makes the context of the specified window current on the - * calling thread. A context can only be made current on a single thread at - * a time and each thread can have only a single current context at a time. + * This function makes the OpenGL or OpenGL ES context of the specified window + * current on the calling thread. A context can only be made current on + * a single thread at a time and each thread can have only a single current + * context at a time. + * + * By default, making a context non-current implicitly forces a pipeline flush. + * On machines that support `GL_KHR_context_flush_control`, you can control + * whether a context performs this flush by setting the + * [GLFW_CONTEXT_RELEASE_BEHAVIOR](@ref window_hints_ctx) window hint. * * @param[in] window The window whose context to make current, or `NULL` to * detach the current context. * - * @remarks This function may be called from secondary threads. + * @par Thread Safety + * This function may be called from any thread. * + * @sa @ref context_current * @sa glfwGetCurrentContext * + * @since Added in GLFW 3.0. + * * @ingroup context */ GLFWAPI void glfwMakeContextCurrent(GLFWwindow* window); /*! @brief Returns the window whose context is current on the calling thread. * - * This function returns the window whose context is current on the calling - * thread. + * This function returns the window whose OpenGL or OpenGL ES context is + * current on the calling thread. * * @return The window whose context is current, or `NULL` if no window's * context is current. * - * @remarks This function may be called from secondary threads. + * @par Thread Safety + * This function may be called from any thread. * + * @sa @ref context_current * @sa glfwMakeContextCurrent * + * @since Added in GLFW 3.0. + * * @ingroup context */ GLFWAPI GLFWwindow* glfwGetCurrentContext(void); @@ -2158,24 +3207,28 @@ GLFWAPI GLFWwindow* glfwGetCurrentContext(void); * * @param[in] window The window whose buffers to swap. * - * @remarks This function may be called from secondary threads. - * - * @par New in GLFW 3 - * This function no longer calls @ref glfwPollEvents. You need to call it or - * @ref glfwWaitEvents yourself. + * @par Thread Safety + * This function may be called from any thread. * + * @sa @ref buffer_swap * @sa glfwSwapInterval * - * @ingroup context + * @since Added in GLFW 1.0. + * + * @par + * __GLFW 3:__ Added window handle parameter. + * + * @ingroup window */ GLFWAPI void glfwSwapBuffers(GLFWwindow* window); /*! @brief Sets the swap interval for the current context. * * This function sets the swap interval for the current context, i.e. the - * number of screen updates to wait before swapping the buffers of a window and - * returning from @ref glfwSwapBuffers. This is sometimes called 'vertical - * synchronization', 'vertical retrace synchronization' or 'vsync'. + * number of screen updates to wait from the time @ref glfwSwapBuffers was + * called before swapping the buffers and returning. This is sometimes called + * _vertical synchronization_, _vertical retrace synchronization_ or just + * _vsync_. * * Contexts that support either of the `WGL_EXT_swap_control_tear` and * `GLX_EXT_swap_control_tear` extensions also accept negative swap intervals, @@ -2184,17 +3237,29 @@ GLFWAPI void glfwSwapBuffers(GLFWwindow* window); * glfwExtensionSupported. For more information about swap tearing, see the * extension specifications. * + * A context must be current on the calling thread. Calling this function + * without a current context will cause a @ref GLFW_NO_CURRENT_CONTEXT error. + * * @param[in] interval The minimum number of screen updates to wait for * until the buffers are swapped by @ref glfwSwapBuffers. * - * @remarks This function may be called from secondary threads. + * @note This function is not called during window creation, leaving the swap + * interval set to whatever is the default on that platform. This is done + * because some swap interval extensions used by GLFW do not allow the swap + * interval to be reset to zero once it has been set to a non-zero value. * * @note Some GPU drivers do not honor the requested swap interval, either * because of user settings that override the request or due to bugs in the * driver. * + * @par Thread Safety + * This function may be called from any thread. + * + * @sa @ref buffer_swap * @sa glfwSwapBuffers * + * @since Added in GLFW 1.0. + * * @ingroup context */ GLFWAPI void glfwSwapInterval(int interval); @@ -2202,19 +3267,28 @@ GLFWAPI void glfwSwapInterval(int interval); /*! @brief Returns whether the specified extension is available. * * This function returns whether the specified - * [OpenGL or context creation API extension](@ref context_glext) is supported - * by the current context. For example, on Windows both the OpenGL and WGL - * extension strings are checked. + * [API extension](@ref context_glext) is supported by the current OpenGL or + * OpenGL ES context. It searches both for OpenGL and OpenGL ES extension and + * platform-specific context creation API extensions. + * + * A context must be current on the calling thread. Calling this function + * without a current context will cause a @ref GLFW_NO_CURRENT_CONTEXT error. + * + * As this functions retrieves and searches one or more extension strings each + * call, it is recommended that you cache its results if it is going to be used + * frequently. The extension strings will not change during the lifetime of + * a context, so there is no danger in doing this. * * @param[in] extension The ASCII encoded name of the extension. * @return `GL_TRUE` if the extension is available, or `GL_FALSE` otherwise. * - * @remarks This function may be called from secondary threads. + * @par Thread Safety + * This function may be called from any thread. * - * @note As this functions searches one or more extension strings on each call, - * it is recommended that you cache its results if it's going to be used - * frequently. The extension strings will not change during the lifetime of - * a context, so there is no danger in doing this. + * @sa @ref context_glext + * @sa glfwGetProcAddress + * + * @since Added in GLFW 1.0. * * @ingroup context */ @@ -2227,15 +3301,27 @@ GLFWAPI int glfwExtensionSupported(const char* extension); * [client API or extension function](@ref context_glext), if it is supported * by the current context. * + * A context must be current on the calling thread. Calling this function + * without a current context will cause a @ref GLFW_NO_CURRENT_CONTEXT error. + * * @param[in] procname The ASCII encoded name of the function. * @return The address of the function, or `NULL` if the function is - * unavailable. + * unavailable or an [error](@ref error_handling) occurred. * - * @remarks This function may be called from secondary threads. + * @note The addresses of a given function is not guaranteed to be the same + * between contexts. * - * @note The addresses of these functions are not guaranteed to be the same for - * all contexts, especially if they use different client APIs or even different - * context creation hints. + * @par Pointer Lifetime + * The returned function pointer is valid until the context is destroyed or the + * library is terminated. + * + * @par Thread Safety + * This function may be called from any thread. + * + * @sa @ref context_glext + * @sa glfwExtensionSupported + * + * @since Added in GLFW 1.0. * * @ingroup context */ diff --git a/external/glfw3/include/GLFW/glfw3native.h b/external/glfw3/include/GLFW/glfw3native.h index d570f5876..b3ce7482d 100644 --- a/external/glfw3/include/GLFW/glfw3native.h +++ b/external/glfw3/include/GLFW/glfw3native.h @@ -1,5 +1,5 @@ /************************************************************************* - * GLFW 3.0 - www.glfw.org + * GLFW 3.1 - www.glfw.org * A library for OpenGL, window and input *------------------------------------------------------------------------ * Copyright (c) 2002-2006 Marcus Geelnard @@ -40,13 +40,13 @@ extern "C" { /*! @defgroup native Native access * - * **By using the native API, you assert that you know what you're doing and - * how to fix problems caused by using it. If you don't, you shouldn't be - * using it.** + * **By using the native access functions you assert that you know what you're + * doing and how to fix problems caused by using them. If you don't, you + * shouldn't be using them.** * * Before the inclusion of @ref glfw3native.h, you must define exactly one - * window API macro and exactly one context API macro. Failure to do this - * will cause a compile-time error. + * window system API macro and exactly one context creation API macro. Failure + * to do this will cause a compile-time error. * * The available window API macros are: * * `GLFW_EXPOSE_NATIVE_WIN32` @@ -71,8 +71,13 @@ extern "C" { *************************************************************************/ #if defined(GLFW_EXPOSE_NATIVE_WIN32) + // This is a workaround for the fact that glfw3.h needs to export APIENTRY (for + // example to allow applications to correctly declare a GL_ARB_debug_output + // callback) but windows.h assumes no one will define APIENTRY before it does + #undef APIENTRY #include #elif defined(GLFW_EXPOSE_NATIVE_COCOA) + #include #if defined(__OBJC__) #import #else @@ -80,8 +85,9 @@ extern "C" { #endif #elif defined(GLFW_EXPOSE_NATIVE_X11) #include + #include #else - #error "No window API specified" + #error "No window API selected" #endif #if defined(GLFW_EXPOSE_NATIVE_WGL) @@ -93,7 +99,7 @@ extern "C" { #elif defined(GLFW_EXPOSE_NATIVE_EGL) #include #else - #error "No context API specified" + #error "No context API selected" #endif @@ -102,8 +108,49 @@ extern "C" { *************************************************************************/ #if defined(GLFW_EXPOSE_NATIVE_WIN32) +/*! @brief Returns the adapter device name of the specified monitor. + * + * @return The UTF-8 encoded adapter device name (for example `\\.\DISPLAY1`) + * of the specified monitor, or `NULL` if an [error](@ref error_handling) + * occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.1. + * + * @ingroup native + */ +GLFWAPI const char* glfwGetWin32Adapter(GLFWmonitor* monitor); + +/*! @brief Returns the display device name of the specified monitor. + * + * @return The UTF-8 encoded display device name (for example + * `\\.\DISPLAY1\Monitor0`) of the specified monitor, or `NULL` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.1. + * + * @ingroup native + */ +GLFWAPI const char* glfwGetWin32Monitor(GLFWmonitor* monitor); + /*! @brief Returns the `HWND` of the specified window. - * @return The `HWND` of the specified window. + * + * @return The `HWND` of the specified window, or `NULL` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.0. + * * @ingroup native */ GLFWAPI HWND glfwGetWin32Window(GLFWwindow* window); @@ -111,15 +158,48 @@ GLFWAPI HWND glfwGetWin32Window(GLFWwindow* window); #if defined(GLFW_EXPOSE_NATIVE_WGL) /*! @brief Returns the `HGLRC` of the specified window. - * @return The `HGLRC` of the specified window. + * + * @return The `HGLRC` of the specified window, or `NULL` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.0. + * * @ingroup native */ GLFWAPI HGLRC glfwGetWGLContext(GLFWwindow* window); #endif #if defined(GLFW_EXPOSE_NATIVE_COCOA) +/*! @brief Returns the `CGDirectDisplayID` of the specified monitor. + * + * @return The `CGDirectDisplayID` of the specified monitor, or + * `kCGNullDirectDisplay` if an [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.1. + * + * @ingroup native + */ +GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* monitor); + /*! @brief Returns the `NSWindow` of the specified window. - * @return The `NSWindow` of the specified window. + * + * @return The `NSWindow` of the specified window, or `nil` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.0. + * * @ingroup native */ GLFWAPI id glfwGetCocoaWindow(GLFWwindow* window); @@ -127,7 +207,16 @@ GLFWAPI id glfwGetCocoaWindow(GLFWwindow* window); #if defined(GLFW_EXPOSE_NATIVE_NSGL) /*! @brief Returns the `NSOpenGLContext` of the specified window. - * @return The `NSOpenGLContext` of the specified window. + * + * @return The `NSOpenGLContext` of the specified window, or `nil` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.0. + * * @ingroup native */ GLFWAPI id glfwGetNSGLContext(GLFWwindow* window); @@ -135,12 +224,61 @@ GLFWAPI id glfwGetNSGLContext(GLFWwindow* window); #if defined(GLFW_EXPOSE_NATIVE_X11) /*! @brief Returns the `Display` used by GLFW. - * @return The `Display` used by GLFW. + * + * @return The `Display` used by GLFW, or `NULL` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.0. + * * @ingroup native */ GLFWAPI Display* glfwGetX11Display(void); + +/*! @brief Returns the `RRCrtc` of the specified monitor. + * + * @return The `RRCrtc` of the specified monitor, or `None` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.1. + * + * @ingroup native + */ +GLFWAPI RRCrtc glfwGetX11Adapter(GLFWmonitor* monitor); + +/*! @brief Returns the `RROutput` of the specified monitor. + * + * @return The `RROutput` of the specified monitor, or `None` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.1. + * + * @ingroup native + */ +GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* monitor); + /*! @brief Returns the `Window` of the specified window. - * @return The `Window` of the specified window. + * + * @return The `Window` of the specified window, or `None` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.0. + * * @ingroup native */ GLFWAPI Window glfwGetX11Window(GLFWwindow* window); @@ -148,7 +286,16 @@ GLFWAPI Window glfwGetX11Window(GLFWwindow* window); #if defined(GLFW_EXPOSE_NATIVE_GLX) /*! @brief Returns the `GLXContext` of the specified window. - * @return The `GLXContext` of the specified window. + * + * @return The `GLXContext` of the specified window, or `NULL` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.0. + * * @ingroup native */ GLFWAPI GLXContext glfwGetGLXContext(GLFWwindow* window); @@ -156,17 +303,46 @@ GLFWAPI GLXContext glfwGetGLXContext(GLFWwindow* window); #if defined(GLFW_EXPOSE_NATIVE_EGL) /*! @brief Returns the `EGLDisplay` used by GLFW. - * @return The `EGLDisplay` used by GLFW. + * + * @return The `EGLDisplay` used by GLFW, or `EGL_NO_DISPLAY` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.0. + * * @ingroup native */ GLFWAPI EGLDisplay glfwGetEGLDisplay(void); + /*! @brief Returns the `EGLContext` of the specified window. - * @return The `EGLContext` of the specified window. + * + * @return The `EGLContext` of the specified window, or `EGL_NO_CONTEXT` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.0. + * * @ingroup native */ GLFWAPI EGLContext glfwGetEGLContext(GLFWwindow* window); + /*! @brief Returns the `EGLSurface` of the specified window. - * @return The `EGLSurface` of the specified window. + * + * @return The `EGLSurface` of the specified window, or `EGL_NO_SURFACE` if an + * [error](@ref error_handling) occurred. + * + * @par Thread Safety + * This function may be called from any thread. Access is not synchronized. + * + * @par History + * Added in GLFW 3.0. + * * @ingroup native */ GLFWAPI EGLSurface glfwGetEGLSurface(GLFWwindow* window); diff --git a/external/glfw3/lib/linux/libglfw3.a b/external/glfw3/lib/linux/libglfw3.a new file mode 100644 index 000000000..84cc1d202 Binary files /dev/null and b/external/glfw3/lib/linux/libglfw3.a differ diff --git a/external/glfw3/lib/win32/libglfw3.a b/external/glfw3/lib/win32/libglfw3.a index 7138ee9b5..fc9203961 100644 Binary files a/external/glfw3/lib/win32/libglfw3.a and b/external/glfw3/lib/win32/libglfw3.a differ diff --git a/external/glfw3/lib/win32/libglfw3dll.a b/external/glfw3/lib/win32/libglfw3dll.a index 415d85630..f1bd73b17 100644 Binary files a/external/glfw3/lib/win32/libglfw3dll.a and b/external/glfw3/lib/win32/libglfw3dll.a differ diff --git a/games/just_do_GGJ2015/src/just_do.c b/games/just_do_GGJ2015/src/just_do.c new file mode 100644 index 000000000..beac9e149 --- /dev/null +++ b/games/just_do_GGJ2015/src/just_do.c @@ -0,0 +1,358 @@ +/******************************************************************************************* +* +* JUST DO - Global Game Jam 2015 Videogame +* Experimental puzzle game that lets the user try to find a logic solution to +* different shape-color-based situations. +* +* Developed by: Ramon Santamaria (Ray San) +* +* This game has been created using raylib (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* raylib - Copyright (c) 2015 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +********************************************************************************************/ + +#include "raylib.h" +#include "screens/screens.h" // NOTE: Defines currentScreen + +#if defined(PLATFORM_WEB) + #include +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- +const int screenWidth = 1280; // Moved to screens.h +const int screenHeight = 720; // Moved to screens.h + +// Required variables to manage screen transitions (fade-in, fade-out) +float transAlpha = 0; +bool onTransition = false; +bool transFadeOut = false; +int transFromScreen = -1; +int transToScreen = -1; +int framesCounter = 0; + +//static Sound levelWin; + +//---------------------------------------------------------------------------------- +// Local Functions Declaration +//---------------------------------------------------------------------------------- +void TransitionToScreen(int screen); +void UpdateTransition(void); +void DrawTransition(void); + +void UpdateDrawFrame(void); // Update and Draw one frame + +//---------------------------------------------------------------------------------- +// Main entry point +//---------------------------------------------------------------------------------- +int main(void) +{ + // Initialization + //--------------------------------------------------------- + const char windowTitle[30] = "JUST DO"; + + //SetupFlags(FLAG_FULLSCREEN_MODE); + InitWindow(screenWidth, screenHeight, windowTitle); + + // TODO: Load global data here (assets that must be available in all screens, i.e. fonts) + InitAudioDevice(); + + levelWin = LoadSound("resources/win.wav"); + + // Setup and Init first screen + currentScreen = LOGO; + InitLogoScreen(); + +#if defined(PLATFORM_WEB) + emscripten_set_main_loop(UpdateDrawFrame, 0, 1); +#else + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + + if (IsKeyPressed(KEY_SPACE)) PlaySound(levelWin); + + UpdateDrawFrame(); + } +#endif + + // De-Initialization + //-------------------------------------------------------------------------------------- + + // TODO: Unload all global loaded data (i.e. fonts) here! + UnloadSound(levelWin); + + CloseAudioDevice(); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +//---------------------------------------------------------------------------------- +// Local Functions Definition +//---------------------------------------------------------------------------------- +void TransitionToScreen(int screen) +{ + onTransition = true; + transFromScreen = currentScreen; + transToScreen = screen; +} + +void UpdateTransition(void) +{ + if (!transFadeOut) + { + transAlpha += 0.02f; + + if (transAlpha >= 1.0) + { + transAlpha = 1.0; + currentScreen = transToScreen; + transFadeOut = true; + framesCounter = 0; + } + } + else // Transition fade out logic + { + transAlpha -= 0.02f; + + if (transAlpha <= 0) + { + transAlpha = 0; + transFadeOut = false; + onTransition = false; + transFromScreen = -1; + transToScreen = -1; + } + } +} + +void DrawTransition(void) +{ + DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Fade(RAYWHITE, transAlpha)); +} + +void UpdateDrawFrame(void) +{ + // Update + //---------------------------------------------------------------------------------- + if (!onTransition) + { + if (IsKeyPressed('0')) + { + TransitionToScreen(LEVEL00); + InitLevel00Screen(); + } + else if (IsKeyPressed('1')) + { + TransitionToScreen(LEVEL01); + InitLevel01Screen(); + } + else if (IsKeyPressed('2')) + { + TransitionToScreen(LEVEL02); + InitLevel02Screen(); + } + else if (IsKeyPressed('3')) + { + TransitionToScreen(LEVEL03); + InitLevel03Screen(); + } + else if (IsKeyPressed('4')) + { + TransitionToScreen(LEVEL04); + InitLevel04Screen(); + } + else if (IsKeyPressed('5')) + { + TransitionToScreen(LEVEL05); + InitLevel05Screen(); + } + else if (IsKeyPressed('6')) + { + TransitionToScreen(LEVEL06); + InitLevel06Screen(); + } + else if (IsKeyPressed('7')) + { + TransitionToScreen(LEVEL07); + InitLevel07Screen(); + } + else if (IsKeyPressed('8')) + { + TransitionToScreen(LEVEL08); + InitLevel08Screen(); + } + else if (IsKeyPressed('9')) + { + TransitionToScreen(LEVEL09); + InitLevel08Screen(); + } + + switch(currentScreen) + { + case LOGO: + { + UpdateLogoScreen(); + + if (FinishLogoScreen()) + { + UnloadLogoScreen(); + TransitionToScreen(LEVEL00); + InitLevel00Screen(); + + PlayMusicStream("resources/ambient.ogg"); + SetMusicVolume(0.6f); + } + } break; + case LEVEL00: + { + UpdateLevel00Screen(); + + if (FinishLevel00Screen()) + { + UnloadLevel00Screen(); + TransitionToScreen(LEVEL01); + InitLevel01Screen(); + } + } break; + case LEVEL01: + { + UpdateLevel01Screen(); + + if (FinishLevel01Screen()) + { + UnloadLevel01Screen(); + TransitionToScreen(LEVEL02); + InitLevel02Screen(); + } + } break; + case LEVEL02: + { + UpdateLevel02Screen(); + + if (FinishLevel02Screen()) + { + UnloadLevel02Screen(); + TransitionToScreen(LEVEL03); + InitLevel03Screen(); + } + } break; + case LEVEL03: + { + UpdateLevel03Screen(); + + if (FinishLevel03Screen()) + { + UnloadLevel03Screen(); + TransitionToScreen(LEVEL04); + InitLevel04Screen(); + } + } break; + case LEVEL04: + { + UpdateLevel04Screen(); + + if (FinishLevel04Screen()) + { + UnloadLevel04Screen(); + TransitionToScreen(LEVEL05); + InitLevel05Screen(); + } + } break; + case LEVEL05: + { + UpdateLevel05Screen(); + + if (FinishLevel05Screen()) + { + UnloadLevel05Screen(); + TransitionToScreen(LEVEL06); + InitLevel06Screen(); + } + } break; + case LEVEL06: + { + UpdateLevel06Screen(); + + if (FinishLevel06Screen()) + { + UnloadLevel06Screen(); + TransitionToScreen(LEVEL07); + InitLevel07Screen(); + } + } break; + case LEVEL07: + { + UpdateLevel07Screen(); + + if (FinishLevel07Screen()) + { + UnloadLevel07Screen(); + TransitionToScreen(LEVEL08); + InitLevel08Screen(); + } + } break; + case LEVEL08: + { + UpdateLevel08Screen(); + + if (FinishLevel08Screen()) + { + UnloadLevel08Screen(); + TransitionToScreen(LEVEL09); + InitLevel09Screen(); + } + } break; + case LEVEL09: + { + UpdateLevel09Screen(); + + if (FinishLevel09Screen()) + { + UnloadLevel09Screen(); + TransitionToScreen(LEVEL00); + InitLevel00Screen(); + } + } break; + default: break; + } + } + else UpdateTransition(); // Update transition (fade-in, fade-out) + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + switch(currentScreen) + { + case LOGO: DrawLogoScreen(); break; + case LEVEL00: DrawLevel00Screen(); break; + case LEVEL01: DrawLevel01Screen(); break; + case LEVEL02: DrawLevel02Screen(); break; + case LEVEL03: DrawLevel03Screen(); break; + case LEVEL04: DrawLevel04Screen(); break; + case LEVEL05: DrawLevel05Screen(); break; + case LEVEL06: DrawLevel06Screen(); break; + case LEVEL07: DrawLevel07Screen(); break; + case LEVEL08: DrawLevel08Screen(); break; + case LEVEL09: DrawLevel09Screen(); break; + default: break; + } + + if (onTransition) DrawTransition(); + + EndDrawing(); + //---------------------------------------------------------------------------------- +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/makefile b/games/just_do_GGJ2015/src/makefile new file mode 100644 index 000000000..32bee4dc1 --- /dev/null +++ b/games/just_do_GGJ2015/src/makefile @@ -0,0 +1,246 @@ +#************************************************************************************************** +# +# raylib - Advance Game +# +# makefile to compile advance game for desktop platforms, Raspberry Pi and HTML5 (emscripten) +# +# Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +# +# This software is provided "as-is", without any express or implied warranty. In no event +# will the authors be held liable for any damages arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, including commercial +# applications, and to alter it and redistribute it freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not claim that you +# wrote the original software. If you use this software in a product, an acknowledgment +# in the product documentation would be appreciated but is not required. +# +# 2. Altered source versions must be plainly marked as such, and must not be misrepresented +# as being the original software. +# +# 3. This notice may not be removed or altered from any source distribution. +# +#************************************************************************************************** + +# define raylib platform to compile for +# possible platforms: PLATFORM_DESKTOP PLATFORM_RPI PLATFORM_WEB +# WARNING: To compile to HTML5, code must be redesigned to use emscripten.h and emscripten_set_main_loop() +PLATFORM ?= PLATFORM_DESKTOP + +# determine PLATFORM_OS in case PLATFORM_DESKTOP selected +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + # No uname.exe on MinGW!, but OS=Windows_NT on Windows! ifeq ($(UNAME),Msys) -> Windows + ifeq ($(OS),Windows_NT) + PLATFORM_OS=WINDOWS + LIBPATH=win32 + else + UNAMEOS:=$(shell uname) + ifeq ($(UNAMEOS),Linux) + PLATFORM_OS=LINUX + LIBPATH=linux + else + ifeq ($(UNAMEOS),Darwin) + PLATFORM_OS=OSX + LIBPATH=osx + endif + endif + endif +endif + +# define compiler: gcc for C program, define as g++ for C++ +ifeq ($(PLATFORM),PLATFORM_WEB) + # define emscripten compiler + CC = emcc +else +ifeq ($(PLATFORM_OS),OSX) + # define llvm compiler for mac + CC = clang +else + # define default gcc compiler + CC = gcc +endif +endif + +# define compiler flags: +# -O2 defines optimization level +# -Wall turns on most, but not all, compiler warnings +# -std=c99 use standard C from 1999 revision +ifeq ($(PLATFORM),PLATFORM_RPI) + CFLAGS = -O2 -Wall -std=gnu99 -fgnu89-inline +else + CFLAGS = -O2 -Wall -std=c99 +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + CFLAGS = -O2 -Wall -std=c99 -s USE_GLFW=3 + #-s ASSERTIONS=1 --preload-file resources + #-s ALLOW_MEMORY_GROWTH=1 # to allow memory resizing + #-s TOTAL_MEMORY=16777216 # to specify heap memory size (default = 16MB) +endif + +#CFLAGSEXTRA = -Wextra -Wmissing-prototypes -Wstrict-prototypes + +# define any directories containing required header files +ifeq ($(PLATFORM),PLATFORM_RPI) + INCLUDES = -I. -I../../src -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads +else + INCLUDES = -I. -I../../src -I../../../src +# external libraries headers +# GLFW3 + INCLUDES += -I../../external/glfw3/include +# GLEW + INCLUDES += -I../../external/glew/include +# OpenAL Soft + INCLUDES += -I../../external/openal_soft/include +endif + +# define library paths containing required libs +ifeq ($(PLATFORM),PLATFORM_RPI) + LFLAGS = -L. -L../../src -L/opt/vc/lib +else + LFLAGS = -L. -L../../src -L../../../src + # external libraries to link with + # GLFW3 + LFLAGS += -L../../external/glfw3/lib/$(LIBPATH) + ifneq ($(PLATFORM_OS),OSX) + # OpenAL Soft + LFLAGS += -L../../external/openal_soft/lib/$(LIBPATH) + # GLEW + LFLAGS += -L../../external/glew/lib/$(LIBPATH) + endif +endif + +# define any libraries to link into executable +# if you want to link libraries (libname.so or libname.a), use the -lname +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),LINUX) + # libraries for Debian GNU/Linux desktop compiling + # requires the following packages: + # libglfw3-dev libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw -lGLEW -lGL -lopenal + endif + ifeq ($(PLATFORM_OS),OSX) + # libraries for OS X 10.9 desktop compiling + # requires the following packages: + # libglfw3-dev libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw -framework OpenGL -framework OpenAl -framework Cocoa + else + # libraries for Windows desktop compiling + # NOTE: GLFW3 and OpenAL Soft libraries should be installed + LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 + endif +endif +ifeq ($(PLATFORM),PLATFORM_RPI) + # libraries for Raspberry Pi compiling + # NOTE: OpenAL Soft library should be installed (libopenal1 package) + LIBS = -lraylib -lGLESv2 -lEGL -lpthread -lrt -lm -lbcm_host -lopenal +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + LIBS = ../../src/libraylib.bc +endif + +# define additional parameters and flags for windows +ifeq ($(PLATFORM_OS),WINDOWS) + # resources file contains windows exe icon + # -Wl,--subsystem,windows hides the console window + WINFLAGS = ../../src/resources -Wl,--subsystem,windows +endif + +ifeq ($(PLATFORM),PLATFORM_WEB) + EXT = .html +endif + +# define all screen object files required +SCREENS = \ + screens/screen_logo.o \ + screens/screen_level00.o \ + screens/screen_level01.o \ + screens/screen_level02.o \ + screens/screen_level03.o \ + screens/screen_level04.o \ + screens/screen_level05.o \ + screens/screen_level06.o \ + screens/screen_level07.o \ + screens/screen_level08.o \ + screens/screen_level09.o \ + +# typing 'make' will invoke the first target entry in the file, +# in this case, the 'default' target entry is just_do +default: just_do + +# compile just_do +just_do: just_do.c $(SCREENS) + $(CC) -o $@$(EXT) $< $(SCREENS) $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) $(WINFLAGS) + +# compile screen LOGO +screens/screen_logo.o: screens/screen_logo.c + $(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile screen LEVEL00 +screens/screen_level00.o: screens/screen_level00.c + $(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile screen LEVEL01 +screens/screen_level01.o: screens/screen_level01.c + $(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile screen LEVEL02 +screens/screen_level02.o: screens/screen_level02.c + $(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile screen LEVEL03 +screens/screen_level03.o: screens/screen_level03.c + $(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile screen LEVEL04 +screens/screen_level04.o: screens/screen_level04.c + $(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile screen LEVEL05 +screens/screen_level05.o: screens/screen_level05.c + $(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile screen LEVEL06 +screens/screen_level06.o: screens/screen_level06.c + $(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile screen LEVEL07 +screens/screen_level07.o: screens/screen_level07.c + $(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile screen LEVEL08 +screens/screen_level08.o: screens/screen_level08.c + $(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile screen LEVEL09 +screens/screen_level09.o: screens/screen_level09.c + $(CC) -c $< -o $@ $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# clean everything +clean: +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),OSX) + find . -type f -perm +ugo+x -delete + rm -f *.o + else + ifeq ($(PLATFORM_OS),LINUX) + find . -type f -executable -delete + rm -f *.o + else + del *.o *.exe + endif + endif +endif +ifeq ($(PLATFORM),PLATFORM_RPI) + find . -type f -executable -delete + rm -f *.o +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + del *.o *.html *.js +endif + @echo Cleaning done + +# instead of defining every module one by one, we can define a pattern +# this pattern below will automatically compile every module defined on $(OBJS) +#%.exe : %.c +# $(CC) -o $@ $< $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) diff --git a/games/just_do_GGJ2015/src/resources/ambient.ogg b/games/just_do_GGJ2015/src/resources/ambient.ogg new file mode 100644 index 000000000..af7f836ed Binary files /dev/null and b/games/just_do_GGJ2015/src/resources/ambient.ogg differ diff --git a/games/just_do_GGJ2015/src/resources/win.wav b/games/just_do_GGJ2015/src/resources/win.wav new file mode 100644 index 000000000..72520f993 Binary files /dev/null and b/games/just_do_GGJ2015/src/resources/win.wav differ diff --git a/games/just_do_GGJ2015/src/screens/screen_level00.c b/games/just_do_GGJ2015/src/screens/screen_level00.c new file mode 100644 index 000000000..99f298492 --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_level00.c @@ -0,0 +1,167 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Level00 Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Level00 screen global variables +static int framesCounter; +static int finishScreen; + +static Rectangle boundsU, boundsO; + +static bool mouseOverU = false; +static bool mouseOverO = false; +static bool placedU = false; +static bool placedO = false; + +static bool done = false; +static int levelTimeSec = 0; +static bool levelFinished = false; + +//---------------------------------------------------------------------------------- +// Level00 Screen Functions Definition +//---------------------------------------------------------------------------------- + +// Level00 Screen Initialization logic +void InitLevel00Screen(void) +{ + // Initialize Level00 screen variables here! + framesCounter = 0; + finishScreen = 0; + + boundsU = (Rectangle){GetScreenWidth()/2 - 265, -200, MeasureText("U", 160) + 40, 160 }; + boundsO = (Rectangle){GetScreenWidth() - 370, -30, MeasureText("O", 160) + 40, 160 }; +} + +// Level00 Screen Update logic +void UpdateLevel00Screen(void) +{ + // Update Level00 screen variables here! + if (!done) framesCounter++; + + if (!done) + { + if (!placedU) boundsU.y += 2; + + if (boundsU.y >= GetScreenHeight()) boundsU.y = -boundsU.height; + + Vector2 mousePos = GetMousePosition(); + + if (CheckCollisionPointRec(mousePos, boundsU)) + { + mouseOverU = true; + + if (!placedU && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if ((boundsU.y > GetScreenHeight()/2 - 110) && ((boundsU.y + boundsU.height) < (GetScreenHeight()/2 + 100))) + { + placedU = true; + } + } + } + else mouseOverU = false; + + if (CheckCollisionPointRec(mousePos, boundsO)) + { + mouseOverO = true; + + if (!placedO && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) boundsO.y += 100; + + if (boundsO.y >= (GetScreenHeight()/2 - 130)) placedO = true; + } + else mouseOverO = false; + + if (placedO && placedU) + { + done = true; + PlaySound(levelWin); + } + } + + if (done && !levelFinished) + { + levelTimeSec = framesCounter/60; + levelFinished = true; + framesCounter = 0; + } + + if (levelFinished) + { + framesCounter++; + + if ((framesCounter > 30) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) finishScreen = true; + } +} + +// Level00 Screen Draw logic +void DrawLevel00Screen(void) +{ + // Draw Level00 screen + DrawText("U", boundsU.x, boundsU.y + 10, 160, GRAY); + DrawText("J", GetScreenWidth()/2 - MeasureText("JUST DO", 160)/2, GetScreenHeight()/2 - 80, 160, GRAY); + DrawText("ST D", GetScreenWidth()/2 - MeasureText("JUST DO", 160)/2 + 210, GetScreenHeight()/2 - 80, 160, GRAY); + DrawText("O", boundsO.x, boundsO.y + 10, 160, GRAY); + + DrawText("by RAMON SANTAMARIA (@raysan5)", 370, GetScreenHeight()/2 + 100, 30, Fade(LIGHTGRAY, 0.4f)); + + if (mouseOverU && !placedU) DrawRectangleLines(boundsU.x - 20, boundsU.y, boundsU.width, boundsU.height, Fade(LIGHTGRAY, 0.8f)); + //DrawRectangleBordersRec(boundsU, -20, 0, 20, Fade(RED, 0.3f)); + + if (mouseOverO && !placedO) DrawRectangleLines(boundsO.x - 20, boundsO.y, boundsO.width, boundsO.height, Fade(LIGHTGRAY, 0.8f)); + //DrawRectangleBordersRec(boundsO, -20, 0, 20, Fade(RED, 0.3f)); + + if (levelFinished) + { + DrawRectangleBordersRec((Rectangle){0, 0, GetScreenWidth(), GetScreenHeight()}, 0, 0, 60, Fade(LIGHTGRAY, 0.6f)); + DrawText("LEVEL 00", GetScreenWidth()/2 - MeasureText("LEVEL 00", 30)/2, 20, 30, GRAY); + DrawText(FormatText("DONE! (Seconds: %03i)", levelTimeSec), GetScreenWidth()/2 - MeasureText("DONE! (Seconds: 000)", 30)/2, GetScreenHeight() - 40, 30, GRAY); + } + else DrawText("LEVEL 00", GetScreenWidth()/2 - MeasureText("LEVEL 00", 30)/2, 20, 30, LIGHTGRAY); +} + +// Level00 Screen Unload logic +void UnloadLevel00Screen(void) +{ + // TODO: Unload Level00 screen variables here! +} + +// Level00 Screen should finish? +int FinishLevel00Screen(void) +{ + return finishScreen; +} + +void DrawRectangleBordersRec(Rectangle rec, int offsetX, int offsetY, int borderSize, Color col) +{ + DrawRectangle(rec.x + offsetX, rec.y + offsetY, rec.width, borderSize, col); + DrawRectangle(rec.x + offsetX, rec.y + borderSize + offsetY, borderSize, rec.height - borderSize*2, col); + DrawRectangle(rec.x + rec.width - borderSize + offsetX, rec.y + borderSize + offsetY, borderSize, rec.height - borderSize*2, col); + DrawRectangle(rec.x + offsetX, rec.y + rec.height - borderSize + offsetY, rec.width, borderSize, col); +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screen_level01.c b/games/just_do_GGJ2015/src/screens/screen_level01.c new file mode 100644 index 000000000..cedcb2e0c --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_level01.c @@ -0,0 +1,163 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Level01 Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Level01 screen global variables +static int framesCounter; +static int finishScreen; + +static Rectangle innerLeftRec, outerLeftRec; +static Rectangle innerRightRec, outerRightRec; + +static bool done = false; +static int levelTimeSec = 0; +static bool levelFinished = false; + +//---------------------------------------------------------------------------------- +// Level01 Screen Functions Definition +//---------------------------------------------------------------------------------- + +// Level01 Screen Initialization logic +void InitLevel01Screen(void) +{ + // Initialize Level01 screen variables here! + framesCounter = 0; + finishScreen = 0; + + outerLeftRec = (Rectangle){ 0, 0, GetScreenWidth()/2, GetScreenHeight() }; + outerRightRec = (Rectangle){ GetScreenWidth()/2, 0, GetScreenWidth()/2, GetScreenHeight() }; + + innerLeftRec = (Rectangle){ GetScreenWidth()/4 - 200, GetScreenHeight()/2 - 200, 400, 400}; + innerRightRec = (Rectangle){ GetScreenWidth()/2 + GetScreenWidth()/4 - 200, GetScreenHeight()/2 - 200, 400, 400}; +} + +// Level01 Screen Update logic +void UpdateLevel01Screen(void) +{ + // Update Level01 screen + framesCounter++; + + if (!done) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (CheckCollisionPointRec(GetMousePosition(), innerLeftRec)) + { + if (innerRightRec.width > 0) + { + innerRightRec.x += 20; + innerRightRec.y += 20; + innerRightRec.width -= 40; + innerRightRec.height -= 40; + } + } + else if (CheckCollisionPointRec(GetMousePosition(), innerRightRec)) + { + if (innerLeftRec.width > 0) + { + innerLeftRec.x += 20; + innerLeftRec.y += 20; + innerLeftRec.width -= 40; + innerLeftRec.height -= 40; + } + } + else if (CheckCollisionPointRec(GetMousePosition(), outerLeftRec)) + { + innerLeftRec.x -= 20; + innerLeftRec.y -= 20; + innerLeftRec.width += 40; + innerLeftRec.height += 40; + } + else if (CheckCollisionPointRec(GetMousePosition(), outerRightRec)) + { + innerRightRec.x -= 20; + innerRightRec.y -= 20; + innerRightRec.width += 40; + innerRightRec.height += 40; + } + } + + + if (((innerRightRec.width == 0) && (innerLeftRec.height >= GetScreenHeight())) || + ((innerLeftRec.width == 0) && (innerRightRec.height >= GetScreenHeight()))) + { + done = true; + PlaySound(levelWin); + } + } + + if (done && !levelFinished) + { + levelTimeSec = framesCounter/60; + levelFinished = true; + framesCounter = 0; + } + + if (levelFinished) + { + framesCounter++; + + if ((framesCounter > 90) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) finishScreen = true; + } +} + +// Level01 Screen Draw logic +void DrawLevel01Screen(void) +{ + // Draw Level01 screen + if (!levelFinished) DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), LIGHTGRAY); + else DrawRectangle(60, 60, GetScreenWidth() - 120, GetScreenHeight() - 120, LIGHTGRAY); + + DrawRectangleRec(outerLeftRec, GRAY); + DrawRectangleRec(innerLeftRec, RAYWHITE); + DrawRectangleRec(outerRightRec, RAYWHITE); + DrawRectangleRec(innerRightRec, GRAY); + + if (levelFinished) + { + DrawRectangleBordersRec((Rectangle){0, 0, GetScreenWidth(), GetScreenHeight()}, 0, 0, 60, Fade(LIGHTGRAY, 0.6f)); + DrawText("LEVEL 01", GetScreenWidth()/2 - MeasureText("LEVEL 01", 30)/2, 20, 30, GRAY); + DrawText(FormatText("DONE! (Seconds: %03i)", levelTimeSec), GetScreenWidth()/2 - MeasureText("DONE! (Seconds: 000)", 30)/2, GetScreenHeight() - 40, 30, GRAY); + } + else DrawText("LEVEL 01", GetScreenWidth()/2 - MeasureText("LEVEL 01", 30)/2, 20, 30, LIGHTGRAY); +} + +// Level01 Screen Unload logic +void UnloadLevel01Screen(void) +{ + // TODO: Unload Level01 screen variables here! +} + +// Level01 Screen should finish? +int FinishLevel01Screen(void) +{ + return finishScreen; +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screen_level02.c b/games/just_do_GGJ2015/src/screens/screen_level02.c new file mode 100644 index 000000000..ccfa355e8 --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_level02.c @@ -0,0 +1,170 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Level02 Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +#include + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Level02 screen global variables +static int framesCounter; +static int finishScreen; + +static Vector2 bouncingBallPos; +static float bouncingBallRadius = 40; +static Vector2 bouncingBallSpeed; + +static Vector2 holeCirclePos; +static float holeCircleRadius = 50; + +static bool ballOnHole = false; + +static int levelTimeSec = 0; +static bool levelFinished = false; + +//---------------------------------------------------------------------------------- +// Level02 Screen Functions Definition +//---------------------------------------------------------------------------------- +float Vector2Distance(Vector2 v1, Vector2 v2); + +// Level02 Screen Initialization logic +void InitLevel02Screen(void) +{ + // TODO: Initialize Level02 screen variables here! + framesCounter = 0; + finishScreen = 0; + + bouncingBallPos = (Vector2){ 120, 80 }; + bouncingBallSpeed = (Vector2){ 6, 8 }; + holeCirclePos = (Vector2){ GetScreenWidth()/2, GetScreenHeight()/2 }; +} + +// Level02 Screen Update logic +void UpdateLevel02Screen(void) +{ + // Update Level02 screen + framesCounter++; + + if (!ballOnHole) + { + bouncingBallPos.x += bouncingBallSpeed.x; + bouncingBallPos.y += bouncingBallSpeed.y; + + if (((bouncingBallPos.x - bouncingBallRadius) <= 0) || ((bouncingBallPos.x + bouncingBallRadius) >= GetScreenWidth())) bouncingBallSpeed.x *= -1; + if (((bouncingBallPos.y - bouncingBallRadius) <= 0) || ((bouncingBallPos.y + bouncingBallRadius) >= GetScreenHeight())) bouncingBallSpeed.y *= -1; + + Vector2 mousePos = GetMousePosition(); + + if (CheckCollisionPointCircle(mousePos, bouncingBallPos, 120)) + { + bouncingBallPos.x = GetRandomValue(80, 1200); + bouncingBallPos.y = GetRandomValue(80, 650); + } + + if (CheckCollisionPointCircle(mousePos, holeCirclePos, 120)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + holeCirclePos = mousePos; + + if ((holeCirclePos.x - holeCircleRadius) <= 0) holeCirclePos.x = holeCircleRadius; + else if ((holeCirclePos.x + holeCircleRadius) >= GetScreenWidth()) holeCirclePos.x = GetScreenWidth() - holeCircleRadius; + + if ((holeCirclePos.y - holeCircleRadius) <= 0) holeCirclePos.y = holeCircleRadius; + else if ((holeCirclePos.y + holeCircleRadius) >= GetScreenHeight()) holeCirclePos.y = GetScreenHeight() - holeCircleRadius; + } + } + + if (Vector2Distance(bouncingBallPos, holeCirclePos) < 20) + { + ballOnHole = true; + PlaySound(levelWin); + } + } + + if (ballOnHole && !levelFinished) + { + levelTimeSec = framesCounter/60; + levelFinished = true; + framesCounter = 0; + } + + if (levelFinished) + { + framesCounter++; + + if ((framesCounter > 90) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) finishScreen = true; + } +} + +// Level02 Screen Draw logic +void DrawLevel02Screen(void) +{ + // Draw Level02 screen + + DrawCircleV(holeCirclePos, holeCircleRadius, LIGHTGRAY); + DrawCircleV(bouncingBallPos, bouncingBallRadius, DARKGRAY); + + DrawCircleLines(bouncingBallPos.x, bouncingBallPos.y, 120, Fade(LIGHTGRAY, 0.8f)); + + + + if (levelFinished) + { + DrawRectangleBordersRec((Rectangle){0, 0, GetScreenWidth(), GetScreenHeight()}, 0, 0, 60, Fade(LIGHTGRAY, 0.6f)); + DrawText("LEVEL 02", GetScreenWidth()/2 - MeasureText("LEVEL 02", 30)/2, 20, 30, GRAY); + DrawText(FormatText("DONE! (Seconds: %03i)", levelTimeSec), GetScreenWidth()/2 - MeasureText("DONE! (Seconds: 000)", 30)/2, GetScreenHeight() - 40, 30, GRAY); + } + else DrawText("LEVEL 02", GetScreenWidth()/2 - MeasureText("LEVEL 02", 30)/2, 20, 30, LIGHTGRAY); +} + +// Level02 Screen Unload logic +void UnloadLevel02Screen(void) +{ + // TODO: Unload Level02 screen variables here! +} + +// Level02 Screen should finish? +int FinishLevel02Screen(void) +{ + return finishScreen; +} + +// Calculate distance between two points +float Vector2Distance(Vector2 v1, Vector2 v2) +{ + float result; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + + result = sqrt(dx*dx + dy*dy); + + return result; +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screen_level03.c b/games/just_do_GGJ2015/src/screens/screen_level03.c new file mode 100644 index 000000000..e87324149 --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_level03.c @@ -0,0 +1,134 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Level03 Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Level03 screen global variables +static int framesCounter; +static int finishScreen; + +static Rectangle holeRec, pieceRec; +static bool showPiece = false; +static bool pieceSelected = false; + +static bool done = false; +static int levelTimeSec = 0; +static bool levelFinished = false; + +//---------------------------------------------------------------------------------- +// Level03 Screen Functions Definition +//---------------------------------------------------------------------------------- + +// Level03 Screen Initialization logic +void InitLevel03Screen(void) +{ + // Initialize Level03 screen variables here! + framesCounter = 0; + finishScreen = 0; + + holeRec = (Rectangle){ GetScreenWidth()/2 - 50, GetScreenHeight()/2 - 50, 100, 100 }; + pieceRec = (Rectangle){ 200, 400, 100, 100 }; +} + +// Level03 Screen Update logic +void UpdateLevel03Screen(void) +{ + // Update Level03 screen variables here! + framesCounter++; + + Vector2 mousePos = GetMousePosition(); + + if (!done) + { + if (CheckCollisionPointRec(mousePos, holeRec)) showPiece = true; + else showPiece = false; + + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CheckCollisionPointRec(mousePos, pieceRec)) + { + pieceSelected = true; + + pieceRec.x = ((int)mousePos.x - 50); + pieceRec.y = ((int)mousePos.y - 50); + } + } + + if ((pieceRec.x == holeRec.x) && !(CheckCollisionPointRec(mousePos, holeRec))) + { + done = true; + PlaySound(levelWin); + } + } + + if (done && !levelFinished) + { + levelTimeSec = framesCounter/60; + levelFinished = true; + framesCounter = 0; + } + + if (levelFinished) + { + framesCounter++; + + if ((framesCounter > 90) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) finishScreen = true; + } +} + +// Level03 Screen Draw logic +void DrawLevel03Screen(void) +{ + // Draw Level03 screen + DrawRectangleRec(holeRec, GRAY); + DrawRectangleRec(pieceRec, RAYWHITE); + + if (showPiece) DrawRectangleLines(pieceRec.x, pieceRec.y, pieceRec.width, pieceRec.height, Fade(LIGHTGRAY, 0.8f)); + + if (levelFinished) + { + DrawRectangleBordersRec((Rectangle){0, 0, GetScreenWidth(), GetScreenHeight()}, 0, 0, 60, Fade(LIGHTGRAY, 0.6f)); + DrawText("LEVEL 03", GetScreenWidth()/2 - MeasureText("LEVEL 03", 30)/2, 20, 30, GRAY); + DrawText(FormatText("DONE! (Seconds: %03i)", levelTimeSec), GetScreenWidth()/2 - MeasureText("DONE! (Seconds: 000)", 30)/2, GetScreenHeight() - 40, 30, GRAY); + } + else DrawText("LEVEL 03", GetScreenWidth()/2 - MeasureText("LEVEL 03", 30)/2, 20, 30, LIGHTGRAY); +} + +// Level03 Screen Unload logic +void UnloadLevel03Screen(void) +{ + // TODO: Unload Level03 screen variables here! +} + +// Level03 Screen should finish? +int FinishLevel03Screen(void) +{ + return finishScreen; +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screen_level04.c b/games/just_do_GGJ2015/src/screens/screen_level04.c new file mode 100644 index 000000000..c4e4e2c00 --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_level04.c @@ -0,0 +1,147 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Level04 Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Level04 screen global variables +static int framesCounter; +static int finishScreen; + +static Vector2 circlesCenter; +static float innerCircleRadius = 40; +static float outerCircleRadius = 300; + +static bool done = false; +static int levelTimeSec = 0; +static bool levelFinished = false; + +//---------------------------------------------------------------------------------- +// Level04 Screen Functions Definition +//---------------------------------------------------------------------------------- + +// Level04 Screen Initialization logic +void InitLevel04Screen(void) +{ + // Initialize Level04 screen variables here! + framesCounter = 0; + finishScreen = 0; + + circlesCenter = (Vector2){ GetScreenWidth()/2, GetScreenHeight()/2 }; +} + +// Level04 Screen Update logic +void UpdateLevel04Screen(void) +{ + // Update Level04 screen variables here! + framesCounter++; + + if (!done) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CheckCollisionPointCircle(GetMousePosition(), circlesCenter, innerCircleRadius)) + { + innerCircleRadius += 2; + } + else if (CheckCollisionPointCircle(GetMousePosition(), circlesCenter, outerCircleRadius)) + { + outerCircleRadius += 2; + } + else + { + outerCircleRadius -= 2; + + if (outerCircleRadius <= 260) outerCircleRadius = 260; + } + } + else + { + if (!done) + { + innerCircleRadius -= 2; + if (outerCircleRadius > 300) outerCircleRadius -= 2; + } + } + + if (innerCircleRadius >= 270) innerCircleRadius = 270; + else if (innerCircleRadius <= 40) innerCircleRadius = 40; + + if (outerCircleRadius >= 600) outerCircleRadius = 600; + + if (innerCircleRadius >= outerCircleRadius) + { + done = true; + PlaySound(levelWin); + } + } + + if (done && !levelFinished) + { + levelTimeSec = framesCounter/60; + levelFinished = true; + framesCounter = 0; + } + + if (levelFinished) + { + framesCounter++; + + if ((framesCounter > 90) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) finishScreen = true; + } +} + +// Level04 Screen Draw logic +void DrawLevel04Screen(void) +{ + // Draw Level04 screen here! + //DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), GRAY); + DrawCircleV(circlesCenter, outerCircleRadius, GRAY); + DrawCircleV(circlesCenter, innerCircleRadius, RAYWHITE); + + if (levelFinished) + { + DrawRectangleBordersRec((Rectangle){0, 0, GetScreenWidth(), GetScreenHeight()}, 0, 0, 60, Fade(LIGHTGRAY, 0.6f)); + DrawText("LEVEL 04", GetScreenWidth()/2 - MeasureText("LEVEL 04", 30)/2, 20, 30, GRAY); + DrawText(FormatText("DONE! (Seconds: %03i)", levelTimeSec), GetScreenWidth()/2 - MeasureText("DONE! (Seconds: 000)", 30)/2, GetScreenHeight() - 40, 30, GRAY); + } + else DrawText("LEVEL 04", GetScreenWidth()/2 - MeasureText("LEVEL 04", 30)/2, 20, 30, LIGHTGRAY); +} + +// Level04 Screen Unload logic +void UnloadLevel04Screen(void) +{ + // TODO: Unload Level04 screen variables here! +} + +// Level04 Screen should finish? +int FinishLevel04Screen(void) +{ + return finishScreen; +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screen_level05.c b/games/just_do_GGJ2015/src/screens/screen_level05.c new file mode 100644 index 000000000..f2e4d852a --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_level05.c @@ -0,0 +1,185 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Level05 Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +#define NUM_CIRCLES 10 + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Level05 screen global variables +static int framesCounter; +static int finishScreen; + +static Vector2 circleCenter; +static float circleRadius[NUM_CIRCLES]; +static bool circleLocked[NUM_CIRCLES]; +static Color circleColor[NUM_CIRCLES]; + +static bool done = false; +static int levelTimeSec = 0; +static bool levelFinished = false; + +//---------------------------------------------------------------------------------- +// Level05 Screen Functions Definition +//---------------------------------------------------------------------------------- +static bool CheckColor(Color col1, Color col2); + +// Level05 Screen Initialization logic +void InitLevel05Screen(void) +{ + // Initialize Level05 screen variables here! + framesCounter = 0; + finishScreen = 0; + + circleCenter = (Vector2){ GetScreenWidth()/2, GetScreenHeight()/2 }; + + for (int i = 0; i < NUM_CIRCLES; i++) + { + circleRadius[i] = 760/NUM_CIRCLES*(NUM_CIRCLES - i); + circleLocked[i] = false; + } + + // That's a dirty hack to give sonme coherence to this puzzle... + circleColor[9] = GRAY; + circleColor[8] = RAYWHITE; + circleColor[7] = RAYWHITE; + circleColor[6] = GRAY; + circleColor[5] = RAYWHITE; + circleColor[4] = GRAY; + circleColor[3] = GRAY; + circleColor[2] = GRAY; + circleColor[1] = RAYWHITE; + circleColor[0] = GRAY; +} + +// Level05 Screen Update logic +void UpdateLevel05Screen(void) +{ + // Update Level05 screen variables here! + framesCounter++; + + if (!done) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + for (int i = NUM_CIRCLES - 1; i >= 0; i--) + { + if (CheckCollisionPointCircle(GetMousePosition(), circleCenter, circleRadius[i])) + { + if (i == 0) + { + if (CheckColor(circleColor[8], GRAY)) circleColor[8] = RAYWHITE; + else circleColor[8] = GRAY; + } + else if (i == 2) + { + if (CheckColor(circleColor[5], GRAY)) circleColor[5] = RAYWHITE; + else circleColor[5] = GRAY; + } + else if (i == 3) + { + if (CheckColor(circleColor[6], GRAY)) circleColor[6] = RAYWHITE; + else circleColor[6] = GRAY; + } + else + { + if (CheckColor(circleColor[i], GRAY)) circleColor[i] = RAYWHITE; + else circleColor[i] = GRAY; + } + return; + } + } + } + + // Check all cicles done + for (int i = 0; i < NUM_CIRCLES; i++) + { + done = true; + + if (CheckColor(circleColor[i], RAYWHITE)) + { + done = false; + return; + } + + //if (done) PlaySound(levelWin); + } + } + + if (done && !levelFinished) + { + levelTimeSec = framesCounter/60; + levelFinished = true; + framesCounter = 0; + } + + if (levelFinished) + { + framesCounter++; + + if ((framesCounter > 90) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) finishScreen = true; + } +} + +// Level05 Screen Draw logic +void DrawLevel05Screen(void) +{ + // Draw Level05 screen + for (int i = 0; i < NUM_CIRCLES; i++) + { + DrawPoly(circleCenter, 64, circleRadius[i], 0.0f, circleColor[i]); + } + + + + if (levelFinished) + { + DrawRectangleBordersRec((Rectangle){0, 0, GetScreenWidth(), GetScreenHeight()}, 0, 0, 60, Fade(LIGHTGRAY, 0.6f)); + DrawText("LEVEL 05", GetScreenWidth()/2 - MeasureText("LEVEL 05", 30)/2, 20, 30, GRAY); + DrawText(FormatText("DONE! (Seconds: %03i)", levelTimeSec), GetScreenWidth()/2 - MeasureText("DONE! (Seconds: 000)", 30)/2, GetScreenHeight() - 40, 30, GRAY); + } + else DrawText("LEVEL 05", GetScreenWidth()/2 - MeasureText("LEVEL 05", 30)/2, 20, 30, LIGHTGRAY); +} + +// Level05 Screen Unload logic +void UnloadLevel05Screen(void) +{ + // TODO: Unload Level05 screen variables here! +} + +// Level05 Screen should finish? +int FinishLevel05Screen(void) +{ + return finishScreen; +} + +static bool CheckColor(Color col1, Color col2) +{ + return ((col1.r == col2.r) && (col1.g == col2.g) && (col1.b == col2.b) && (col1.a == col2.a)); +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screen_level06.c b/games/just_do_GGJ2015/src/screens/screen_level06.c new file mode 100644 index 000000000..b5881db41 --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_level06.c @@ -0,0 +1,156 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Level06 Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Level06 screen global variables +static int framesCounter; +static int finishScreen; + +static Rectangle centerRec; + +static Rectangle movingRecs[4]; +static int speedRecs[4]; +static bool stoppedRec[4]; +static int mouseOverNum = -1; + +static bool done = false; +static int levelTimeSec = 0; +static bool levelFinished = false; + +//---------------------------------------------------------------------------------- +// Level06 Screen Functions Definition +//---------------------------------------------------------------------------------- + +// Level06 Screen Initialization logic +void InitLevel06Screen(void) +{ + // Initialize Level06 screen variables here! + framesCounter = 0; + finishScreen = 0; + + centerRec = (Rectangle){ GetScreenWidth()/2 - 100, 0, 200, GetScreenHeight() }; + + for (int i = 0; i < 4; i++) + { + movingRecs[i] = (Rectangle){ GetRandomValue(0, 5)*150, (i*150) + 90, 100, 100 }; + stoppedRec[i] = false; + speedRecs[i] = GetRandomValue(4, 8); + } +} + +// Level06 Screen Update logic +void UpdateLevel06Screen(void) +{ + // Update Level06 screen variables here! + framesCounter++; + + if (!done) + { + for (int i = 0; i < 4; i++) + { + if (!stoppedRec[i]) movingRecs[i].x += speedRecs[i]; + + if (movingRecs[i].x >= GetScreenWidth()) movingRecs[i].x = -movingRecs[i].width; + + if (CheckCollisionPointRec(GetMousePosition(), movingRecs[i])) + { + mouseOverNum = i; + + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (i == 0) stoppedRec[3] = !stoppedRec[3]; + else if (i == 1) stoppedRec[2] = !stoppedRec[2]; + else if (i == 2) stoppedRec[0] = !stoppedRec[0]; + else if (i == 3) stoppedRec[1] = !stoppedRec[1]; + } + } + } + + // Check if all boxes are aligned + if (((movingRecs[0].x > centerRec.x) && ((movingRecs[0].x + movingRecs[0].width) < (centerRec.x + centerRec.width))) && + ((movingRecs[1].x > centerRec.x) && ((movingRecs[1].x + movingRecs[1].width) < (centerRec.x + centerRec.width))) && + ((movingRecs[2].x > centerRec.x) && ((movingRecs[2].x + movingRecs[2].width) < (centerRec.x + centerRec.width))) && + ((movingRecs[3].x > centerRec.x) && ((movingRecs[3].x + movingRecs[3].width) < (centerRec.x + centerRec.width)))) + { + done = true; + PlaySound(levelWin); + } + } + + + if (done && !levelFinished) + { + levelTimeSec = framesCounter/60; + levelFinished = true; + framesCounter = 0; + } + + if (levelFinished) + { + framesCounter++; + + if ((framesCounter > 90) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) finishScreen = true; + } +} + +// Level06 Screen Draw logic +void DrawLevel06Screen(void) +{ + // Draw Level06 screen + DrawRectangleRec(centerRec, LIGHTGRAY); + + for (int i = 0; i < 4; i++) + { + DrawRectangleRec(movingRecs[i], GRAY); + } + + if (!done & (mouseOverNum >= 0)) DrawRectangleLines(movingRecs[mouseOverNum].x - 5, movingRecs[mouseOverNum].y - 5, movingRecs[mouseOverNum].width + 10, movingRecs[mouseOverNum].height + 10, Fade(LIGHTGRAY, 0.8f)); + + if (levelFinished) + { + DrawRectangleBordersRec((Rectangle){0, 0, GetScreenWidth(), GetScreenHeight()}, 0, 0, 60, Fade(LIGHTGRAY, 0.6f)); + DrawText("LEVEL 06", GetScreenWidth()/2 - MeasureText("LEVEL 06", 30)/2, 20, 30, GRAY); + DrawText(FormatText("DONE! (Seconds: %03i)", levelTimeSec), GetScreenWidth()/2 - MeasureText("DONE! (Seconds: 000)", 30)/2, GetScreenHeight() - 40, 30, GRAY); + } + else DrawText("LEVEL 06", GetScreenWidth()/2 - MeasureText("LEVEL 06", 30)/2, 20, 30, LIGHTGRAY); +} + +// Level06 Screen Unload logic +void UnloadLevel06Screen(void) +{ + // TODO: Unload Level06 screen variables here! +} + +// Level06 Screen should finish? +int FinishLevel06Screen(void) +{ + return finishScreen; +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screen_level07.c b/games/just_do_GGJ2015/src/screens/screen_level07.c new file mode 100644 index 000000000..d305b0255 --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_level07.c @@ -0,0 +1,178 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Level07 Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Level07 screen global variables +static int framesCounter; +static int finishScreen; + +static Vector2 leftCirclePos, middleCirclePos, rightCirclePos; +static Vector2 leftBtnPos, middleBtnPos, rightBtnPos; +static float circleRadius = 100; +static float btnRadius = 80; + +static bool leftCircleActive, middleCircleActive, rightCircleActive; +static Color leftCircleColor, middleCircleColor, rightCircleColor; + +static bool done = false; +static int levelTimeSec = 0; +static bool levelFinished = false; + +//---------------------------------------------------------------------------------- +// Level07 Screen Functions Definition +//---------------------------------------------------------------------------------- +static bool CheckColor(Color col1, Color col2); + +// Level07 Screen Initialization logic +void InitLevel07Screen(void) +{ + // Initialize Level07 screen variables here! + framesCounter = 0; + finishScreen = 0; + + leftCirclePos = (Vector2){ GetScreenWidth()/2 - 340, GetScreenHeight()/2 - 100 }; + middleCirclePos = (Vector2){ GetScreenWidth()/2, GetScreenHeight()/2 - 100 }; + rightCirclePos = (Vector2){ GetScreenWidth()/2 + 340, GetScreenHeight()/2 - 100 }; + + leftBtnPos = (Vector2){ GetScreenWidth()/2 - 340, GetScreenHeight()/2 + 120 }; + middleBtnPos = (Vector2){ GetScreenWidth()/2, GetScreenHeight()/2 + 120 }; + rightBtnPos = (Vector2){ GetScreenWidth()/2 + 340, GetScreenHeight()/2 + 120 }; + + leftCircleActive = false; + middleCircleActive = true; + rightCircleActive = false; + + leftCircleColor = GRAY; + middleCircleColor = GRAY; + rightCircleColor = GRAY; +} + +// Level07 Screen Update logic +void UpdateLevel07Screen(void) +{ + // Update Level07 screen variables here! + framesCounter++; + + if (!done) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (CheckCollisionPointCircle(GetMousePosition(), leftBtnPos, btnRadius)) leftCircleActive = !leftCircleActive; + else if (CheckCollisionPointCircle(GetMousePosition(), middleBtnPos, btnRadius)) middleCircleActive = !middleCircleActive; + else if (CheckCollisionPointCircle(GetMousePosition(), rightBtnPos, btnRadius)) rightCircleActive = !rightCircleActive; + + if (rightCircleActive && CheckCollisionPointCircle(GetMousePosition(), leftCirclePos, circleRadius)) + { + if (CheckColor(leftCircleColor, GRAY)) leftCircleColor = LIGHTGRAY; + else leftCircleColor = GRAY; + } + + if (middleCircleActive && CheckCollisionPointCircle(GetMousePosition(), middleCirclePos, circleRadius)) + { + if (CheckColor(middleCircleColor, GRAY)) middleCircleColor = LIGHTGRAY; + else middleCircleColor = GRAY; + } + + if (rightCircleActive && leftCircleActive && CheckCollisionPointCircle(GetMousePosition(), rightCirclePos, circleRadius)) + { + if (CheckColor(rightCircleColor, GRAY)) rightCircleColor = LIGHTGRAY; + else rightCircleColor = GRAY; + } + } + + // Check all cicles done + if (CheckColor(leftCircleColor, LIGHTGRAY) && CheckColor(middleCircleColor, LIGHTGRAY) && CheckColor(rightCircleColor, LIGHTGRAY) && + !leftCircleActive && !middleCircleActive && !rightCircleActive) + { + done = true; + PlaySound(levelWin); + } + } + + if (done && !levelFinished) + { + levelTimeSec = framesCounter/60; + levelFinished = true; + framesCounter = 0; + } + + if (levelFinished) + { + framesCounter++; + + if ((framesCounter > 90) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) finishScreen = true; + } +} + +// Level07 Screen Draw logic +void DrawLevel07Screen(void) +{ + // Draw Level07 screen here! + DrawCircleV(leftCirclePos, circleRadius, leftCircleColor); + DrawCircleV(middleCirclePos, circleRadius, middleCircleColor); + DrawCircleV(rightCirclePos, circleRadius, rightCircleColor); + + if (leftCircleActive) DrawCircleV(leftBtnPos, btnRadius, GRAY); + else DrawCircleV(leftBtnPos, btnRadius, LIGHTGRAY); + + if (middleCircleActive) DrawCircleV(middleBtnPos, btnRadius, GRAY); + else DrawCircleV(middleBtnPos, btnRadius, LIGHTGRAY); + + if (rightCircleActive) DrawCircleV(rightBtnPos, btnRadius, GRAY); + else DrawCircleV(rightBtnPos, btnRadius, LIGHTGRAY); + + + + if (levelFinished) + { + DrawRectangleBordersRec((Rectangle){0, 0, GetScreenWidth(), GetScreenHeight()}, 0, 0, 60, Fade(LIGHTGRAY, 0.6f)); + DrawText("LEVEL 07", GetScreenWidth()/2 - MeasureText("LEVEL 07", 30)/2, 20, 30, GRAY); + DrawText(FormatText("DONE! (Seconds: %03i)", levelTimeSec), GetScreenWidth()/2 - MeasureText("DONE! (Seconds: 000)", 30)/2, GetScreenHeight() - 40, 30, GRAY); + } + else DrawText("LEVEL 07", GetScreenWidth()/2 - MeasureText("LEVEL 07", 30)/2, 20, 30, LIGHTGRAY); +} + +// Level07 Screen Unload logic +void UnloadLevel07Screen(void) +{ + // TODO: Unload Level07 screen variables here! +} + +// Level07 Screen should finish? +int FinishLevel07Screen(void) +{ + return finishScreen; +} + +static bool CheckColor(Color col1, Color col2) +{ + return ((col1.r == col2.r) && (col1.g == col2.g) && (col1.b == col2.b) && (col1.a == col2.a)); +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screen_level08.c b/games/just_do_GGJ2015/src/screens/screen_level08.c new file mode 100644 index 000000000..4cb0443be --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_level08.c @@ -0,0 +1,157 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Level08 Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Level08 screen global variables +static int framesCounter; +static int finishScreen; + +static Rectangle leftColumnRec, middleColumnRec, rightColumnRec; +static Rectangle movingBox; +static int moveSpeed = 4; + +static bool leftColumnActive, middleColumnActive, rightColumnActive; + +static bool done = false; +static int levelTimeSec = 0; +static bool levelFinished = false; + +//---------------------------------------------------------------------------------- +// Level08 Screen Functions Definition +//---------------------------------------------------------------------------------- + +// Level08 Screen Initialization logic +void InitLevel08Screen(void) +{ + // TODO: Initialize Level08 screen variables here! + framesCounter = 0; + finishScreen = 0; + + movingBox = (Rectangle){ 20, GetScreenHeight()/2 - 20, 40, 40 }; + + leftColumnRec = (Rectangle){ 240, 0, 100, GetScreenHeight() }; + middleColumnRec = (Rectangle){ GetScreenWidth()/2 - 50, 0, 100, GetScreenHeight() }; + rightColumnRec = (Rectangle){ 920, 0, 100, GetScreenHeight() }; + + leftColumnActive = true; + middleColumnActive = false; + rightColumnActive = true; +} + +// Level08 Screen Update logic +void UpdateLevel08Screen(void) +{ + // Update Level08 screen variables here! + framesCounter++; + + if (!done) + { + movingBox.x += moveSpeed; + + if (movingBox.x <= 0) moveSpeed *= -1; + + if ((leftColumnActive && (CheckCollisionRecs(leftColumnRec, movingBox))) || + (middleColumnActive && (CheckCollisionRecs(middleColumnRec, movingBox))) || + (rightColumnActive && (CheckCollisionRecs(rightColumnRec, movingBox)))) moveSpeed *= -1; + + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (CheckCollisionPointRec(GetMousePosition(), leftColumnRec)) + { + middleColumnActive = false; + rightColumnActive = true; + } + else if (CheckCollisionPointRec(GetMousePosition(), middleColumnRec)) + { + rightColumnActive = false; + leftColumnActive = true; + } + else if (CheckCollisionPointRec(GetMousePosition(), rightColumnRec)) + { + leftColumnActive = false; + middleColumnActive = true; + } + } + + if (movingBox.x >= 1100) + { + done = true; + PlaySound(levelWin); + } + } + + if (done && !levelFinished) + { + levelTimeSec = framesCounter/60; + levelFinished = true; + framesCounter = 0; + } + + if (levelFinished) + { + framesCounter++; + + if ((framesCounter > 90) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) finishScreen = true; + } +} + +// Level08 Screen Draw logic +void DrawLevel08Screen(void) +{ + // Draw Level08 screen + DrawRectangle(1100, GetScreenHeight()/2 - 20, 40, 40, GRAY); + + DrawRectangleRec(movingBox, LIGHTGRAY); + + if (leftColumnActive) DrawRectangleRec(leftColumnRec, GRAY); + if (middleColumnActive) DrawRectangleRec(middleColumnRec, GRAY); + if (rightColumnActive) DrawRectangleRec(rightColumnRec, GRAY); + + if (levelFinished) + { + DrawRectangleBordersRec((Rectangle){0, 0, GetScreenWidth(), GetScreenHeight()}, 0, 0, 60, Fade(LIGHTGRAY, 0.6f)); + DrawText("LEVEL 08", GetScreenWidth()/2 - MeasureText("LEVEL 08", 30)/2, 20, 30, GRAY); + DrawText(FormatText("DONE! (Seconds: %03i)", levelTimeSec), GetScreenWidth()/2 - MeasureText("DONE! (Seconds: 000)", 30)/2, GetScreenHeight() - 40, 30, GRAY); + } + else DrawText("LEVEL 08", GetScreenWidth()/2 - MeasureText("LEVEL 08", 30)/2, 20, 30, LIGHTGRAY); +} + +// Level08 Screen Unload logic +void UnloadLevel08Screen(void) +{ + // TODO: Unload Level08 screen variables here! +} + +// Level08 Screen should finish? +int FinishLevel08Screen(void) +{ + return finishScreen; +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screen_level09.c b/games/just_do_GGJ2015/src/screens/screen_level09.c new file mode 100644 index 000000000..d20f4bfb2 --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_level09.c @@ -0,0 +1,199 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Level09 Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +#define NUM_BOXES 21 + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Level09 screen global variables +static int framesCounter; +static int finishScreen; + +static Rectangle bwRecs[NUM_BOXES]; +static Color bwColors[NUM_BOXES]; +static bool activated[NUM_BOXES]; +static int resetCounter = 0; +static bool enableCounter = 0; + +static bool done = false; +static int levelTimeSec = 0; +static bool levelFinished = false; + +//---------------------------------------------------------------------------------- +// Level09 Screen Functions Definition +//---------------------------------------------------------------------------------- +static bool CheckColor(Color col1, Color col2); + +// Level09 Screen Initialization logic +void InitLevel09Screen(void) +{ + // Initialize Level09 screen variables here! + framesCounter = 0; + finishScreen = 0; + + for (int i = 0; i < NUM_BOXES; i++) + { + bwRecs[i].x = GetScreenWidth()/7*(i%7); + bwRecs[i].y = GetScreenHeight()/3*(i/7); + bwRecs[i].width = GetScreenWidth()/7; + bwRecs[i].height = GetScreenHeight()/3; + + activated[i] = false; + + if (i%2 == 0) bwColors[i] = LIGHTGRAY; + else bwColors[i] = GRAY; + } + + bwColors[10] = RAYWHITE; +} + +// Level09 Screen Update logic +void UpdateLevel09Screen(void) +{ + // Update Level09 screen variables here! + framesCounter++; + if (enableCounter) resetCounter++; + + if (!done) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + for (int i = 0; i < NUM_BOXES; i++) + { + if (CheckCollisionPointRec(GetMousePosition(), bwRecs[i])) + { + if (i == 10) + { + if (CheckColor(bwColors[i], RAYWHITE)) + { + bwColors[i] = LIGHTGRAY; + enableCounter = true; + resetCounter = 0; + activated[1] = true; + } + else + { + bwColors[i] = RAYWHITE; + enableCounter = false; + resetCounter = 5*60; + + for (int i = 0; i < NUM_BOXES; i++) activated[i] = false; + } + } + else if ((i%2 == 1) && enableCounter) + { + if (activated[i]) + { + bwColors[i] = LIGHTGRAY; + if (i != 19) activated[i + 2] = true; + } + } + } + } + } + + if (resetCounter > (4*60 + 10)) + { + for (int i = 0; i < NUM_BOXES; i++) + { + if (i%2 == 0) bwColors[i] = LIGHTGRAY; + else bwColors[i] = GRAY; + + activated[i] = false; + } + + bwColors[10] = RAYWHITE; + enableCounter = false; + resetCounter = 0; + } + + for (int i = 0; i < NUM_BOXES; i++) + { + done = true; + + if (!CheckColor(bwColors[i], LIGHTGRAY)) + { + done = false; + return; + } + + //if (done) PlaySound(levelWin); + } + } + + if (done && !levelFinished) + { + levelTimeSec = framesCounter/60; + levelFinished = true; + framesCounter = 0; + } + + if (levelFinished) + { + framesCounter++; + + if ((framesCounter > 90) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) finishScreen = true; + } +} + +// Level09 Screen Draw logic +void DrawLevel09Screen(void) +{ + // Draw Level09 screen + for (int i = 0; i < NUM_BOXES; i++) + { + DrawRectangleRec(bwRecs[i], bwColors[i]); + } + + if (levelFinished) + { + DrawRectangleBordersRec((Rectangle){0, 0, GetScreenWidth(), GetScreenHeight()}, 0, 0, 60, Fade(RAYWHITE, 0.6f)); + DrawText("LEVEL 09", GetScreenWidth()/2 - MeasureText("LEVEL 09", 30)/2, 20, 30, GRAY); + DrawText(FormatText("DONE! (Seconds: %03i)", levelTimeSec), GetScreenWidth()/2 - MeasureText("DONE! (Seconds: 000)", 30)/2, GetScreenHeight() - 40, 30, GRAY); + } + else DrawText("LEVEL 09", GetScreenWidth()/2 - MeasureText("LEVEL 09", 30)/2, 20, 30, LIGHTGRAY); +} + +// Level09 Screen Unload logic +void UnloadLevel09Screen(void) +{ + // TODO: Unload Level09 screen variables here! +} + +// Level09 Screen should finish? +int FinishLevel09Screen(void) +{ + return finishScreen; +} + +static bool CheckColor(Color col1, Color col2) +{ + return ((col1.r == col2.r) && (col1.g == col2.g) && (col1.b == col2.b) && (col1.a == col2.a)); +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screen_level10.c b/games/just_do_GGJ2015/src/screens/screen_level10.c new file mode 100644 index 000000000..338060068 --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_level10.c @@ -0,0 +1,153 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Level10 Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Level10 screen global variables +static int framesCounter; +static int finishScreen; + +static Rectangle leftColumnRec, middleColumnRec, rightColumnRec; +static Rectangle movingBox; +static int moveSpeed = 4; + +static bool leftColumnActive, middleColumnActive, rightColumnActive; + +static bool done = false; +static int levelTimeSec = 0; +static bool levelFinished = false; + +//---------------------------------------------------------------------------------- +// Level10 Screen Functions Definition +//---------------------------------------------------------------------------------- + +// Level10 Screen Initialization logic +void InitLevel10Screen(void) +{ + // TODO: Initialize Level10 screen variables here! + framesCounter = 0; + finishScreen = 0; + + movingBox = (Rectangle){ 20, GetScreenHeight()/2 - 20, 40, 40 }; + + leftColumnRec = (Rectangle){ 240, 0, 100, GetScreenHeight() }; + middleColumnRec = (Rectangle){ GetScreenWidth()/2 - 50, 0, 100, GetScreenHeight() }; + rightColumnRec = (Rectangle){ 920, 0, 100, GetScreenHeight() }; + + leftColumnActive = true; + middleColumnActive = false; + rightColumnActive = true; +} + +// Level10 Screen Update logic +void UpdateLevel10Screen(void) +{ + // Update Level10 screen variables here! + framesCounter++; + + if (!done) + { + movingBox.x += moveSpeed; + + if (movingBox.x <= 0) moveSpeed *= -1; + + if ((leftColumnActive && (CheckCollisionRecs(leftColumnRec, movingBox))) || + (middleColumnActive && (CheckCollisionRecs(middleColumnRec, movingBox))) || + (rightColumnActive && (CheckCollisionRecs(rightColumnRec, movingBox)))) moveSpeed *= -1; + + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (CheckCollisionPointRec(GetMousePosition(), leftColumnRec)) + { + middleColumnActive = false; + rightColumnActive = true; + } + else if (CheckCollisionPointRec(GetMousePosition(), middleColumnRec)) + { + rightColumnActive = false; + leftColumnActive = true; + } + else if (CheckCollisionPointRec(GetMousePosition(), rightColumnRec)) + { + leftColumnActive = false; + middleColumnActive = true; + } + } + } + + if (movingBox.x >= 1100) done = true; + + if (done && !levelFinished) + { + levelTimeSec = framesCounter/60; + levelFinished = true; + framesCounter = 0; + } + + if (levelFinished) + { + framesCounter++; + + if ((framesCounter > 90) && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) finishScreen = true; + } +} + +// Level10 Screen Draw logic +void DrawLevel10Screen(void) +{ + // Draw Level10 screen + DrawRectangle(1100, GetScreenHeight()/2 - 20, 40, 40, GRAY); + + DrawRectangleRec(movingBox, LIGHTGRAY); + + if (leftColumnActive) DrawRectangleRec(leftColumnRec, GRAY); + if (middleColumnActive) DrawRectangleRec(middleColumnRec, GRAY); + if (rightColumnActive) DrawRectangleRec(rightColumnRec, GRAY); + + if (levelFinished) + { + DrawRectangleBordersRec((Rectangle){0, 0, GetScreenWidth(), GetScreenHeight()}, 0, 0, 60, Fade(LIGHTGRAY, 0.6f)); + DrawText("LEVEL 08", GetScreenWidth()/2 - MeasureText("LEVEL 08", 30)/2, 20, 30, GRAY); + DrawText(FormatText("DONE! (Seconds: %03i)", levelTimeSec), GetScreenWidth()/2 - MeasureText("DONE! (Seconds: 000)", 30)/2, GetScreenHeight() - 40, 30, GRAY); + } + else DrawText("LEVEL 08", GetScreenWidth()/2 - MeasureText("LEVEL 08", 30)/2, 20, 30, LIGHTGRAY); +} + +// Level10 Screen Unload logic +void UnloadLevel10Screen(void) +{ + // TODO: Unload Level10 screen variables here! +} + +// Level10 Screen should finish? +int FinishLevel10Screen(void) +{ + return finishScreen; +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screen_logo.c b/games/just_do_GGJ2015/src/screens/screen_logo.c new file mode 100644 index 000000000..9639602de --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screen_logo.c @@ -0,0 +1,227 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Logo Screen Functions Definitions (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "screens.h" + +#include + +//---------------------------------------------------------------------------------- +// Global Variables Definition (local to this module) +//---------------------------------------------------------------------------------- + +// Logo screen global variables +static int framesCounter; +static int finishScreen; + +const char msgLogoA[64] = "A simple and easy-to-use library"; +const char msgLogoB[64] = "to learn videogames programming"; + +int logoPositionX; +int logoPositionY; + +int raylibLettersCount = 0; + +int topSideRecWidth = 16; +int leftSideRecHeight = 16; + +int bottomSideRecWidth = 16; +int rightSideRecHeight = 16; + +char raylib[8] = " \0"; // raylib text array, max 8 letters + +int logoScreenState = 0; // Tracking animation states (State Machine) +bool msgLogoADone = false; +bool msgLogoBDone = false; + +int lettersCounter = 0; +char msgBuffer[128] = { ' ' }; + +//---------------------------------------------------------------------------------- +// Logo Screen Functions Definition +//---------------------------------------------------------------------------------- + +// Logo Screen Initialization logic +void InitLogoScreen(void) +{ + // Initialize LOGO screen variables here! + framesCounter = 0; + finishScreen = 0; + + logoPositionX = GetScreenWidth()/2 - 128; + logoPositionY = GetScreenHeight()/2 - 128; +} + +// Logo Screen Update logic +void UpdateLogoScreen(void) +{ + // Update LOGO screen + framesCounter++; // Count frames + + // Update LOGO screen variables + if (logoScreenState == 0) // State 0: Small box blinking + { + framesCounter++; + + if (framesCounter == 120) + { + logoScreenState = 1; + framesCounter = 0; // Reset counter... will be used later... + } + } + else if (logoScreenState == 1) // State 1: Top and left bars growing + { + topSideRecWidth += 4; + leftSideRecHeight += 4; + + if (topSideRecWidth == 256) logoScreenState = 2; + } + else if (logoScreenState == 2) // State 2: Bottom and right bars growing + { + bottomSideRecWidth += 4; + rightSideRecHeight += 4; + + if (bottomSideRecWidth == 256) + { + lettersCounter = 0; + for (int i = 0; i < strlen(msgBuffer); i++) msgBuffer[i] = ' '; + + logoScreenState = 3; + } + } + else if (logoScreenState == 3) // State 3: Letters appearing (one by one) + { + framesCounter++; + + // Every 12 frames, one more letter! + if ((framesCounter%12) == 0) raylibLettersCount++; + + switch (raylibLettersCount) + { + case 1: raylib[0] = 'r'; break; + case 2: raylib[1] = 'a'; break; + case 3: raylib[2] = 'y'; break; + case 4: raylib[3] = 'l'; break; + case 5: raylib[4] = 'i'; break; + case 6: raylib[5] = 'b'; break; + default: break; + } + + if (raylibLettersCount >= 10) + { + // Write raylib description messages + if ((framesCounter%2) == 0) lettersCounter++; + + if (!msgLogoADone) + { + if (lettersCounter <= strlen(msgLogoA)) strncpy(msgBuffer, msgLogoA, lettersCounter); + else + { + for (int i = 0; i < strlen(msgBuffer); i++) msgBuffer[i] = ' '; + + lettersCounter = 0; + msgLogoADone = true; + } + } + else if (!msgLogoBDone) + { + if (lettersCounter <= strlen(msgLogoB)) strncpy(msgBuffer, msgLogoB, lettersCounter); + else + { + msgLogoBDone = true; + framesCounter = 0; + PlaySound(levelWin); + } + } + } + } + + // Wait for 2 seconds (60 frames) before jumping to TITLE screen + if (msgLogoBDone) + { + framesCounter++; + + if (framesCounter > 90) finishScreen = true; + } +} + +// Logo Screen Draw logic +void DrawLogoScreen(void) +{ + // Draw LOGO screen + if (logoScreenState == 0) + { + if ((framesCounter/15)%2) DrawRectangle(logoPositionX, logoPositionY - 60, 16, 16, BLACK); + } + else if (logoScreenState == 1) + { + DrawRectangle(logoPositionX, logoPositionY - 60, topSideRecWidth, 16, BLACK); + DrawRectangle(logoPositionX, logoPositionY - 60, 16, leftSideRecHeight, BLACK); + } + else if (logoScreenState == 2) + { + DrawRectangle(logoPositionX, logoPositionY - 60, topSideRecWidth, 16, BLACK); + DrawRectangle(logoPositionX, logoPositionY - 60, 16, leftSideRecHeight, BLACK); + + DrawRectangle(logoPositionX + 240, logoPositionY - 60, 16, rightSideRecHeight, BLACK); + DrawRectangle(logoPositionX, logoPositionY + 240 - 60, bottomSideRecWidth, 16, BLACK); + } + else if (logoScreenState == 3) + { + DrawRectangle(logoPositionX, logoPositionY - 60, topSideRecWidth, 16, BLACK); + DrawRectangle(logoPositionX, logoPositionY + 16 - 60, 16, leftSideRecHeight - 32, BLACK); + + DrawRectangle(logoPositionX + 240, logoPositionY + 16 - 60, 16, rightSideRecHeight - 32, BLACK); + DrawRectangle(logoPositionX, logoPositionY + 240 - 60, bottomSideRecWidth, 16, BLACK); + + DrawRectangle(GetScreenWidth()/2 - 112, GetScreenHeight()/2 - 112 - 60, 224, 224, RAYWHITE); + + DrawText(raylib, GetScreenWidth()/2 - 44, GetScreenHeight()/2 + 48 - 60, 50, BLACK); + + if (!msgLogoADone) DrawText(msgBuffer, GetScreenWidth()/2 - MeasureText(msgLogoA, 30)/2, logoPositionY + 230, 30, GRAY); + else + { + DrawText(msgLogoA, GetScreenWidth()/2 - MeasureText(msgLogoA, 30)/2, logoPositionY + 230, 30, GRAY); + + if (!msgLogoBDone) DrawText(msgBuffer, GetScreenWidth()/2 - MeasureText(msgLogoB, 30)/2, logoPositionY + 280, 30, GRAY); + else + { + DrawText(msgLogoB, GetScreenWidth()/2 - MeasureText(msgLogoA, 30)/2, logoPositionY + 280, 30, GRAY); + } + } + } +} + +// Logo Screen Unload logic +void UnloadLogoScreen(void) +{ + // TODO: Unload LOGO screen variables here! +} + +// Logo Screen should finish? +int FinishLogoScreen(void) +{ + return finishScreen; +} \ No newline at end of file diff --git a/games/just_do_GGJ2015/src/screens/screens.h b/games/just_do_GGJ2015/src/screens/screens.h new file mode 100644 index 000000000..7fa594053 --- /dev/null +++ b/games/just_do_GGJ2015/src/screens/screens.h @@ -0,0 +1,150 @@ +/********************************************************************************************** +* +* raylib - Standard Game template +* +* Screens Functions Declarations (Init, Update, Draw, Unload) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef SCREENS_H +#define SCREENS_H + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef enum GameScreen { LOGO, LEVEL00, LEVEL01, LEVEL02, LEVEL03, LEVEL04, LEVEL05, LEVEL06, LEVEL07, LEVEL08, LEVEL09 } GameScreen; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +GameScreen currentScreen; +Sound levelWin; + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Logo Screen Functions Declaration +//---------------------------------------------------------------------------------- +void InitLogoScreen(void); +void UpdateLogoScreen(void); +void DrawLogoScreen(void); +void UnloadLogoScreen(void); +int FinishLogoScreen(void); + +//---------------------------------------------------------------------------------- +// Level00 Screen Functions Declaration +//---------------------------------------------------------------------------------- +void InitLevel00Screen(void); +void UpdateLevel00Screen(void); +void DrawLevel00Screen(void); +void UnloadLevel00Screen(void); +int FinishLevel00Screen(void); + +//---------------------------------------------------------------------------------- +// Level01 Screen Functions Declaration +//---------------------------------------------------------------------------------- +void InitLevel01Screen(void); +void UpdateLevel01Screen(void); +void DrawLevel01Screen(void); +void UnloadLevel01Screen(void); +int FinishLevel01Screen(void); + +//---------------------------------------------------------------------------------- +// Level02 Screen Functions Declaration +//---------------------------------------------------------------------------------- +void InitLevel02Screen(void); +void UpdateLevel02Screen(void); +void DrawLevel02Screen(void); +void UnloadLevel02Screen(void); +int FinishLevel02Screen(void); + +//---------------------------------------------------------------------------------- +// Level03 Screen Functions Declaration +//---------------------------------------------------------------------------------- +void InitLevel03Screen(void); +void UpdateLevel03Screen(void); +void DrawLevel03Screen(void); +void UnloadLevel03Screen(void); +int FinishLevel03Screen(void); + +//---------------------------------------------------------------------------------- +// Level04 Screen Functions Declaration +//---------------------------------------------------------------------------------- +void InitLevel04Screen(void); +void UpdateLevel04Screen(void); +void DrawLevel04Screen(void); +void UnloadLevel04Screen(void); +int FinishLevel04Screen(void); + +//---------------------------------------------------------------------------------- +// Level05 Screen Functions Declaration +//---------------------------------------------------------------------------------- +void InitLevel05Screen(void); +void UpdateLevel05Screen(void); +void DrawLevel05Screen(void); +void UnloadLevel05Screen(void); +int FinishLevel05Screen(void); + +//---------------------------------------------------------------------------------- +// Level06 Screen Functions Declaration +//---------------------------------------------------------------------------------- +void InitLevel06Screen(void); +void UpdateLevel06Screen(void); +void DrawLevel06Screen(void); +void UnloadLevel06Screen(void); +int FinishLevel06Screen(void); + +//---------------------------------------------------------------------------------- +// Level07 Screen Functions Declaration +//---------------------------------------------------------------------------------- +void InitLevel07Screen(void); +void UpdateLevel07Screen(void); +void DrawLevel07Screen(void); +void UnloadLevel07Screen(void); +int FinishLevel07Screen(void); + +//---------------------------------------------------------------------------------- +// Level08 Screen Functions Declaration +//---------------------------------------------------------------------------------- +void InitLevel08Screen(void); +void UpdateLevel08Screen(void); +void DrawLevel08Screen(void); +void UnloadLevel08Screen(void); +int FinishLevel08Screen(void); + +//---------------------------------------------------------------------------------- +// Level09 Screen Functions Declaration +//---------------------------------------------------------------------------------- +void InitLevel09Screen(void); +void UpdateLevel09Screen(void); +void DrawLevel09Screen(void); +void UnloadLevel09Screen(void); +int FinishLevel09Screen(void); + + +void DrawRectangleBordersRec(Rectangle rec, int offsetX, int offsetY, int borderSize, Color col); + +#ifdef __cplusplus +} +#endif + +#endif // SCREENS_H \ No newline at end of file diff --git a/src/audio.c b/src/audio.c index 40c248951..9653c0917 100644 --- a/src/audio.c +++ b/src/audio.c @@ -39,7 +39,8 @@ #include "utils.h" // rRES data decompression utility function // NOTE: Includes Android fopen function map -#include "stb_vorbis.h" // OGG loading functions +//#define STB_VORBIS_HEADER_ONLY +#include "stb_vorbis.h" // OGG loading functions //---------------------------------------------------------------------------------- // Defines and Macros @@ -49,7 +50,7 @@ #if defined(PLATFORM_RPI) // NOTE: On RPI should be lower to avoid frame-stalls #define MUSIC_BUFFER_SIZE 4096*2 // PCM data buffer (short) - 16Kb (RPI) -#else +#else // NOTE: On HTML5 (emscripten) this is allocated on heap, by default it's only 16MB!...just take care... #define MUSIC_BUFFER_SIZE 4096*8 // PCM data buffer (short) - 64Kb #endif @@ -201,7 +202,7 @@ Sound LoadSound(char *fileName) // Attach sound buffer to source alSourcei(source, AL_BUFFER, buffer); - + TraceLog(INFO, "[%s] Sound file loaded successfully (SampleRate: %i, BitRate: %i, Channels: %i)", fileName, wave.sampleRate, wave.bitsPerSample, wave.channels); // Unallocate WAV data @@ -283,7 +284,7 @@ Sound LoadSoundFromRES(const char *rresName, int resId) FILE *rresFile = fopen(rresName, "rb"); - if (rresFile == NULL) + if (rresFile == NULL) { TraceLog(WARNING, "[%s] rRES raylib resource file could not be opened", rresName); } @@ -378,7 +379,7 @@ Sound LoadSoundFromRES(const char *rresName, int resId) // Attach sound buffer to source alSourcei(source, AL_BUFFER, buffer); - + TraceLog(INFO, "[%s] Sound loaded successfully from resource (SampleRate: %i, BitRate: %i, Channels: %i)", rresName, wave.sampleRate, wave.bitsPerSample, wave.channels); // Unallocate WAV data @@ -574,6 +575,7 @@ void PauseMusicStream(void) { TraceLog(INFO, "Pausing music stream"); alSourcePause(currentMusic.source); + musicEnabled = false; } } @@ -581,10 +583,14 @@ void PauseMusicStream(void) void ResumeMusicStream(void) { // Resume music playing... if music available! - if (musicEnabled) + ALenum state; + alGetSourcei(currentMusic.source, AL_SOURCE_STATE, &state); + + if (state == AL_PAUSED) { - TraceLog(INFO, "Resume music stream"); + TraceLog(INFO, "Resuming music stream"); alSourcePlay(currentMusic.source); + musicEnabled = true; } } @@ -865,13 +871,12 @@ static Wave LoadOGG(char *fileName) TraceLog(DEBUG, "[%s] Total samples calculated: %i", fileName, totalSamples); - //short *data wave.data = malloc(sizeof(short)*totalSamplesLength); int samplesObtained = stb_vorbis_get_samples_short_interleaved(oggFile, info.channels, wave.data, totalSamplesLength); TraceLog(DEBUG, "[%s] Samples obtained: %i", fileName, samplesObtained); - + TraceLog(INFO, "[%s] OGG file loaded successfully (SampleRate: %i, BitRate: %i, Channels: %i)", fileName, wave.sampleRate, wave.bitsPerSample, wave.channels); stb_vorbis_close(oggFile); diff --git a/src/camera.c b/src/camera.c new file mode 100644 index 000000000..cb99ba6b9 --- /dev/null +++ b/src/camera.c @@ -0,0 +1,455 @@ +/********************************************************************************************** +* +* raylib.camera +* +* Camera Modes Setup and Control Functions +* +* Copyright (c) 2015 Marc Palau and Ramon Santamaria +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// CAMERA_GENERIC +#define CAMERA_SCROLL_SENSITIVITY 1.5 + +// FREE_CAMERA +#define FREE_CAMERA_MOUSE_SENSITIVITY 0.01 +#define FREE_CAMERA_DISTANCE_MIN_CLAMP 0.3 +#define FREE_CAMERA_DISTANCE_MAX_CLAMP 120 +#define FREE_CAMERA_MIN_CLAMP 85 +#define FREE_CAMERA_MAX_CLAMP -85 +#define FREE_CAMERA_SMOOTH_ZOOM_SENSITIVITY 0.05 +#define FREE_CAMERA_PANNING_DIVIDER 5.1 + +// ORBITAL_CAMERA +#define ORBITAL_CAMERA_SPEED 0.01 + +// FIRST_PERSON +//#define FIRST_PERSON_MOUSE_SENSITIVITY 0.003 +#define FIRST_PERSON_FOCUS_DISTANCE 25 +#define FIRST_PERSON_MIN_CLAMP 85 +#define FIRST_PERSON_MAX_CLAMP -85 + +#define FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER 5.0 +#define FIRST_PERSON_STEP_DIVIDER 30.0 +#define FIRST_PERSON_WAVING_DIVIDER 200.0 + +#define FIRST_PERSON_HEIGHT_RELATIVE_EYES_POSITION 0.85 + +// THIRD_PERSON +//#define THIRD_PERSON_MOUSE_SENSITIVITY 0.003 +#define THIRD_PERSON_DISTANCE_CLAMP 1.2 +#define THIRD_PERSON_MIN_CLAMP 5 +#define THIRD_PERSON_MAX_CLAMP -85 +#define THIRD_PERSON_OFFSET (Vector3){ 0.4, 0, 0 } + +// PLAYER (used by camera) +#define PLAYER_WIDTH 0.4 +#define PLAYER_HEIGHT 0.9 +#define PLAYER_DEPTH 0.4 +#define PLAYER_MOVEMENT_DIVIDER 20.0 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static Camera internalCamera = {{2,0,2},{0,0,0},{0,1,0}}; +static Vector2 cameraAngle = { 0, 0 }; +static float cameraTargetDistance = 5; +static Vector3 resetingPosition = { 0, 0, 0 }; +static int resetingKey = 'Z'; +static Vector2 cameraMousePosition = { 0, 0 }; +static Vector2 cameraMouseVariation = { 0, 0 }; +static float mouseSensitivity = 0.003; +static int cameraMovementController[6] = { 'W', 'A', 'S', 'D', 'E', 'Q' }; +static int cameraMovementCounter = 0; +static bool cameraUseGravity = true; +static int pawnControllingKey = MOUSE_MIDDLE_BUTTON; +static int fnControllingKey = KEY_LEFT_ALT; +static int smoothZoomControllingKey = KEY_LEFT_CONTROL; + +static int cameraMode = CAMERA_CUSTOM; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static void ProcessCamera(Camera *camera, Vector3 *playerPosition); +/* +static void SetCameraControls(int front, int left, int back, right, up, down); +static void SetMouseSensitivity(int sensitivity); +static void SetResetPosition(Vector3 resetPosition); +static void SetResetControl(int resetKey); +static void SetPawnControl(int pawnControlKey); +static void SetFnControl(int fnControlKey); +static void SetSmoothZoomControl(int smoothZoomControlKey); +*/ + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Select camera mode (multiple camera modes available) +void SetCameraMode(int mode) +{ + if ((cameraMode == CAMERA_FIRST_PERSON) && (mode == CAMERA_FREE)) + { + cameraMode = CAMERA_THIRD_PERSON; + cameraTargetDistance = 5; + cameraAngle.y = -40 * DEG2RAD; + ProcessCamera(&internalCamera, &internalCamera.position); + } + else if ((cameraMode == CAMERA_FIRST_PERSON) && (mode == CAMERA_ORBITAL)) + { + cameraMode = CAMERA_THIRD_PERSON; + cameraTargetDistance = 5; + cameraAngle.y = -40 * DEG2RAD; + ProcessCamera(&internalCamera, &internalCamera.position); + } + else if ((cameraMode == CAMERA_CUSTOM) && (mode == CAMERA_FREE)) + { + cameraTargetDistance = 10; + cameraAngle.x = 45 * DEG2RAD; + cameraAngle.y = -40 * DEG2RAD; + internalCamera.target = (Vector3){ 0, 0, 0}; + ProcessCamera(&internalCamera, &internalCamera.position); + } + else if ((cameraMode == CAMERA_CUSTOM) && (mode == CAMERA_ORBITAL)) + { + cameraTargetDistance = 10; + cameraAngle.x = 225 * DEG2RAD; + cameraAngle.y = -40 * DEG2RAD; + internalCamera.target = (Vector3){ 3, 0, 3}; + ProcessCamera(&internalCamera, &internalCamera.position); + } + + cameraMode = mode; +} + +// Update camera with position +Camera UpdateCamera(Vector3 *position) +{ + // Calculate camera + if (cameraMode != CAMERA_CUSTOM) ProcessCamera(&internalCamera, position); + + return internalCamera; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Process desired camera mode and controls +static void ProcessCamera(Camera *camera, Vector3 *playerPosition) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) + // Mouse movement detection + if ((cameraMode != CAMERA_FREE) && (cameraMode != CAMERA_ORBITAL)) + { + HideCursor(); + + if (GetMousePosition().x < GetScreenHeight() / 3) SetMousePosition((Vector2){ GetScreenWidth() - GetScreenHeight() / 3, GetMousePosition().y}); + else if (GetMousePosition().y < GetScreenHeight() / 3) SetMousePosition((Vector2){ GetMousePosition().x, GetScreenHeight() - GetScreenHeight() / 3}); + else if (GetMousePosition().x > GetScreenWidth() - GetScreenHeight() / 3) SetMousePosition((Vector2) { GetScreenHeight() / 3, GetMousePosition().y}); + else if (GetMousePosition().y > GetScreenHeight() - GetScreenHeight() / 3) SetMousePosition((Vector2){ GetMousePosition().x, GetScreenHeight() / 3}); + else + { + cameraMouseVariation.x = GetMousePosition().x - cameraMousePosition.x; + cameraMouseVariation.y = GetMousePosition().y - cameraMousePosition.y; + } + } + else + { + ShowCursor(); + + cameraMouseVariation.x = GetMousePosition().x - cameraMousePosition.x; + cameraMouseVariation.y = GetMousePosition().y - cameraMousePosition.y; + } + + cameraMousePosition = GetMousePosition(); + + // Support for multiple automatic camera modes + switch (cameraMode) + { + case CAMERA_FREE: + { + // Camera zoom + if ((cameraTargetDistance < FREE_CAMERA_DISTANCE_MAX_CLAMP) && (GetMouseWheelMove() < 0)) + { + cameraTargetDistance -= (GetMouseWheelMove() * CAMERA_SCROLL_SENSITIVITY); + + if (cameraTargetDistance > FREE_CAMERA_DISTANCE_MAX_CLAMP) cameraTargetDistance = FREE_CAMERA_DISTANCE_MAX_CLAMP; + } + // Camera looking down + else if ((camera->position.y > camera->target.y) && (cameraTargetDistance == FREE_CAMERA_DISTANCE_MAX_CLAMP) && (GetMouseWheelMove() < 0)) + { + camera->target.x += GetMouseWheelMove() * (camera->target.x - camera->position.x) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.y += GetMouseWheelMove() * (camera->target.y - camera->position.y) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.z += GetMouseWheelMove() * (camera->target.z - camera->position.z) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + } + else if ((camera->position.y > camera->target.y) && (camera->target.y >= 0)) + { + camera->target.x += GetMouseWheelMove() * (camera->target.x - camera->position.x) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.y += GetMouseWheelMove() * (camera->target.y - camera->position.y) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.z += GetMouseWheelMove() * (camera->target.z - camera->position.z) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + + if (camera->target.y < 0) camera->target.y = -0.001; + } + else if ((camera->position.y > camera->target.y) && (camera->target.y < 0) && (GetMouseWheelMove() > 0)) + { + cameraTargetDistance -= (GetMouseWheelMove() * CAMERA_SCROLL_SENSITIVITY); + if (cameraTargetDistance < FREE_CAMERA_DISTANCE_MIN_CLAMP) cameraTargetDistance = FREE_CAMERA_DISTANCE_MIN_CLAMP; + } + // Camera looking up + else if ((camera->position.y < camera->target.y) && (cameraTargetDistance == FREE_CAMERA_DISTANCE_MAX_CLAMP) && (GetMouseWheelMove() < 0)) + { + camera->target.x += GetMouseWheelMove() * (camera->target.x - camera->position.x) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.y += GetMouseWheelMove() * (camera->target.y - camera->position.y) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.z += GetMouseWheelMove() * (camera->target.z - camera->position.z) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + } + else if ((camera->position.y < camera->target.y) && (camera->target.y <= 0)) + { + camera->target.x += GetMouseWheelMove() * (camera->target.x - camera->position.x) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.y += GetMouseWheelMove() * (camera->target.y - camera->position.y) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.z += GetMouseWheelMove() * (camera->target.z - camera->position.z) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + + if (camera->target.y > 0) camera->target.y = 0.001; + } + else if ((camera->position.y < camera->target.y) && (camera->target.y > 0) && (GetMouseWheelMove() > 0)) + { + cameraTargetDistance -= (GetMouseWheelMove() * CAMERA_SCROLL_SENSITIVITY); + if (cameraTargetDistance < FREE_CAMERA_DISTANCE_MIN_CLAMP) cameraTargetDistance = FREE_CAMERA_DISTANCE_MIN_CLAMP; + } + + // Inputs + if (IsKeyDown(fnControllingKey)) + { + if (IsKeyDown(smoothZoomControllingKey)) + { + // Camera smooth zoom + if (IsMouseButtonDown(pawnControllingKey)) cameraTargetDistance += (cameraMouseVariation.y * FREE_CAMERA_SMOOTH_ZOOM_SENSITIVITY); + } + // Camera orientation calculation + else if (IsMouseButtonDown(pawnControllingKey)) + { + // Camera orientation calculation + // Get the mouse sensitivity + cameraAngle.x += cameraMouseVariation.x * -FREE_CAMERA_MOUSE_SENSITIVITY; + cameraAngle.y += cameraMouseVariation.y * -FREE_CAMERA_MOUSE_SENSITIVITY; + + // Angle clamp + if (cameraAngle.y > FREE_CAMERA_MIN_CLAMP * DEG2RAD) cameraAngle.y = FREE_CAMERA_MIN_CLAMP * DEG2RAD; + else if (cameraAngle.y < FREE_CAMERA_MAX_CLAMP * DEG2RAD) cameraAngle.y = FREE_CAMERA_MAX_CLAMP * DEG2RAD; + } + } + // Paning + else if (IsMouseButtonDown(pawnControllingKey)) + { + camera->target.x += ((cameraMouseVariation.x * -FREE_CAMERA_MOUSE_SENSITIVITY) * cos(cameraAngle.x) + (cameraMouseVariation.y * FREE_CAMERA_MOUSE_SENSITIVITY) * sin(cameraAngle.x) * sin(cameraAngle.y)) * (cameraTargetDistance / FREE_CAMERA_PANNING_DIVIDER); + camera->target.y += ((cameraMouseVariation.y * FREE_CAMERA_MOUSE_SENSITIVITY) * cos(cameraAngle.y)) * (cameraTargetDistance / FREE_CAMERA_PANNING_DIVIDER); + camera->target.z += ((cameraMouseVariation.x * FREE_CAMERA_MOUSE_SENSITIVITY) * sin(cameraAngle.x) + (cameraMouseVariation.y * FREE_CAMERA_MOUSE_SENSITIVITY) * cos(cameraAngle.x) * sin(cameraAngle.y)) * (cameraTargetDistance / FREE_CAMERA_PANNING_DIVIDER); + } + + // Focus to center + if (IsKeyDown(resetingKey)) camera->target = resetingPosition; + + // Camera position update + camera->position.x = sin(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.x; + + if (cameraAngle.y <= 0) camera->position.y = sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + else camera->position.y = -sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + + camera->position.z = cos(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.z; + + } break; + case CAMERA_ORBITAL: + { + cameraAngle.x += ORBITAL_CAMERA_SPEED; + + // Camera zoom + cameraTargetDistance -= (GetMouseWheelMove() * CAMERA_SCROLL_SENSITIVITY); + // Camera distance clamp + if (cameraTargetDistance < THIRD_PERSON_DISTANCE_CLAMP) cameraTargetDistance = THIRD_PERSON_DISTANCE_CLAMP; + + // Focus to center + if (IsKeyDown('Z')) camera->target = (Vector3) { 0, 0, 0 }; + + // Camera position update + camera->position.x = sin(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.x; + + if (cameraAngle.y <= 0) camera->position.y = sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + else camera->position.y = -sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + + camera->position.z = cos(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.z; + + } break; + case CAMERA_FIRST_PERSON: + case CAMERA_THIRD_PERSON: + { + bool isMoving = false; + + // Keyboard inputs + if (IsKeyDown(cameraMovementController[0])) + { + playerPosition->x -= sin(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + playerPosition->z -= cos(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + if (!cameraUseGravity) camera->position.y += sin(cameraAngle.y) / PLAYER_MOVEMENT_DIVIDER; + + isMoving = true; + } + else if (IsKeyDown(cameraMovementController[2])) + { + playerPosition->x += sin(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + playerPosition->z += cos(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + if (!cameraUseGravity) camera->position.y -= sin(cameraAngle.y) / PLAYER_MOVEMENT_DIVIDER; + + isMoving = true; + } + + if (IsKeyDown(cameraMovementController[1])) + { + playerPosition->x -= cos(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + playerPosition->z += sin(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + + isMoving = true; + } + else if (IsKeyDown(cameraMovementController[3])) + { + playerPosition->x += cos(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + playerPosition->z -= sin(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + + isMoving = true; + } + + if (IsKeyDown(cameraMovementController[4])) + { + if (!cameraUseGravity) playerPosition->y += 1 / PLAYER_MOVEMENT_DIVIDER; + } + else if (IsKeyDown(cameraMovementController[5])) + { + if (!cameraUseGravity) playerPosition->y -= 1 / PLAYER_MOVEMENT_DIVIDER; + } + + if (cameraMode == CAMERA_THIRD_PERSON) + { + // Camera orientation calculation + // Get the mouse sensitivity + cameraAngle.x += cameraMouseVariation.x * -mouseSensitivity; + cameraAngle.y += cameraMouseVariation.y * -mouseSensitivity; + + // Angle clamp + if (cameraAngle.y > THIRD_PERSON_MIN_CLAMP * DEG2RAD) cameraAngle.y = THIRD_PERSON_MIN_CLAMP * DEG2RAD; + else if (cameraAngle.y < THIRD_PERSON_MAX_CLAMP * DEG2RAD) cameraAngle.y = THIRD_PERSON_MAX_CLAMP * DEG2RAD; + + // Camera zoom + cameraTargetDistance -= (GetMouseWheelMove() * CAMERA_SCROLL_SENSITIVITY); + + // Camera distance clamp + if (cameraTargetDistance < THIRD_PERSON_DISTANCE_CLAMP) cameraTargetDistance = THIRD_PERSON_DISTANCE_CLAMP; + + // Camera is always looking at player + camera->target.x = playerPosition->x + THIRD_PERSON_OFFSET.x * cos(cameraAngle.x) + THIRD_PERSON_OFFSET.z * sin(cameraAngle.x); + camera->target.y = playerPosition->y + PLAYER_HEIGHT * FIRST_PERSON_HEIGHT_RELATIVE_EYES_POSITION + THIRD_PERSON_OFFSET.y; + camera->target.z = playerPosition->z + THIRD_PERSON_OFFSET.z * sin(cameraAngle.x) - THIRD_PERSON_OFFSET.x * sin(cameraAngle.x); + + // Camera position update + camera->position.x = sin(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.x; + + if (cameraAngle.y <= 0) camera->position.y = sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + else camera->position.y = -sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + + camera->position.z = cos(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.z; + } + else + { + if (isMoving) cameraMovementCounter++; + + // Camera orientation calculation + // Get the mouse sensitivity + cameraAngle.x += cameraMouseVariation.x * -mouseSensitivity; + cameraAngle.y += cameraMouseVariation.y * -mouseSensitivity; + + // Angle clamp + if (cameraAngle.y > FIRST_PERSON_MIN_CLAMP * DEG2RAD) cameraAngle.y = FIRST_PERSON_MIN_CLAMP * DEG2RAD; + else if (cameraAngle.y < FIRST_PERSON_MAX_CLAMP * DEG2RAD) cameraAngle.y = FIRST_PERSON_MAX_CLAMP * DEG2RAD; + + // Camera is always looking at player + camera->target.x = camera->position.x - sin(cameraAngle.x) * FIRST_PERSON_FOCUS_DISTANCE; + camera->target.y = camera->position.y + sin(cameraAngle.y) * FIRST_PERSON_FOCUS_DISTANCE; + camera->target.z = camera->position.z - cos(cameraAngle.x) * FIRST_PERSON_FOCUS_DISTANCE; + + camera->position.x = playerPosition->x; + camera->position.y = (playerPosition->y + PLAYER_HEIGHT * FIRST_PERSON_HEIGHT_RELATIVE_EYES_POSITION) - sin(cameraMovementCounter / FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER) / FIRST_PERSON_STEP_DIVIDER; + camera->position.z = playerPosition->z; + + camera->up.x = sin(cameraMovementCounter / (FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER * 2)) / FIRST_PERSON_WAVING_DIVIDER; + camera->up.z = -sin(cameraMovementCounter / (FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER * 2)) / FIRST_PERSON_WAVING_DIVIDER; + } + } break; + default: break; + } +#endif +} + +void SetCameraControls(int frontKey, int leftKey, int backKey, int rightKey, int upKey, int downKey) +{ + cameraMovementController[0] = frontKey; + cameraMovementController[1] = leftKey; + cameraMovementController[2] = backKey; + cameraMovementController[3] = rightKey; + cameraMovementController[4] = upKey; + cameraMovementController[5] = downKey; +} + +void SetMouseSensitivity(float sensitivity) +{ + mouseSensitivity = (sensitivity / 10000.0); +} + +void SetResetPosition(Vector3 resetPosition) +{ + resetingPosition = resetPosition; +} + +void SetResetControl(int resetKey) +{ + resetingKey = resetKey; +} + +void SetPawnControl(int pawnControlKey) +{ + pawnControllingKey = pawnControlKey; +} + +void SetFnControl(int fnControlKey) +{ + fnControllingKey = fnControlKey; +} + +void SetSmoothZoomControl(int smoothZoomControlKey) +{ + smoothZoomControllingKey = smoothZoomControlKey; +} + +void SetOrbitalTarget(Vector3 target) +{ + internalCamera.target = target; +} diff --git a/src/core.c b/src/core.c index 26b1dd6d6..7bbe3892a 100644 --- a/src/core.c +++ b/src/core.c @@ -45,13 +45,18 @@ #include // Standard input / output lib #include // Declares malloc() and free() for memory management, rand(), atexit() #include // Required for typedef unsigned long long int uint64_t, used by hi-res timer -#include // Useful to initialize random seed - Android/RPI hi-res timer +#include // Useful to initialize random seed - Android/RPI hi-res timer (NOTE: Linux only!) #include // Math related functions, tan() used to set perspective #include // String function definitions, memset() #include // Macros for reporting and retrieving error conditions through error codes #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) #include // GLFW3 library: Windows, OpenGL context and Input management + #ifdef __linux + #define GLFW_EXPOSE_NATIVE_X11 // Linux specific definitions for getting + #define GLFW_EXPOSE_NATIVE_GLX // native functions like glfwGetX11Window + #include // which are required for hiding mouse + #endif //#include // OpenGL functions (GLFW3 already includes gl.h) //#define GLFW_DLL // Using GLFW DLL on Windows -> No, we use static version! #endif @@ -104,28 +109,13 @@ //---------------------------------------------------------------------------------- #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) static GLFWwindow *window; // Native window (graphic device) +static bool windowMinimized = false; #elif 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 bool windowReady = false; // Used to detect display initialization -// Gestures detection variables -static float tapTouchX, tapTouchY; -static int64_t lastTapTime = 0; -static float lastTapX = 0, lastTapY = 0; -static bool touchTap = false; -static bool doubleTap = false; -static bool drag = false; -static int stdVector[MAX_TOUCH_POINTS]; -static int indexPosition = 0; -const AInputEvent* eventDrag; -static int32_t touchId; -const int32_t DOUBLE_TAP_TIMEOUT = 300*1000000; -const int32_t DOUBLE_TAP_SLOP = 100; -const int32_t TAP_TIMEOUT = 180*1000000; -const int32_t TOUCH_SLOP = 8; - #elif defined(PLATFORM_RPI) static EGL_DISPMANX_WINDOW_T nativeWindow; // Native window (graphic device) @@ -152,8 +142,7 @@ static int gamepadStream = -1; // Gamepad device file descripto #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 EGLContext context; // Graphic context, mode in which drawing can be done static uint64_t baseTime; // Base time measure for hi-res timer static bool windowShouldClose = false; // Flag to set window for closing #endif @@ -191,11 +180,8 @@ static int currentMouseWheelY = 0; // Required to track mouse wheel var static int exitKey = KEY_ESCAPE; // Default exit key (ESC) static int lastKeyPressed = -1; -#endif -#if defined(PLATFORM_ANDROID) -static float touchX; // Touch position X -static float touchY; // Touch position Y +static bool cursorHidden; #endif static double currentTime, previousTime; // Used to track timmings @@ -206,6 +192,9 @@ static double targetTime = 0.0; // Desired time for one frame, if 0 static char configFlags = 0; static bool showLogo = false; +// Shaders variables +static bool enabledPostpro = false; + //---------------------------------------------------------------------------------- // Other Modules Functions Declaration (required by core) //---------------------------------------------------------------------------------- @@ -214,6 +203,17 @@ extern void UnloadDefaultFont(void); // [Module: text] Unloads defaul extern void UpdateMusicStream(void); // [Module: audio] Updates buffers for music streaming +extern Vector2 GetRawPosition(void); +extern void ResetGestures(void); + +#if defined(PLATFORM_ANDROID) +extern void InitAndroidGestures(struct android_app *app); +#endif + +#if defined(PLATFORM_WEB) +extern void InitWebGestures(void); // [Module: gestures] Initializes emscripten gestures for web +#endif + //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- @@ -227,6 +227,7 @@ static void SwapBuffers(void); // Copy back buffer to f static void PollInputEvents(void); // Register user events static void LogoAnimation(void); // Plays raylib logo appearing animation static void SetupFramebufferSize(int displayWidth, int displayHeight); + #if defined(PLATFORM_RPI) static void InitMouse(void); // Mouse initialization (including mouse thread) static void *MouseThread(void *arg); // Mouse reading thread @@ -243,6 +244,7 @@ static void CharCallback(GLFWwindow *window, unsigned int key); static void ScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized +static void WindowIconifyCallback(GLFWwindow* window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored #endif #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) @@ -250,8 +252,7 @@ static void TakeScreenshot(void); #endif #if defined(PLATFORM_ANDROID) -static int32_t InputCallback(struct android_app *app, AInputEvent *event); // Process Android activity input events -static void CommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands +static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands #endif //---------------------------------------------------------------------------------- @@ -261,8 +262,8 @@ static void CommandCallback(struct android_app *app, int32_t cmd); // // Initialize Window and Graphics Context (OpenGL) void InitWindow(int width, int height, const char *title) { - TraceLog(INFO, "Initializing raylib..."); - + TraceLog(INFO, "Initializing raylib (v1.3.0)"); + // Store window title (could be useful...) windowTitle = title; @@ -285,6 +286,11 @@ void InitWindow(int width, int height, const char *title) InitKeyboard(); // Keyboard init InitGamepad(); // Gamepad init #endif + +#if defined(PLATFORM_WEB) + InitWebGestures(); // Init touch input events for web +#endif + mousePosition.x = screenWidth/2; mousePosition.y = screenHeight/2; @@ -300,8 +306,8 @@ void InitWindow(int width, int height, const char *title) // Android activity initialization void InitWindow(int width, int height, struct android_app *state) { - TraceLog(INFO, "Initializing raylib..."); - + TraceLog(INFO, "Initializing raylib (v1.3.0)"); + app_dummy(); screenWidth = width; @@ -336,8 +342,9 @@ void InitWindow(int width, int height, struct android_app *state) //AConfiguration_getScreenLong(app->config); //state->userData = &engine; - app->onAppCmd = CommandCallback; - app->onInputEvent = InputCallback; + app->onAppCmd = AndroidCommandCallback; + + InitAndroidGestures(app); InitAssetManager(app->activity->assetManager); @@ -399,6 +406,9 @@ void CloseWindow(void) bool WindowShouldClose(void) { #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + // While window minimized, stop loop execution + while (windowMinimized) glfwPollEvents(); + return (glfwWindowShouldClose(window)); #elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) return windowShouldClose; @@ -429,7 +439,7 @@ void SetCustomCursor(const char *cursorImage) cursor = LoadTexture(cursorImage); #if defined(PLATFORM_DESKTOP) - // NOTE: emscripten not implemented + // NOTE: emscripten not implemented glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); #endif customCursor = true; @@ -469,22 +479,31 @@ void BeginDrawing(void) updateTime = currentTime - previousTime; previousTime = currentTime; + if (enabledPostpro) rlEnableFBO(); + rlClearScreenBuffers(); rlLoadIdentity(); // Reset current matrix (MODELVIEW) rlMultMatrixf(GetMatrixVector(downscaleView)); // If downscale required, apply it here -// rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 + //rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 // NOTE: Not required with OpenGL 3.3+ } // End canvas drawing and Swap Buffers (Double Buffering) void EndDrawing(void) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + + if (enabledPostpro) rlglDrawPostpro(); // Draw postprocessing effect (shader) SwapBuffers(); // Copy back buffer to front buffer + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) + ResetGestures(); +#endif + PollInputEvents(); // Poll user events UpdateMusicStream(); // NOTE: Function checks if music is enabled @@ -510,7 +529,7 @@ void EndDrawing(void) // Initializes 3D mode for drawing (Camera setup) void Begin3dMode(Camera camera) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) rlMatrixMode(RL_PROJECTION); // Switch to projection matrix @@ -528,14 +547,14 @@ void Begin3dMode(Camera camera) rlLoadIdentity(); // Reset current matrix (MODELVIEW) // Setup Camera view - Matrix matLookAt = MatrixLookAt(camera.position, camera.target, camera.up); - rlMultMatrixf(GetMatrixVector(matLookAt)); // Multiply MODELVIEW matrix by view matrix (camera) + Matrix view = MatrixLookAt(camera.position, camera.target, camera.up); + rlMultMatrixf(GetMatrixVector(view)); // Multiply MODELVIEW matrix by view matrix (camera) } // Ends 3D mode and returns to default 2D orthographic mode void End3dMode(void) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) rlMatrixMode(RL_PROJECTION); // Switch to projection matrix rlPopMatrix(); // Restore previous matrix (PROJECTION) from matrix stack @@ -612,9 +631,8 @@ Color Fade(Color color, float alpha) return (Color){color.r, color.g, color.b, color.a*alpha}; } -// Enable some window configurations (SetWindowFlags()?) -// TODO: Review function name and usage -void SetupFlags(char flags) +// Enable some window/system configurations +void SetConfigFlags(char flags) { configFlags = flags; @@ -628,6 +646,41 @@ void ShowLogo(void) showLogo = true; } +Ray GetMouseRay(Vector2 mousePosition, Camera camera) +{ + Ray ray; + + Matrix proj = MatrixIdentity(); + Matrix view = MatrixLookAt(camera.position, camera.target, camera.up); + + float aspect = (GLfloat)GetScreenWidth()/(GLfloat)GetScreenHeight(); + double top = 0.1f*tanf(45.0f*PI / 360.0f); + double right = top*aspect; + + proj = MatrixFrustum(-right, right, -top, top, 0.01f, 1000.0f); + MatrixTranspose(&proj); + + float realy = (float)GetScreenHeight() - mousePosition.y; + + //float z; + // glReadPixels(mousePosition.x, mousePosition.y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z); + //http://www.bfilipek.com/2012/06/select-mouse-opengl.html + + Vector3 nearPoint = { mousePosition.x, realy, 0.0f }; + Vector3 farPoint = { mousePosition.x, realy, 1.0f }; + + //nearPoint = internalCamera.position; + farPoint = rlglUnproject(farPoint, proj, view); + + Vector3 direction = VectorSubtract(farPoint, nearPoint); + VectorNormalize(&direction); + + ray.position = nearPoint; + ray.direction = direction; + + return ray; +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions //---------------------------------------------------------------------------------- @@ -733,7 +786,7 @@ void SetMousePosition(Vector2 position) { mousePosition = position; #if defined(PLATFORM_DESKTOP) - // NOTE: emscripten not implemented + // NOTE: emscripten not implemented glfwSetCursorPos(window, position.x, position.y); #endif } @@ -741,17 +794,52 @@ void SetMousePosition(Vector2 position) // Returns mouse wheel movement Y int GetMouseWheelMove(void) { - previousMouseWheelY = currentMouseWheelY; - - currentMouseWheelY = 0; - return previousMouseWheelY; } + +// 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; +} + +// 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; +} + +// Check if mouse cursor is hidden +bool IsCursorHidden() +{ + return cursorHidden; +} #endif // TODO: Enable gamepad usage on Rapsberry Pi // NOTE: emscripten not implemented -#if defined(PLATFORM_DESKTOP) +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) // Detect if a gamepad is available bool IsGamepadAvailable(int gamepad) { @@ -848,66 +936,73 @@ bool IsGamepadButtonUp(int gamepad, int button) } #endif -#if defined(PLATFORM_ANDROID) -bool IsScreenTouched(void) -{ - return touchTap; -} - -bool IsDoubleTap(void) -{ - if (doubleTap) TraceLog(INFO, "DOUBLE TAP gesture detected"); - - return doubleTap; -} - -bool IsDragGesture(void) -{ - return drag; -} - +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) // Returns touch position X int GetTouchX(void) { - return (int)touchX; + return (int)GetRawPosition().x; } // Returns touch position Y int GetTouchY(void) { - return (int)touchY; + return (int)GetRawPosition().y; } // Returns touch position XY Vector2 GetTouchPosition(void) { - Vector2 position = { touchX, touchY }; + Vector2 position = GetRawPosition(); + + if ((screenWidth > displayWidth) || (screenHeight > displayHeight)) + { + // TODO: Seems to work ok but... review! + position.x = position.x*((float)screenWidth / (float)(displayWidth - renderOffsetX)) - renderOffsetX/2; + position.y = position.y*((float)screenHeight / (float)(displayHeight - renderOffsetY)) - renderOffsetY/2; + } + else + { + position.x = position.x*((float)renderWidth / (float)displayWidth) - renderOffsetX/2; + position.y = position.y*((float)renderHeight / (float)displayHeight) - renderOffsetY/2; + } return position; } - -/*bool GetPointer(Vector2 *dragPositions) -{ - //static int stdVector[MAX_TOUCH_POINTS]; - //static int indexPosition = 0; - //if (indexPosition == 0) return false; - Vector2 vec_pointers_[]; - - //eventDrag - int32_t iIndex = FindIndex( eventDrag, vec_pointers_[0] ); - - if (iIndex == -1) return false; - - float x = AMotionEvent_getX(eventDrag, iIndex); - float y = AMotionEvent_getY(eventDrag, iIndex); - - *dragPositions = Vector2( x, y ); - - - return true; -}*/ #endif +// Set postprocessing shader +void SetPostproShader(Shader shader) +{ + if (rlGetVersion() == OPENGL_11) TraceLog(WARNING, "Postprocessing shaders not supported on OpenGL 1.1"); + else + { + if (!enabledPostpro) + { + enabledPostpro = true; + rlglInitPostpro(); + rlglSetPostproShader(shader); + } + else + { + rlglSetPostproShader(shader); + } + } +} + +// Set custom shader to be used in batch draw +void SetCustomShader(Shader shader) +{ + rlglSetCustomShader(shader); +} + +// Set default shader to be used in batch draw +void SetDefaultShader(void) +{ + rlglSetDefaultShader(); + + enabledPostpro = false; +} + //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- @@ -946,7 +1041,7 @@ static void InitDisplay(int width, int height) displayWidth = screenWidth; displayHeight = screenHeight; #endif - + glfwDefaultWindowHints(); // Set default windows hints glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); // Avoid window being resizable @@ -956,13 +1051,18 @@ static void InitDisplay(int width, int height) //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 - // NOTE: When asking for an OpenGL context version, most drivers provide highest supported version + // 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. if (rlGetVersion() == OPENGL_33) { - //glfwWindowHint(GLFW_SAMPLES, 4); // Enables multisampling x4 (MSAA), default is 0 + if (configFlags & FLAG_MSAA_4X_HINT) + { + glfwWindowHint(GLFW_SAMPLES, 4); // Enables multisampling x4 (MSAA), default is 0 + TraceLog(INFO, "Enabled 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.2 and above! @@ -1009,12 +1109,18 @@ static void InitDisplay(int width, int height) glfwSetMouseButtonCallback(window, MouseButtonCallback); glfwSetCharCallback(window, CharCallback); glfwSetScrollCallback(window, ScrollCallback); + glfwSetWindowIconifyCallback(window, WindowIconifyCallback); glfwMakeContextCurrent(window); - //glfwSwapInterval(0); // Disables GPU v-sync (if set), 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() + // 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() + if (configFlags & FLAG_VSYNC_HINT) + { + glfwSwapInterval(1); + TraceLog(INFO, "Trying to enable VSYNC"); + } //glfwGetFramebufferSize(window, &renderWidth, &renderHeight); // Get framebuffer size of current window @@ -1036,6 +1142,7 @@ static void InitDisplay(int width, int height) VC_RECT_T srcRect; #endif + // TODO: if (configFlags & FLAG_MSAA_4X_HINT) activate (EGL_SAMPLES, 4) const EGLint framebufferAttribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // Type of context support -> Required on RPI? @@ -1158,7 +1265,7 @@ static void InitDisplay(int width, int height) } // Initialize OpenGL graphics -void InitGraphics(void) +static void InitGraphics(void) { rlglInit(); // Init rlgl @@ -1216,7 +1323,7 @@ static void MouseButtonCallback(GLFWwindow *window, int button, int action, int static void CharCallback(GLFWwindow *window, unsigned int key) { lastKeyPressed = key; - + //TraceLog(INFO, "Char Callback Key pressed: %i\n", key); } @@ -1242,212 +1349,30 @@ static void WindowSizeCallback(GLFWwindow *window, int width, int height) // Background must be also re-cleared ClearBackground(RAYWHITE); } + +// GLFW3 WindowIconify Callback, runs when window is minimized/restored +static void WindowIconifyCallback(GLFWwindow* window, int iconified) +{ + if (iconified) + { + // The window was iconified + PauseMusicStream(); + + windowMinimized = true; + } + else + { + // The window was restored + ResumeMusicStream(); + + windowMinimized = false; + } +} #endif #if defined(PLATFORM_ANDROID) -// Android: Process activity input events -static int32_t InputCallback(struct android_app *app, AInputEvent *event) -{ - int type = AInputEvent_getType(event); - //int32_t key = 0; - - if (type == AINPUT_EVENT_TYPE_MOTION) - { - // Detect TOUCH position - if ((screenWidth > displayWidth) || (screenHeight > displayHeight)) - { - // TODO: Seems to work ok but... review! - touchX = AMotionEvent_getX(event, 0) * ((float)screenWidth / (float)(displayWidth - renderOffsetX)) - renderOffsetX/2; - touchY = AMotionEvent_getY(event, 0) * ((float)screenHeight / (float)(displayHeight - renderOffsetY)) - renderOffsetY/2; - } - else - { - touchX = AMotionEvent_getX(event, 0) * ((float)renderWidth / (float)displayWidth) - renderOffsetX/2; - touchY = AMotionEvent_getY(event, 0) * ((float)renderHeight / (float)displayHeight) - renderOffsetY/2; - } - - // Detect TAP event -/* - if (AMotionEvent_getPointerCount(event) > 1 ) - { - // Only support single touch - return false; - } -*/ - int32_t action = AMotionEvent_getAction(event); - unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; - - switch (flags) - { - case AMOTION_EVENT_ACTION_DOWN: - { - touchId = AMotionEvent_getPointerId(event, 0); - tapTouchX = AMotionEvent_getX(event, 0); - tapTouchY = AMotionEvent_getY(event, 0); - - } break; - case AMOTION_EVENT_ACTION_UP: - { - int64_t eventTime = AMotionEvent_getEventTime(event); - int64_t downTime = AMotionEvent_getDownTime(event); - - if (eventTime - downTime <= TAP_TIMEOUT) - { - if (touchId == AMotionEvent_getPointerId(event, 0)) - { - float x = AMotionEvent_getX(event, 0) - tapTouchX; - float y = AMotionEvent_getY(event, 0) - tapTouchY; - - float densityFactor = 1.0f; - - if ( x*x + y*y < TOUCH_SLOP*TOUCH_SLOP * densityFactor) - { - // TAP Detected - touchTap = true; - } - } - } - break; - } - } - - //float AMotionEvent_getX(event, size_t pointer_index); - //int32_t AMotionEvent_getButtonState(event); // Pressed buttons - //int32_t AMotionEvent_getPointerId(event, size_t pointer_index); - //size_t pointerCount = AMotionEvent_getPointerCount(event); - //float AMotionEvent_getPressure(const AInputEvent *motion_event, size_t pointer_index); // 0 to 1 - //float AMotionEvent_getSize(const AInputEvent *motion_event, size_t pointer_index); // Pressed area - - // Detect DOUBLE TAP event - bool tapDetected = touchTap; - - switch (flags) - { - case AMOTION_EVENT_ACTION_DOWN: - { - int64_t eventTime = AMotionEvent_getEventTime(event); - - if (eventTime - lastTapTime <= DOUBLE_TAP_TIMEOUT) - { - float x = AMotionEvent_getX(event, 0) - lastTapX; - float y = AMotionEvent_getY(event, 0) - lastTapY; - - float densityFactor = 1.0f; - - if ((x*x + y*y) < (DOUBLE_TAP_SLOP*DOUBLE_TAP_SLOP*densityFactor)) - { - // Doubletap detected - doubleTap = true; - - } - } - } break; - case AMOTION_EVENT_ACTION_UP: - { - if (tapDetected) - { - lastTapTime = AMotionEvent_getEventTime(event); - lastTapX = AMotionEvent_getX(event, 0); - lastTapY = AMotionEvent_getY(event, 0); - - } - } break; - } - - - // Detect DRAG event - //int32_t action = AMotionEvent_getAction(event); - - int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; - //uint32_t flags = action & AMOTION_EVENT_ACTION_MASK; - //event_ = event; - - int32_t count = AMotionEvent_getPointerCount(event); - - switch (flags) - { - case AMOTION_EVENT_ACTION_DOWN: - { - stdVector[indexPosition] = AMotionEvent_getPointerId(event, 0); - indexPosition++; - TraceLog(INFO, "ACTION_DOWN"); - - //ret = GESTURE_STATE_START; - } break; - case AMOTION_EVENT_ACTION_POINTER_DOWN: - { - stdVector[indexPosition] = AMotionEvent_getPointerId(event, index); - indexPosition++; - TraceLog(INFO, "ACTION_POINTER_DOWN"); - - } break; - case AMOTION_EVENT_ACTION_UP: - { - //int value = stdVector[indexPosition]; - indexPosition--; - //ret = GESTURE_STATE_END; - TraceLog(INFO, "ACTION_UP"); - - } break; - case AMOTION_EVENT_ACTION_POINTER_UP: - { - int32_t releasedPointerId = AMotionEvent_getPointerId(event, index); - - int i = 0; - for (i = 0; i < MAX_TOUCH_POINTS; i++) - { - if (stdVector[i] == releasedPointerId) - { - for (int k = i; k < indexPosition - 1; k++) - { - stdVector[k] = stdVector[k + 1]; - } - - //indexPosition--; - indexPosition = 0; - break; - } - } - - if (i <= 1) - { - // Reset pinch or drag - //if (count == 2) //ret = GESTURE_STATE_START; - } - TraceLog(INFO, "ACTION_POINTER_UP"); - - } break; - case AMOTION_EVENT_ACTION_MOVE: - { - if (count == 1) - { - //TraceLog(INFO, "DRAG gesture detected"); - - drag = true; //ret = GESTURE_STATE_MOVE; - } - else break; - TraceLog(INFO, "ACTION_MOVE"); - - } break; - case AMOTION_EVENT_ACTION_CANCEL: break; - default: break; - } - - //-------------------------------------------------------------------- - - return 1; - } - else if (type == AINPUT_EVENT_TYPE_KEY) - { - //key = AKeyEvent_getKeyCode(event); - //int32_t AKeyEvent_getMetaState(event); - } - - return 0; -} - // Android: Process activity lifecycle commands -static void CommandCallback(struct android_app *app, int32_t cmd) +static void AndroidCommandCallback(struct android_app *app, int32_t cmd) { switch (cmd) { @@ -1636,24 +1561,24 @@ static void PollInputEvents(void) // Keyboard polling // Automatically managed by GLFW3 through callback lastKeyPressed = -1; - + // 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]; - - glfwPollEvents(); // Register keyboard/mouse events + + previousMouseWheelY = currentMouseWheelY; + currentMouseWheelY = 0; + + glfwPollEvents(); // Register keyboard/mouse events... and window events! #elif defined(PLATFORM_ANDROID) // TODO: Check virtual keyboard (?) - // Reset touch events - touchTap = false; - doubleTap = false; - drag = false; - // Poll Events (registered events) + // TODO: Enable/disable activityMinimized to block activity if minimized + //while ((ident = ALooper_pollAll(activityMinimized ? 0 : -1, NULL, &events,(void**)&source)) >= 0) while ((ident = ALooper_pollAll(0, NULL, &events,(void**)&source)) >= 0) { // Process this event @@ -1664,8 +1589,8 @@ static void PollInputEvents(void) { // NOTE: Never close window, native activity is controlled by the system! //TraceLog(INFO, "Closing Window..."); - //windowShouldClose = true; - + //windowShouldClose = true; + //ANativeActivity_finish(app->activity); } } @@ -1952,13 +1877,13 @@ static void SetupFramebufferSize(int displayWidth, int displayHeight) if (widthRatio <= heightRatio) { renderWidth = displayWidth; - renderHeight = (int)((float)screenHeight*widthRatio); + renderHeight = (int)round((float)screenHeight*widthRatio); renderOffsetX = 0; renderOffsetY = (displayHeight - renderHeight); } else { - renderWidth = (int)((float)screenWidth*heightRatio); + renderWidth = (int)round((float)screenWidth*heightRatio); renderHeight = displayHeight; renderOffsetX = (displayWidth - renderWidth); renderOffsetY = 0; @@ -1988,13 +1913,13 @@ static void SetupFramebufferSize(int displayWidth, int displayHeight) if (displayRatio <= screenRatio) { renderWidth = screenWidth; - renderHeight = (int)((float)screenWidth/displayRatio); + renderHeight = (int)round((float)screenWidth/displayRatio); renderOffsetX = 0; renderOffsetY = (renderHeight - screenHeight); } else { - renderWidth = (int)((float)screenHeight*displayRatio); + renderWidth = (int)round((float)screenHeight*displayRatio); renderHeight = screenHeight; renderOffsetX = (renderWidth - screenWidth); renderOffsetY = 0; @@ -2134,4 +2059,3 @@ static void LogoAnimation(void) showLogo = false; // Prevent for repeating when reloading window (Android) } - diff --git a/src/gestures.c b/src/gestures.c new file mode 100644 index 000000000..13209b328 --- /dev/null +++ b/src/gestures.c @@ -0,0 +1,645 @@ +/********************************************************************************************** +* +* raylib.gestures +* +* Gestures Detection and Usage Functions Definitions +* +* Copyright (c) 2015 Marc Palau and Ramon Santamaria +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" +#include "utils.h" + +#include // malloc(), free() +#include // printf(), fprintf() +#include // Used for ... +#include // Defines int32_t, int64_t + +#if defined(_WIN32) + //#include +#elif defined(__linux) + #include // Used for clock functions +#endif + +#if defined(PLATFORM_ANDROID) + #include // Java native interface + #include // Android sensors functions + #include // Defines AWINDOW_FLAG_FULLSCREEN and others +#endif + +#if defined(PLATFORM_WEB) + #include + #include +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define FORCE_TO_SWIPE 20 +#define TAP_TIMEOUT 300 + +#define MAX_TOUCH_POINTS 4 + +typedef enum { + TYPE_MOTIONLESS, + TYPE_DRAG, + TYPE_DUAL_INPUT +} GestureType; + +typedef enum { + UP, + DOWN, + MOVE +} ActionType; + +typedef struct { + ActionType action; + int pointCount; + int pointerId[MAX_TOUCH_POINTS]; + Vector2 position[MAX_TOUCH_POINTS]; +} GestureEvent; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- + +static GestureType gestureType = TYPE_MOTIONLESS; +static double eventTime = 0; +//static int32_t touchId; // Not used... + +// Tap +// Our initial press position on tap +static Vector2 initialTapPosition = { 0, 0 }; + +// Double tap +// If we are double tapping or not +static bool doubleTapping = false; +// If we recently made a tap +static bool untap = false; + +// Drag +// Our initial press position on drag +static Vector2 initialDragPosition = { 0, 0 }; +// Position that will compare itself with the mouse one +static Vector2 endDragPosition = { 0, 0 }; +// Position of the last event detection +static Vector2 lastDragPosition = { 0, 0 }; +// The total drag vector +static Vector2 dragVector = { 0, 0 }; +// The distance traveled dragging +static float magnitude = 0; +// The angle direction of the drag +static float angle = 0; +// A magnitude to calculate how fast we did the drag ( pixels per frame ) +static float intensity = 0; +// Time that have passed while dragging +static int draggingTimeCounter = 0; + +// Pinch +// First initial pinch position +static Vector2 firstInitialPinchPosition = { 0, 0 }; +// Second initial pinch position +static Vector2 secondInitialPinchPosition = { 0, 0 }; +// First end pinch position +static Vector2 firstEndPinchPosition = { 0, 0 }; +// Second end pinch position +static Vector2 secondEndPinchPosition = { 0, 0 }; +// Delta Displacement +static float pinchDelta = 0; + +// Detected gesture +static int currentGesture = GESTURE_NONE; + +static Vector2 touchPosition; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +extern void ProcessMotionEvent(GestureEvent event); +extern void ResetGestures(void); +extern Vector2 GetRawPosition(void); + +static float CalculateAngle(Vector2 initialPosition, Vector2 actualPosition, float magnitude); +static float OnPinch(); +static void SetDualInput(GestureEvent event); +static float Distance(Vector2 v1, Vector2 v2); +static float DotProduct(Vector2 v1, Vector2 v2); +static double GetCurrentTime(); + +#if defined(PLATFORM_WEB) +static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); +#endif + +#if defined(PLATFORM_ANDROID) +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Returns tap position XY +extern Vector2 GetRawPosition(void) +{ + return touchPosition; +} + +// Check if a gesture have been detected +bool IsGestureDetected(void) +{ + if (currentGesture == GESTURE_DRAG) TraceLog(INFO, "DRAG"); + else if (currentGesture == GESTURE_TAP) TraceLog(INFO, "TAP"); + else if (currentGesture == GESTURE_DOUBLETAP) TraceLog(INFO, "DOUBLE"); + else if (currentGesture == GESTURE_HOLD) TraceLog(INFO, "HOLD"); + else if (currentGesture == GESTURE_SWIPE_RIGHT) TraceLog(INFO, "RIGHT"); + else if (currentGesture == GESTURE_SWIPE_UP) TraceLog(INFO, "UP"); + else if (currentGesture == GESTURE_SWIPE_LEFT) TraceLog(INFO, "LEFT"); + else if (currentGesture == GESTURE_SWIPE_DOWN) TraceLog(INFO, "DOWN"); + else if (currentGesture == GESTURE_PINCH_IN) TraceLog(INFO, "PINCH IN"); + else if (currentGesture == GESTURE_PINCH_OUT) TraceLog(INFO, "PINCH OUT"); + + if (currentGesture != GESTURE_NONE) return true; + else return false; +} + +// Check gesture type +int GetGestureType(void) +{ + return currentGesture; +} + +// Get drag intensity (pixels per frame) +float GetDragIntensity(void) +{ + return intensity; +} + +// Get drag angle +// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise +float GetDragAngle(void) +{ + return angle; +} + +// Get drag vector (between initial and final position) +Vector2 GetDragVector(void) +{ + return dragVector; +} + +// Hold time measured in frames +int GetHoldDuration(void) +{ + return 0; +} + +// Get magnitude between two pinch points +float GetPinchDelta(void) +{ + return pinchDelta; +} + +// Get angle beween two pinch points +// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise +float GetPinchAngle(void) +{ + return 0; +} + +extern void ResetGestures(void) +{ + if (currentGesture == GESTURE_TAP) currentGesture = GESTURE_HOLD; + else if (currentGesture != GESTURE_HOLD) currentGesture = GESTURE_NONE; +} + +#if defined(PLATFORM_WEB) +extern void InitWebGestures(void) +{ + /* + emscripten_set_touchstart_callback("#canvas", data, 0, Emscripten_HandleTouch); + emscripten_set_touchend_callback("#canvas", data, 0, Emscripten_HandleTouch); + emscripten_set_touchmove_callback("#canvas", data, 0, Emscripten_HandleTouch); + emscripten_set_touchcancel_callback("#canvas", data, 0, Emscripten_HandleTouch); + */ + + //emscripten_set_touchstart_callback(0, NULL, 1, Emscripten_HandleTouch); + + emscripten_set_touchstart_callback("#canvas", NULL, 1, EmscriptenInputCallback); + emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenInputCallback); + emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenInputCallback); + emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenInputCallback); +} +#endif + +#if defined(PLATFORM_ANDROID) +extern void InitAndroidGestures(struct android_app *app) +{ + app->onInputEvent = AndroidInputCallback; + + // TODO: Receive frameBuffer data: displayWidth/displayHeight, renderWidth/renderHeight, screenWidth/screenHeight +} +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +extern void ProcessMotionEvent(GestureEvent event) +{ + // Resets + dragVector = (Vector2){ 0, 0 }; + pinchDelta = 0; + + switch (gestureType) + { + case TYPE_MOTIONLESS: // Detect TAP, DOUBLE_TAP and HOLD events + { + if (event.action == DOWN) + { + if (event.pointCount > 1) SetDualInput(event); + else + { + // Set the press position + initialTapPosition = event.position[0]; + + // If too much time have passed, we reset the double tap + if (GetCurrentTime() - eventTime > TAP_TIMEOUT) untap = false; + + // If we are in time, we detect the double tap + if (untap) doubleTapping = true; + + // Update our event time + eventTime = GetCurrentTime(); + + // Set hold + if (doubleTapping) currentGesture = GESTURE_DOUBLETAP; + else currentGesture = GESTURE_TAP; + } + } + else if (event.action == UP) + { + currentGesture = GESTURE_NONE; + + // Detect that we are tapping instead of holding + if (GetCurrentTime() - eventTime < TAP_TIMEOUT) + { + if (doubleTapping) untap = false; + else untap = true; + } + + // Tap finished + doubleTapping = false; + + // Update our event time + eventTime = GetCurrentTime(); + } + // Begin dragging + else if (event.action == MOVE) + { + if (event.pointCount > 1) SetDualInput(event); + else + { + // Set the drag starting position + initialDragPosition = initialTapPosition; + endDragPosition = initialDragPosition; + + // Initialize drag + draggingTimeCounter = 0; + gestureType = TYPE_DRAG; + currentGesture = GESTURE_NONE; + } + } + } break; + case TYPE_DRAG: // Detect DRAG and SWIPE events + { + // end of the drag + if (event.action == UP) + { + // Return Swipe if we have enough sensitivity + if (intensity > FORCE_TO_SWIPE) + { + if (angle < 30 || angle > 330) currentGesture = GESTURE_SWIPE_RIGHT; // Right + else if (angle > 60 && angle < 120) currentGesture = GESTURE_SWIPE_UP; // Up + else if (angle > 150 && angle < 210) currentGesture = GESTURE_SWIPE_LEFT; // Left + else if (angle > 240 && angle < 300) currentGesture = GESTURE_SWIPE_DOWN; // Down + } + + magnitude = 0; + angle = 0; + intensity = 0; + + gestureType = TYPE_MOTIONLESS; + } + // Update while we are dragging + else if (event.action == MOVE) + { + if (event.pointCount > 1) SetDualInput(event); + else + { + lastDragPosition = endDragPosition; + + endDragPosition = GetRawPosition(); + + //endDragPosition.x = AMotionEvent_getX(event, 0); + //endDragPosition.y = AMotionEvent_getY(event, 0); + + // Calculate attributes + dragVector = (Vector2){ endDragPosition.x - lastDragPosition.x, endDragPosition.y - lastDragPosition.y }; + magnitude = sqrt(pow(endDragPosition.x - initialDragPosition.x, 2) + pow(endDragPosition.y - initialDragPosition.y, 2)); + angle = CalculateAngle(initialDragPosition, endDragPosition, magnitude); + intensity = magnitude / (float)draggingTimeCounter; + + currentGesture = GESTURE_DRAG; + draggingTimeCounter++; + } + } + } break; + case TYPE_DUAL_INPUT: + { + if (event.action == UP) + { + if (event.pointCount == 1) + { + // Set the drag starting position + initialTapPosition = event.position[0]; + } + gestureType = TYPE_MOTIONLESS; + } + else if (event.action == MOVE) + { + // Adapt the ending position of the inputs + firstEndPinchPosition = event.position[0]; + secondEndPinchPosition = event.position[1]; + + // If there is no more than two inputs + if (event.pointCount == 2) + { + // Detect pinch delta + pinchDelta = OnPinch(); + + // Pinch gesture resolution + if (pinchDelta != 0) + { + if (pinchDelta > 0) currentGesture = GESTURE_PINCH_IN; + else currentGesture = GESTURE_PINCH_OUT; + } + } + else + { + // Set the drag starting position + initialTapPosition = event.position[0]; + + gestureType = TYPE_MOTIONLESS; + } + + // Readapt the initial position of the inputs + firstInitialPinchPosition = firstEndPinchPosition; + secondInitialPinchPosition = secondEndPinchPosition; + } + } break; + } + //-------------------------------------------------------------------- +} + + +static float CalculateAngle(Vector2 initialPosition, Vector2 actualPosition, float magnitude) +{ + float angle; + + // Calculate arcsinus of the movement ( Our sinus is (actualPosition.y - initialPosition.y) / magnitude) + angle = asin((actualPosition.y - initialPosition.y) / magnitude); + angle *= RAD2DEG; + + // Calculate angle depending on the sector + if (actualPosition.x - initialPosition.x >= 0) + { + // Sector 4 + if (actualPosition.y - initialPosition.y >= 0) + { + angle *= -1; + angle += 360; + } + // Sector 1 + else + { + angle *= -1; + } + } + else + { + // Sector 3 + if (actualPosition.y - initialPosition.y >= 0) + { + angle += 180; + } + // Sector 2 + else + { + angle *= -1; + angle = 180 - angle; + } + } + + return angle; +} + +static float OnPinch() +{ + // Calculate distances + float initialDistance = Distance(firstInitialPinchPosition, secondInitialPinchPosition); + float endDistance = Distance(firstEndPinchPosition, secondEndPinchPosition); + + // Calculate Vectors + Vector2 firstTouchVector = { firstEndPinchPosition.x - firstInitialPinchPosition.x, firstEndPinchPosition.y - firstInitialPinchPosition.y }; + Vector2 secondTouchVector = { secondEndPinchPosition.x - secondInitialPinchPosition.x, secondEndPinchPosition.y - secondInitialPinchPosition.y }; + + // Detect the pinch gesture + // Calculate Distances + if (DotProduct(firstTouchVector, secondTouchVector) < -0.5) return initialDistance - endDistance; + else return 0; +} + +static void SetDualInput(GestureEvent event) +{ + initialDragPosition = (Vector2){ 0, 0 }; + endDragPosition = (Vector2){ 0, 0 }; + lastDragPosition = (Vector2){ 0, 0 }; + + // Initialize positions + firstInitialPinchPosition = event.position[0]; + secondInitialPinchPosition = event.position[1]; + + firstEndPinchPosition = firstInitialPinchPosition; + secondEndPinchPosition = secondInitialPinchPosition; + + // Resets + magnitude = 0; + angle = 0; + intensity = 0; + + gestureType = TYPE_DUAL_INPUT; +} + +static float Distance(Vector2 v1, Vector2 v2) +{ + float result; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + + result = sqrt(dx*dx + dy*dy); + + return result; +} + +static float DotProduct(Vector2 v1, Vector2 v2) +{ + float result; + + float v1Module = sqrt(v1.x*v1.x + v1.y*v1.y); + float v2Module = sqrt(v2.x*v2.x + v2.y*v2.y); + + Vector2 v1Normalized = { v1.x / v1Module, v1.y / v1Module }; + Vector2 v2Normalized = { v2.x / v2Module, v2.y / v2Module }; + + result = v1Normalized.x*v2Normalized.x + v1Normalized.y*v2Normalized.y; + + return result; +} + +static double GetCurrentTime() +{ + double time = 0; +#if defined(_WIN32) +/* + // NOTE: Requires Windows.h + FILETIME tm; + GetSystemTimePreciseAsFileTime(&tm); + ULONGLONG nowTime = ((ULONGLONG)tm.dwHighDateTime << 32) | (ULONGLONG)tm.dwLowDateTime; // Time provided in 100-nanosecond intervals + + time = ((double)nowTime/10000000.0); // time in seconds +*/ +#endif + +#if defined(__linux) + // NOTE: Only for Linux-based systems + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + uint64_t nowTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; // Time provided in nanoseconds + + time = ((double)nowTime/1000000.0); // time in miliseconds +#endif + + return time; +} + +#if defined(PLATFORM_ANDROID) +// Android: Get input events +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) +{ + int type = AInputEvent_getType(event); + + if (type == AINPUT_EVENT_TYPE_MOTION) + { + touchPosition.x = AMotionEvent_getX(event, 0); + touchPosition.y = AMotionEvent_getY(event, 0); + } + else if (type == AINPUT_EVENT_TYPE_KEY) + { + //int32_t key = AKeyEvent_getKeyCode(event); + //int32_t AKeyEvent_getMetaState(event); + } + + int32_t action = AMotionEvent_getAction(event); + unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; + + GestureEvent gestureEvent; + + // Action + if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.action = DOWN; + else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.action = UP; + else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.action = MOVE; + + // Points + gestureEvent.pointCount = AMotionEvent_getPointerCount(event); + + // Position + gestureEvent.position[0] = (Vector2){ AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0) }; + gestureEvent.position[1] = (Vector2){ AMotionEvent_getX(event, 1), AMotionEvent_getY(event, 1) }; + + ProcessMotionEvent(gestureEvent); + + return 0; +} +#endif + +#if defined(PLATFORM_WEB) +// Web: Get input events +static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) +{ + /* + for (int i = 0; i < touchEvent->numTouches; i++) + { + long x, y, id; + + if (!touchEvent->touches[i].isChanged) continue; + + 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" : ""); + + for(int i = 0; i < event->numTouches; ++i) + { + 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; + + // Action + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.action = DOWN; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.action = UP; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.action = MOVE; + + // Points + gestureEvent.pointCount = touchEvent->numTouches; + + // Position + gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].canvasX, touchEvent->touches[0].canvasY }; + gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].canvasX, touchEvent->touches[1].canvasY }; + + touchPosition = gestureEvent.position[0]; + + ProcessMotionEvent(gestureEvent); + + return 1; +} +#endif + + + + + + diff --git a/src/makefile b/src/makefile index a726b92e4..11bbea9d5 100644 --- a/src/makefile +++ b/src/makefile @@ -93,7 +93,7 @@ else endif # define all object files required -OBJS = core.o rlgl.o raymath.o shapes.o text.o textures.o models.o audio.o utils.o stb_vorbis.o +OBJS = core.o rlgl.o raymath.o shapes.o text.o textures.o models.o audio.o utils.o camera.o gestures.o # typing 'make' will invoke the first target entry in the file, # in this case, the 'default' target entry is raylib @@ -144,9 +144,13 @@ audio.o: audio.c utils.o: utils.c $(CC) -c utils.c $(CFLAGS) $(INCLUDES) -D$(PLATFORM) -# compile stb_vorbis library -stb_vorbis.o: stb_vorbis.c - $(CC) -c stb_vorbis.c $(CFLAGS) $(INCLUDES) -D$(PLATFORM) +# compile camera module +camera.o: camera.c + $(CC) -c camera.c $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile gestures module +gestures.o: gestures.c + $(CC) -c gestures.c $(CFLAGS) $(INCLUDES) -D$(PLATFORM) # clean everything clean: @@ -155,8 +159,7 @@ ifeq ($(PLATFORM),PLATFORM_DESKTOP) rm -f *.o libraylib.a else ifeq ($(PLATFORM_OS),LINUX) - find . -type f -executable -delete - rm -f *.o libraylib.a + find -type f -executable | xargs file -i | grep -E 'x-object|x-archive|x-sharedlib|x-executable' | rev | cut -d ':' -f 2- | rev | xargs rm -f else del *.o libraylib.a endif diff --git a/src/models.c b/src/models.c index f61f79f51..8d772f023 100644 --- a/src/models.c +++ b/src/models.c @@ -40,7 +40,7 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -// Nop... +#define CUBIC_MAP_HALF_BLOCK_SIZE 0.5 //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -285,7 +285,6 @@ void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color rlPushMatrix(); rlTranslatef(centerPos.x, centerPos.y, centerPos.z); rlScalef(radius, radius, radius); - //rlRotatef(rotation, 0, 1, 0); rlBegin(RL_TRIANGLES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -325,7 +324,6 @@ void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Col rlPushMatrix(); rlTranslatef(centerPos.x, centerPos.y, centerPos.z); rlScalef(radius, radius, radius); - //rlRotatef(rotation, 0, 1, 0); rlBegin(RL_LINES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -446,48 +444,17 @@ void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, fl rlPopMatrix(); } -// Draw a quad -void DrawQuad(Vector3 vertices[4], Vector2 textcoords[4], Vector3 normals[4], Color colors[4]) -{ - rlBegin(RL_QUADS); - rlColor4ub(colors[0].r, colors[0].g, colors[0].b, colors[0].a); - rlNormal3f(normals[0].x, normals[0].y, normals[0].z); - rlTexCoord2f(textcoords[0].x, textcoords[0].y); - rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z); - - rlColor4ub(colors[1].r, colors[1].g, colors[1].b, colors[1].a); - rlNormal3f(normals[1].x, normals[1].y, normals[1].z); - rlTexCoord2f(textcoords[1].x, textcoords[1].y); - rlVertex3f(vertices[1].x, vertices[1].y, vertices[1].z); - - rlColor4ub(colors[2].r, colors[2].g, colors[2].b, colors[2].a); - rlNormal3f(normals[2].x, normals[2].y, normals[2].z); - rlTexCoord2f(textcoords[2].x, textcoords[2].y); - rlVertex3f(vertices[2].x, vertices[2].y, vertices[2].z); - - rlColor4ub(colors[3].r, colors[3].g, colors[3].b, colors[3].a); - rlNormal3f(normals[3].x, normals[3].y, normals[3].z); - rlTexCoord2f(textcoords[3].x, textcoords[3].y); - rlVertex3f(vertices[3].x, vertices[3].y, vertices[3].z); - rlEnd(); -} - // Draw a plane -void DrawPlane(Vector3 centerPos, Vector2 size, Vector3 rotation, Color color) +void DrawPlane(Vector3 centerPos, Vector2 size, Color color) { // NOTE: QUADS usage require defining a texture on OpenGL 3.3+ if (rlGetVersion() != OPENGL_11) rlEnableTexture(whiteTexture); // Default white texture - // NOTE: Plane is always created on XZ ground and then rotated + // NOTE: Plane is always created on XZ ground rlPushMatrix(); rlTranslatef(centerPos.x, centerPos.y, centerPos.z); rlScalef(size.x, 1.0f, size.y); - // TODO: Review multiples rotations Gimbal-Lock... use matrix or quaternions... - rlRotatef(rotation.x, 1, 0, 0); - rlRotatef(rotation.y, 0, 1, 0); - rlRotatef(rotation.z, 0, 0, 1); - rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); rlNormal3f(0.0f, 1.0f, 0.0f); @@ -501,51 +468,34 @@ void DrawPlane(Vector3 centerPos, Vector2 size, Vector3 rotation, Color color) if (rlGetVersion() != OPENGL_11) rlDisableTexture(); } -// Draw a plane with divisions -// TODO: Test this function -void DrawPlaneEx(Vector3 centerPos, Vector2 size, Vector3 rotation, int slicesX, int slicesZ, Color color) +// Draw a quad +void DrawQuad(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, Color color) { - float quadWidth = size.x / slicesX; - float quadLenght = size.y / slicesZ; + // TODO: Calculate normals from vertex position + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + //rlNormal3f(0.0f, 0.0f, 0.0f); - float texPieceW = 1 / size.x; - float texPieceH = 1 / size.y; + rlVertex3f(v1.x, v1.y, v1.z); + rlVertex3f(v2.x, v2.y, v2.z); + rlVertex3f(v3.x, v3.y, v3.z); + rlVertex3f(v4.x, v4.y, v4.z); + rlEnd(); +} - // NOTE: Plane is always created on XZ ground and then rotated - rlPushMatrix(); - rlTranslatef(-size.x / 2, 0.0f, -size.y / 2); - rlTranslatef(centerPos.x, centerPos.y, centerPos.z); +// Draw a ray line +void DrawRay(Ray ray, Color color) +{ + float scale = 10000; - // TODO: Review multiples rotations Gimbal-Lock... use matrix or quaternions... - rlRotatef(rotation.x, 1, 0, 0); - rlRotatef(rotation.y, 0, 1, 0); - rlRotatef(rotation.z, 0, 0, 1); + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlColor4ub(color.r, color.g, color.b, color.a); - rlBegin(RL_QUADS); - rlColor4ub(color.r, color.g, color.b, color.a); - rlNormal3f(0.0f, 1.0f, 0.0f); - - for (int z = 0; z < slicesZ; z++) - { - for (int x = 0; x < slicesX; x++) - { - // Draw the plane quad by quad (with textcoords) - rlTexCoord2f((float)x * texPieceW, (float)z * texPieceH); - rlVertex3f((float)x * quadWidth, 0.0f, (float)z * quadLenght); - - rlTexCoord2f((float)x * texPieceW + texPieceW, (float)z * texPieceH); - rlVertex3f((float)x * quadWidth + quadWidth, 0.0f, (float)z * quadLenght); - - rlTexCoord2f((float)x * texPieceW + texPieceW, (float)z * texPieceH + texPieceH); - rlVertex3f((float)x * quadWidth + quadWidth, 0.0f, (float)z * quadLenght + quadLenght); - - rlTexCoord2f((float)x * texPieceW, (float)z * texPieceH + texPieceH); - rlVertex3f((float)x * quadWidth, 0.0f, (float)z * quadLenght + quadLenght); - } - } - rlEnd(); - - rlPopMatrix(); + rlVertex3f(ray.position.x, ray.position.y, ray.position.z); + rlVertex3f(ray.position.x + ray.direction.x*scale, ray.position.y + ray.direction.y*scale, ray.position.z + ray.direction.z*scale); + rlEnd(); } // Draw a grid centered at (0, 0, 0) @@ -604,76 +554,7 @@ void DrawGizmo(Vector3 position) rlPopMatrix(); } -void DrawGizmoEx(Vector3 position, Vector3 rotation, float scale) -{ - // NOTE: RGB = XYZ - rlPushMatrix(); - rlTranslatef(position.x, position.y, position.z); - rlScalef(scale, scale, scale); - rlRotatef(rotation.y, 0, 1, 0); - - rlBegin(RL_LINES); - // X Axis - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x, position.y, position.z); - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x + 1, position.y, position.z); - - // ArrowX - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x + 1.1, position.y, position.z); - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x + .9, position.y, position.z + .1); - - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x + 1.1, position.y, position.z); - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x + .9, position.y, position.z - .1); - - // Y Axis - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x, position.y, position.z); - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x, position.y + 1, position.z); - - // ArrowY - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x, position.y + 1.1, position.z); - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x + .1, position.y + .9, position.z); - - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x, position.y + 1.1, position.z); - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x - .1, position.y + .9, position.z); - - // Z Axis - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x, position.y, position.z); - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x, position.y, position.z - 1); - - // ArrowZ - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x, position.y, position.z - 1.1); - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x + .1, position.y, position.z - .9); - - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x, position.y, position.z - 1.1); - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x - .1, position.y, position.z - .9); - - // Extra - int n = 3; - - // X Axis - for (int i=0; i < 360; i += 6) - { - rlColor4ub(200, 0, 0, 255); rlVertex3f(0, position.x + sin(DEG2RAD*i) * scale/n, position.y + cos(DEG2RAD*i) * scale/n); - rlColor4ub(200, 0, 0, 255); rlVertex3f(0, position.x + sin(DEG2RAD*(i+6)) * scale/n, position.y + cos(DEG2RAD*(i+6)) * scale/n); - } - - // Y Axis - for (int i=0; i < 360; i += 6) - { - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x + sin(DEG2RAD*i) * scale/n, 0, position.y + cos(DEG2RAD*i) * scale/n); - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x + sin(DEG2RAD*(i+6)) * scale/n, 0, position.y + cos(DEG2RAD*(i+6)) * scale/n); - } - - // Z Axis - for (int i=0; i < 360; i += 6) - { - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x + sin(DEG2RAD*i) * scale/n, position.y + cos(DEG2RAD*i) * scale/n, 0); - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x + sin(DEG2RAD*(i+6)) * scale/n, position.y + cos(DEG2RAD*(i+6)) * scale/n, 0); - } - rlEnd(); - rlPopMatrix(); -} - -// Load a 3d model +// Load a 3d model (from file) Model LoadModel(const char *fileName) { VertexData vData; @@ -683,17 +564,34 @@ Model LoadModel(const char *fileName) // NOTE: At this point we have all vertex, texcoord, normal data for the model in vData struct + // NOTE: model properties (transform, texture, shader) are initialized inside rlglLoadModel() Model model = rlglLoadModel(vData); // Upload vertex data to GPU // Now that vertex data is uploaded to GPU, we can free arrays - // NOTE: Despite vertex data is useless on OpenGL 3.3 or ES2, we will keep it... - //free(vData.vertices); - //free(vData.texcoords); - //free(vData.normals); + // NOTE: We don't need CPU vertex data on OpenGL 3.3 or ES2 + if (rlGetVersion() != OPENGL_11) + { + free(vData.vertices); + free(vData.texcoords); + free(vData.normals); + } return model; } +// Load a 3d model (from vertex data) +Model LoadModelEx(VertexData data) +{ + Model model; + + // NOTE: model properties (transform, texture, shader) are initialized inside rlglLoadModel() + model = rlglLoadModel(data); // Upload vertex data to GPU + + // NOTE: Vertex data is managed externally, must be deallocated manually + + return model; +} + // Load a heightmap image as a 3d model Model LoadHeightmap(Image heightmap, float maxHeight) { @@ -701,6 +599,8 @@ Model LoadHeightmap(Image heightmap, float maxHeight) int mapX = heightmap.width; int mapZ = heightmap.height; + + Color *heightmapPixels = GetPixelData(heightmap); // NOTE: One vertex per pixel // TODO: Consider resolution when generating model data? @@ -730,15 +630,15 @@ Model LoadHeightmap(Image heightmap, float maxHeight) // one triangle - 3 vertex vData.vertices[vCounter] = x; - vData.vertices[vCounter + 1] = GetHeightValue(heightmap.pixels[x + z*mapX])*scaleFactor; + vData.vertices[vCounter + 1] = GetHeightValue(heightmapPixels[x + z*mapX])*scaleFactor; vData.vertices[vCounter + 2] = z; vData.vertices[vCounter + 3] = x; - vData.vertices[vCounter + 4] = GetHeightValue(heightmap.pixels[x + (z+1)*mapX])*scaleFactor; + vData.vertices[vCounter + 4] = GetHeightValue(heightmapPixels[x + (z+1)*mapX])*scaleFactor; vData.vertices[vCounter + 5] = z+1; vData.vertices[vCounter + 6] = x+1; - vData.vertices[vCounter + 7] = GetHeightValue(heightmap.pixels[(x+1) + z*mapX])*scaleFactor; + vData.vertices[vCounter + 7] = GetHeightValue(heightmapPixels[(x+1) + z*mapX])*scaleFactor; vData.vertices[vCounter + 8] = z; // another triangle - 3 vertex @@ -751,7 +651,7 @@ Model LoadHeightmap(Image heightmap, float maxHeight) vData.vertices[vCounter + 14] = vData.vertices[vCounter + 5]; vData.vertices[vCounter + 15] = x+1; - vData.vertices[vCounter + 16] = GetHeightValue(heightmap.pixels[(x+1) + (z+1)*mapX])*scaleFactor; + vData.vertices[vCounter + 16] = GetHeightValue(heightmapPixels[(x+1) + (z+1)*mapX])*scaleFactor; vData.vertices[vCounter + 17] = z+1; vCounter += 18; // 6 vertex, 18 floats @@ -793,6 +693,8 @@ Model LoadHeightmap(Image heightmap, float maxHeight) trisCounter += 2; } } + + free(heightmapPixels); // Fill color data // NOTE: Not used any more... just one plain color defined at DrawModel() @@ -803,26 +705,31 @@ Model LoadHeightmap(Image heightmap, float maxHeight) Model model = rlglLoadModel(vData); // Now that vertex data is uploaded to GPU, we can free arrays - // NOTE: Despite vertex data is useless on OpenGL 3.3 or ES2, we will keep it... - //free(vData.vertices); - //free(vData.texcoords); - //free(vData.normals); + // NOTE: We don't need CPU vertex data on OpenGL 3.3 or ES2 + if (rlGetVersion() != OPENGL_11) + { + free(vData.vertices); + free(vData.texcoords); + free(vData.normals); + } return model; } // Load a map image as a 3d model (cubes based) -Model LoadCubicmap(Image cubesmap) +Model LoadCubicmap(Image cubicmap) { VertexData vData; + Color *cubicmapPixels = GetPixelData(cubicmap); + // Map cube size will be 1.0 float mapCubeSide = 1.0f; - int mapWidth = cubesmap.width * (int)mapCubeSide; - int mapHeight = cubesmap.height * (int)mapCubeSide; + int mapWidth = cubicmap.width * (int)mapCubeSide; + int mapHeight = cubicmap.height * (int)mapCubeSide; // NOTE: Max possible number of triangles numCubes * (12 triangles by cube) - int maxTriangles = cubesmap.width*cubesmap.height*12; + int maxTriangles = cubicmap.width*cubicmap.height*12; int vCounter = 0; // Used to count vertices int tcCounter = 0; // Used to count texcoords @@ -830,12 +737,35 @@ Model LoadCubicmap(Image cubesmap) float w = mapCubeSide; float h = mapCubeSide; - float h2 = mapCubeSide; + float h2 = mapCubeSide * 1.5; // TODO: Review walls height... Vector3 *mapVertices = (Vector3 *)malloc(maxTriangles * 3 * sizeof(Vector3)); Vector2 *mapTexcoords = (Vector2 *)malloc(maxTriangles * 3 * sizeof(Vector2)); Vector3 *mapNormals = (Vector3 *)malloc(maxTriangles * 3 * sizeof(Vector3)); + // Define the 6 normals of the cube, we will combine them accordingly later... + Vector3 n1 = { 1.0f, 0.0f, 0.0f }; + Vector3 n2 = { -1.0f, 0.0f, 0.0f }; + Vector3 n3 = { 0.0f, 1.0f, 0.0f }; + Vector3 n4 = { 0.0f, -1.0f, 0.0f }; + Vector3 n5 = { 0.0f, 0.0f, 1.0f }; + Vector3 n6 = { 0.0f, 0.0f, -1.0f }; + + // NOTE: We use texture rectangles to define different textures for top-bottom-front-back-right-left (6) + typedef struct RectangleF { + float x; + float y; + float width; + float height; + } RectangleF; + + RectangleF rightTexUV = { 0, 0, 0.5, 0.5 }; + RectangleF leftTexUV = { 0.5, 0, 0.5, 0.5 }; + RectangleF frontTexUV = { 0, 0, 0.5, 0.5 }; + RectangleF backTexUV = { 0.5, 0, 0.5, 0.5 }; + RectangleF topTexUV = { 0, 0.5, 0.5, 0.5 }; + RectangleF bottomTexUV = { 0.5, 0.5, 0.5, 0.5 }; + for (int z = 0; z < mapHeight; z += mapCubeSide) { for (int x = 0; x < mapWidth; x += mapCubeSide) @@ -850,25 +780,10 @@ Model LoadCubicmap(Image cubesmap) Vector3 v7 = { x - w/2, 0, z + h/2 }; Vector3 v8 = { x + w/2, 0, z + h/2 }; - // Define the 6 normals of the cube, we will combine them accordingly later... - Vector3 n1 = { 1.0f, 0.0f, 0.0f }; - Vector3 n2 = { -1.0f, 0.0f, 0.0f }; - Vector3 n3 = { 0.0f, 1.0f, 0.0f }; - Vector3 n4 = { 0.0f, -1.0f, 0.0f }; - Vector3 n5 = { 0.0f, 0.0f, 1.0f }; - Vector3 n6 = { 0.0f, 0.0f, -1.0f }; - - // Define the 4 texture coordinates of the cube, we will combine them accordingly later... - // TODO: Use texture rectangles to define different textures for top-bottom-front-back-right-left (6) - Vector2 vt2 = { 0.0f, 0.0f }; - Vector2 vt1 = { 0.0f, 1.0f }; - Vector2 vt4 = { 1.0f, 0.0f }; - Vector2 vt3 = { 1.0f, 1.0f }; - // We check pixel color to be WHITE, we will full cubes - if ((cubesmap.pixels[z*cubesmap.width + x].r == 255) && - (cubesmap.pixels[z*cubesmap.width + x].g == 255) && - (cubesmap.pixels[z*cubesmap.width + x].b == 255)) + if ((cubicmapPixels[z*cubicmap.width + x].r == 255) && + (cubicmapPixels[z*cubicmap.width + x].g == 255) && + (cubicmapPixels[z*cubicmap.width + x].b == 255)) { // Define triangles (Checking Collateral Cubes!) //---------------------------------------------- @@ -890,12 +805,12 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n3; nCounter += 6; - mapTexcoords[tcCounter] = vt2; - mapTexcoords[tcCounter + 1] = vt1; - mapTexcoords[tcCounter + 2] = vt3; - mapTexcoords[tcCounter + 3] = vt2; - mapTexcoords[tcCounter + 4] = vt3; - mapTexcoords[tcCounter + 5] = vt4; + mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; tcCounter += 6; // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) @@ -915,18 +830,18 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n4; nCounter += 6; - mapTexcoords[tcCounter] = vt4; - mapTexcoords[tcCounter + 1] = vt1; - mapTexcoords[tcCounter + 2] = vt3; - mapTexcoords[tcCounter + 3] = vt4; - mapTexcoords[tcCounter + 4] = vt2; - mapTexcoords[tcCounter + 5] = vt1; + mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; tcCounter += 6; - if (((z < cubesmap.height - 1) && - (cubesmap.pixels[(z + 1)*cubesmap.width + x].r == 0) && - (cubesmap.pixels[(z + 1)*cubesmap.width + x].g == 0) && - (cubesmap.pixels[(z + 1)*cubesmap.width + x].b == 0)) || (z == cubesmap.height - 1)) + if (((z < cubicmap.height - 1) && + (cubicmapPixels[(z + 1)*cubicmap.width + x].r == 0) && + (cubicmapPixels[(z + 1)*cubicmap.width + x].g == 0) && + (cubicmapPixels[(z + 1)*cubicmap.width + x].b == 0)) || (z == cubicmap.height - 1)) { // Define front triangles (2 tris, 6 vertex) --> v2 v7 v3, v3 v7 v8 // NOTE: Collateral occluded faces are not generated @@ -946,19 +861,19 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n6; nCounter += 6; - mapTexcoords[tcCounter] = vt2; - mapTexcoords[tcCounter + 1] = vt1; - mapTexcoords[tcCounter + 2] = vt4; - mapTexcoords[tcCounter + 3] = vt4; - mapTexcoords[tcCounter + 4] = vt1; - mapTexcoords[tcCounter + 5] = vt3; + mapTexcoords[tcCounter] = (Vector2){ frontTexUV.x, frontTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y + frontTexUV.height }; tcCounter += 6; } if (((z > 0) && - (cubesmap.pixels[(z - 1)*cubesmap.width + x].r == 0) && - (cubesmap.pixels[(z - 1)*cubesmap.width + x].g == 0) && - (cubesmap.pixels[(z - 1)*cubesmap.width + x].b == 0)) || (z == 0)) + (cubicmapPixels[(z - 1)*cubicmap.width + x].r == 0) && + (cubicmapPixels[(z - 1)*cubicmap.width + x].g == 0) && + (cubicmapPixels[(z - 1)*cubicmap.width + x].b == 0)) || (z == 0)) { // Define back triangles (2 tris, 6 vertex) --> v1 v5 v6, v1 v4 v5 // NOTE: Collateral occluded faces are not generated @@ -978,19 +893,19 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n5; nCounter += 6; - mapTexcoords[tcCounter] = vt4; - mapTexcoords[tcCounter + 1] = vt1; - mapTexcoords[tcCounter + 2] = vt3; - mapTexcoords[tcCounter + 3] = vt4; - mapTexcoords[tcCounter + 4] = vt2; - mapTexcoords[tcCounter + 5] = vt1; + mapTexcoords[tcCounter] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y + backTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ backTexUV.x, backTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; tcCounter += 6; } - if (((x < cubesmap.width - 1) && - (cubesmap.pixels[z*cubesmap.width + (x + 1)].r == 0) && - (cubesmap.pixels[z*cubesmap.width + (x + 1)].g == 0) && - (cubesmap.pixels[z*cubesmap.width + (x + 1)].b == 0)) || (x == cubesmap.width - 1)) + if (((x < cubicmap.width - 1) && + (cubicmapPixels[z*cubicmap.width + (x + 1)].r == 0) && + (cubicmapPixels[z*cubicmap.width + (x + 1)].g == 0) && + (cubicmapPixels[z*cubicmap.width + (x + 1)].b == 0)) || (x == cubicmap.width - 1)) { // Define right triangles (2 tris, 6 vertex) --> v3 v8 v4, v4 v8 v5 // NOTE: Collateral occluded faces are not generated @@ -1010,19 +925,19 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n1; nCounter += 6; - mapTexcoords[tcCounter] = vt2; - mapTexcoords[tcCounter + 1] = vt1; - mapTexcoords[tcCounter + 2] = vt4; - mapTexcoords[tcCounter + 3] = vt4; - mapTexcoords[tcCounter + 4] = vt1; - mapTexcoords[tcCounter + 5] = vt3; + mapTexcoords[tcCounter] = (Vector2){ rightTexUV.x, rightTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y + rightTexUV.height }; tcCounter += 6; } if (((x > 0) && - (cubesmap.pixels[z*cubesmap.width + (x - 1)].r == 0) && - (cubesmap.pixels[z*cubesmap.width + (x - 1)].g == 0) && - (cubesmap.pixels[z*cubesmap.width + (x - 1)].b == 0)) || (x == 0)) + (cubicmapPixels[z*cubicmap.width + (x - 1)].r == 0) && + (cubicmapPixels[z*cubicmap.width + (x - 1)].g == 0) && + (cubicmapPixels[z*cubicmap.width + (x - 1)].b == 0)) || (x == 0)) { // Define left triangles (2 tris, 6 vertex) --> v1 v7 v2, v1 v6 v7 // NOTE: Collateral occluded faces are not generated @@ -1042,25 +957,69 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n2; nCounter += 6; - mapTexcoords[tcCounter] = vt2; - mapTexcoords[tcCounter + 1] = vt3; - mapTexcoords[tcCounter + 2] = vt4; - mapTexcoords[tcCounter + 3] = vt2; - mapTexcoords[tcCounter + 4] = vt1; - mapTexcoords[tcCounter + 5] = vt3; + mapTexcoords[tcCounter] = (Vector2){ leftTexUV.x, leftTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ leftTexUV.x, leftTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ leftTexUV.x, leftTexUV.y + leftTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; tcCounter += 6; } } // We check pixel color to be BLACK, we will only draw floor and roof - else if ((cubesmap.pixels[z*cubesmap.width + x].r == 0) && - (cubesmap.pixels[z*cubesmap.width + x].g == 0) && - (cubesmap.pixels[z*cubesmap.width + x].b == 0)) + else if ((cubicmapPixels[z*cubicmap.width + x].r == 0) && + (cubicmapPixels[z*cubicmap.width + x].g == 0) && + (cubicmapPixels[z*cubicmap.width + x].b == 0)) { - // Define top triangles (2 tris, 6 vertex --> v1-v3-v2, v1-v4-v3) - // TODO: ... + // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v3; + mapVertices[vCounter + 2] = v2; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v4; + mapVertices[vCounter + 5] = v3; + vCounter += 6; - // Define bottom triangles (2 tris, 6 vertex --> v6-v7-v8, v6-v8-v5) - // TODO: ... + mapNormals[nCounter] = n4; + mapNormals[nCounter + 1] = n4; + mapNormals[nCounter + 2] = n4; + mapNormals[nCounter + 3] = n4; + mapNormals[nCounter + 4] = n4; + mapNormals[nCounter + 5] = n4; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + tcCounter += 6; + + // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) + mapVertices[vCounter] = v6; + mapVertices[vCounter + 1] = v7; + mapVertices[vCounter + 2] = v8; + mapVertices[vCounter + 3] = v6; + mapVertices[vCounter + 4] = v8; + mapVertices[vCounter + 5] = v5; + vCounter += 6; + + mapNormals[nCounter] = n3; + mapNormals[nCounter + 1] = n3; + mapNormals[nCounter + 2] = n3; + mapNormals[nCounter + 3] = n3; + mapNormals[nCounter + 4] = n3; + mapNormals[nCounter + 5] = n3; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y }; + tcCounter += 6; } } } @@ -1112,16 +1071,21 @@ Model LoadCubicmap(Image cubesmap) free(mapVertices); free(mapNormals); free(mapTexcoords); + + free(cubicmapPixels); // NOTE: At this point we have all vertex, texcoord, normal data for the model in vData struct Model model = rlglLoadModel(vData); // Now that vertex data is uploaded to GPU, we can free arrays - // NOTE: Despite vertex data is useless on OpenGL 3.3 or ES2, we will keep it... - //free(vData.vertices); - //free(vData.texcoords); - //free(vData.normals); + // NOTE: We don't need CPU vertex data on OpenGL 3.3 or ES2 + if (rlGetVersion() != OPENGL_11) + { + free(vData.vertices); + free(vData.texcoords); + free(vData.normals); + } return model; } @@ -1129,45 +1093,80 @@ Model LoadCubicmap(Image cubesmap) // Unload 3d model from memory void UnloadModel(Model model) { - free(model.mesh.vertices); - free(model.mesh.texcoords); - free(model.mesh.normals); + if (rlGetVersion() == OPENGL_11) + { + free(model.mesh.vertices); + free(model.mesh.texcoords); + free(model.mesh.normals); + } - rlDeleteBuffers(model.vboId[0]); - rlDeleteBuffers(model.vboId[1]); - rlDeleteBuffers(model.vboId[2]); + rlDeleteBuffers(model.mesh.vboId[0]); + rlDeleteBuffers(model.mesh.vboId[1]); + rlDeleteBuffers(model.mesh.vboId[2]); - rlDeleteVertexArrays(model.vaoId); + rlDeleteVertexArrays(model.mesh.vaoId); + //rlDeleteTextures(model.texture.id); + //rlDeleteShader(model.shader.id); } +// Link a texture to a model void SetModelTexture(Model *model, Texture2D texture) { - if (texture.id <= 0) model->textureId = 1; // Default white texture (use mesh color) - else model->textureId = texture.id; + if (texture.id <= 0) + { + model->texture.id = whiteTexture; // Default white texture (use mesh color) + model->shader.texDiffuseId = whiteTexture; + } + else + { + model->texture = texture; + model->shader.texDiffuseId = texture.id; + } +} + +// Load a custom shader (vertex shader + fragment shader) +Shader LoadShader(char *vsFileName, char *fsFileName) +{ + Shader shader = rlglLoadShader(vsFileName, fsFileName); + + return shader; +} + +// Unload a custom shader from memory +void UnloadShader(Shader shader) +{ + rlDeleteShader(shader.id); +} + +// Set shader for a model +void SetModelShader(Model *model, Shader shader) +{ + rlglSetModelShader(model, shader); } // Draw a model (with texture if set) void DrawModel(Model model, Vector3 position, float scale, Color tint) { Vector3 vScale = { scale, scale, scale }; - Vector3 rotation = { 0, 0, 0 }; + Vector3 rotationAxis = { 0, 0, 0 }; - rlglDrawModel(model, position, rotation, vScale, tint, false); + DrawModelEx(model, position, 0.0f, rotationAxis, vScale, tint); } // Draw a model with extended parameters -void DrawModelEx(Model model, Vector3 position, Vector3 rotation, Vector3 scale, Color tint) +void DrawModelEx(Model model, Vector3 position, float rotationAngle, Vector3 rotationAxis, Vector3 scale, Color tint) { - rlglDrawModel(model, position, rotation, scale, tint, false); + // NOTE: Rotation must be provided in degrees, it's converted to radians inside rlglDrawModel() + rlglDrawModel(model, position, rotationAngle, rotationAxis, scale, tint, false); } // Draw a model wires (with texture if set) void DrawModelWires(Model model, Vector3 position, float scale, Color color) { Vector3 vScale = { scale, scale, scale }; - Vector3 rotation = { 0, 0, 0 }; + Vector3 rotationAxis = { 0, 0, 0 }; - rlglDrawModel(model, position, rotation, vScale, color, true); + rlglDrawModel(model, position, 0.0f, rotationAxis, vScale, color, true); } // Draw a billboard @@ -1180,7 +1179,10 @@ void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, MatrixTranspose(&viewMatrix); Vector3 right = { viewMatrix.m0, viewMatrix.m4, viewMatrix.m8 }; - Vector3 up = { viewMatrix.m1, viewMatrix.m5, viewMatrix.m9 }; + //Vector3 up = { viewMatrix.m1, viewMatrix.m5, viewMatrix.m9 }; + + // NOTE: Billboard locked to axis-Y + Vector3 up = { 0, 1, 0 }; /* a-------b | | @@ -1250,11 +1252,11 @@ void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vec // Bottom-left corner for texture and quad rlTexCoord2f((float)sourceRec.x / texture.width, (float)sourceRec.y / texture.height); rlVertex3f(a.x, a.y, a.z); - + // Top-left corner for texture and quad rlTexCoord2f((float)sourceRec.x / texture.width, (float)(sourceRec.y + sourceRec.height) / texture.height); rlVertex3f(d.x, d.y, d.z); - + // Top-right corner for texture and quad rlTexCoord2f((float)(sourceRec.x + sourceRec.width) / texture.width, (float)(sourceRec.y + sourceRec.height) / texture.height); rlVertex3f(c.x, c.y, c.z); @@ -1267,6 +1269,338 @@ void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vec rlDisableTexture(); } + +bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB) +{ + bool collision = false; + + float dx = centerA.x - centerB.x; // X distance between centers + float dy = centerA.y - centerB.y; // Y distance between centers + float dz = centerA.z - centerB.z; // Y distance between centers + + float distance = sqrt(dx*dx + dy*dy + dz*dz); // Distance between centers + + if (distance <= (radiusA + radiusB)) collision = true; + + return collision; +} + +bool CheckCollisionBoxes(Vector3 minBBox1, Vector3 maxBBox1, Vector3 minBBox2, Vector3 maxBBox2) +{ + /* + // Get min and max vertex to construct bounds (AABB) + Vector3 minVertex = tempVertices[0]; + Vector3 maxVertex = tempVertices[0]; + + for (int i = 1; i < tempVertices.Count; i++) + { + minVertex = Vector3.Min(minVertex, tempVertices[i]); + maxVertex = Vector3.Max(maxVertex, tempVertices[i]); + } + + bounds = new BoundingBox(minVertex, maxVertex); + */ + + bool collision = true; + + if ((maxBBox1.x >= minBBox2.x) && (minBBox1.x <= maxBBox2.x)) + { + if ((maxBBox1.y < minBBox2.y) || (minBBox1.y > maxBBox2.y)) collision = false; + if ((maxBBox1.z < minBBox2.z) || (minBBox1.z > maxBBox2.z)) collision = false; + } + else collision = false; + + return collision; +} + +bool CheckCollisionBoxSphere(Vector3 minBBox, Vector3 maxBBox, Vector3 centerSphere, float radiusSphere) +{ + bool collision = false; + + if ((centerSphere.x - minBBox.x > radiusSphere) && (centerSphere.y - minBBox.y > radiusSphere) && (centerSphere.z - minBBox.z > radiusSphere) && + (maxBBox.x - centerSphere.x > radiusSphere) && (maxBBox.y - centerSphere.y > radiusSphere) && (maxBBox.z - centerSphere.z > radiusSphere)) + { + collision = true; + } + else + { + float dmin = 0; + + if (centerSphere.x - minBBox.x <= radiusSphere) + dmin += (centerSphere.x - minBBox.x) * (centerSphere.x - minBBox.x); + else if (maxBBox.x - centerSphere.x <= radiusSphere) + dmin += (centerSphere.x - maxBBox.x) * (centerSphere.x - maxBBox.x); + + if (centerSphere.y - minBBox.y <= radiusSphere) + dmin += (centerSphere.y - minBBox.y) * (centerSphere.y - minBBox.y); + else if (maxBBox.y - centerSphere.y <= radiusSphere) + dmin += (centerSphere.y - maxBBox.y) * (centerSphere.y - maxBBox.y); + + if (centerSphere.z - minBBox.z <= radiusSphere) + dmin += (centerSphere.z - minBBox.z) * (centerSphere.z - minBBox.z); + else if (maxBBox.z - centerSphere.z <= radiusSphere) + dmin += (centerSphere.z - maxBBox.z) * (centerSphere.z - maxBBox.z); + + if (dmin <= radiusSphere * radiusSphere) collision = true; + } + + return collision; +} + +// TODO +//BoundingBox GetCollisionArea(BoundingBox box1, BoundingBox box2) + +// Detect and resolve cubicmap collisions +// NOTE: player position (or camera) is modified inside this function +Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *playerPosition, float radius) +{ + Color *cubicmapPixels = GetPixelData(cubicmap); + + // Detect the cell where the player is located + Vector3 impactDirection = { 0, 0, 0 }; + + int locationCellX = 0; + int locationCellY = 0; + + locationCellX = floor(playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE); + locationCellY = floor(playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE); + + if (locationCellX >= 0 && locationCellY >= 0 && locationCellX < cubicmap.width && locationCellY < cubicmap.height) + { + // Multiple Axis -------------------------------------------------------------------------------------------- + + // Axis x-, y- + if (locationCellX > 0 && locationCellY > 0) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) + { + playerPosition->x = locationCellX + mapPosition.x - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + playerPosition->z = locationCellY + mapPosition.z - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + + // Axis x-, y+ + if (locationCellX > 0 && locationCellY < cubicmap.height - 1) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) + { + playerPosition->x = locationCellX + mapPosition.x - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + playerPosition->z = locationCellY + mapPosition.z + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + + // Axis x+, y- + if (locationCellX < cubicmap.width - 1 && locationCellY > 0) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) + { + playerPosition->x = locationCellX + mapPosition.x + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + playerPosition->z = locationCellY + mapPosition.z - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + + // Axis x+, y+ + if (locationCellX < cubicmap.width - 1 && locationCellY < cubicmap.height - 1) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) + { + playerPosition->x = locationCellX + mapPosition.x + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + playerPosition->z = locationCellY + mapPosition.z + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + + // Single Axis --------------------------------------------------------------------------------------------------- + + // Axis x- + if (locationCellX > 0) + { + if (cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) + { + if ((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) + { + playerPosition->x = locationCellX + mapPosition.x - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 0}; + } + } + } + // Axis x+ + if (locationCellX < cubicmap.width - 1) + { + if (cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) + { + if ((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) + { + playerPosition->x = locationCellX + mapPosition.x + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 0}; + } + } + } + // Axis y- + if (locationCellY > 0) + { + if (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0) + { + if ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius) + { + playerPosition->z = locationCellY + mapPosition.z - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 0, 0, 1}; + } + } + } + // Axis y+ + if (locationCellY < cubicmap.height - 1) + { + if (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0) + { + if ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius) + { + playerPosition->z = locationCellY + mapPosition.z + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 0, 0, 1}; + } + } + } + + // Diagonals ------------------------------------------------------------------------------------------------------- + + // Axis x-, y- + if (locationCellX > 0 && locationCellY > 0) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r == 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r == 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX - 1)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX) > ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY)) playerPosition->x = locationCellX + mapPosition.x - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + else playerPosition->z = locationCellY + mapPosition.z - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + + // Return ricochet + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius / 3) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius / 3)) + { + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + } + + // Axis x-, y+ + if (locationCellX > 0 && locationCellY < cubicmap.height - 1) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r == 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r == 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX - 1)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX) > (1 - ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY))) playerPosition->x = locationCellX + mapPosition.x - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + else playerPosition->z = locationCellY + mapPosition.z + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + + // Return ricochet + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius / 3) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius / 3)) + { + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + } + + // Axis x+, y- + if (locationCellX < cubicmap.width - 1 && locationCellY > 0) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r == 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r == 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX + 1)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX) < (1 - ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY))) playerPosition->x = locationCellX + mapPosition.x + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + else playerPosition->z = locationCellY + mapPosition.z - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + + // Return ricochet + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius / 3) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius / 3)) + { + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + } + + // Axis x+, y+ + if (locationCellX < cubicmap.width - 1 && locationCellY < cubicmap.height - 1) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r == 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r == 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX + 1)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX) < ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY)) playerPosition->x = locationCellX + mapPosition.x + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + else playerPosition->z = locationCellY + mapPosition.z + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + + // Return ricochet + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius / 3) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius / 3)) + { + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + } + } + + // Floor collision + if (playerPosition->y <= radius) + { + playerPosition->y = radius + 0.01; + impactDirection = (Vector3) { impactDirection.x, 1, impactDirection.z}; + } + // Roof collision + else if (playerPosition->y >= 1.5 - radius) + { + playerPosition->y = (1.5 - radius) - 0.01; + impactDirection = (Vector3) { impactDirection.x, 1, impactDirection.z}; + } + + free(cubicmapPixels); + + return impactDirection; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + // Get current vertex y altitude (proportional to pixel colors in grayscale) static float GetHeightValue(Color pixel) { @@ -1289,7 +1623,7 @@ static VertexData LoadOBJ(const char *fileName) FILE *objFile; objFile = fopen(fileName, "rt"); - + if (objFile == NULL) { TraceLog(WARNING, "[%s] OBJ file could not be opened", fileName); @@ -1524,35 +1858,3 @@ static VertexData LoadOBJ(const char *fileName) return vData; } - -bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB) -{ - - return false; -} - -bool CheckCollisionBoxes(Vector3 minBBox1, Vector3 maxBBox1, Vector3 minBBox2, Vector3 maxBBox2) -{ - /* - // Get min and max vertex to construct bounds (AABB) - Vector3 minVertex = tempVertices[0]; - Vector3 maxVertex = tempVertices[0]; - - for (int i = 1; i < tempVertices.Count; i++) - { - minVertex = Vector3.Min(minVertex, tempVertices[i]); - maxVertex = Vector3.Max(maxVertex, tempVertices[i]); - } - - bounds = new BoundingBox(minVertex, maxVertex); - */ - return false; -} - -bool CheckCollisionBoxSphere(Vector3 minBBox, Vector3 maxBBox, Vector3 centerSphere, Vector3 radiusSphere) -{ - - return false; -} - -//BoundingBox GetCollisionArea(BoundingBox box1, BoundingBox box2) \ No newline at end of file diff --git a/src/raylib.h b/src/raylib.h index 69966069f..6800b2602 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1,6 +1,6 @@ /********************************************************************************************** * -* raylib 1.2 (www.raylib.com) +* raylib 1.3.0 (www.raylib.com) * * A simple and easy-to-use library to learn videogames programming * @@ -27,7 +27,6 @@ * * Some design decisions: * 32bit Colors - All defined color are always RGBA -* 32bit Textures - All loaded images are converted automatically to RGBA textures * SpriteFonts - All loaded sprite-font images are converted to RGBA and POT textures * One custom default font is loaded automatically when InitWindow() * If using OpenGL 3.3+ or ES2, one default shader is loaded automatically (internally defined) @@ -78,7 +77,7 @@ // Some basic Defines //---------------------------------------------------------------------------------- #ifndef PI -#define PI 3.14159265358979323846 + #define PI 3.14159265358979323846 #endif #define DEG2RAD (PI / 180.0f) @@ -90,8 +89,9 @@ #define FLAG_SHOW_MOUSE_CURSOR 4 #define FLAG_CENTERED_MODE 8 #define FLAG_MSAA_4X_HINT 16 +#define FLAG_VSYNC_HINT 32 -// Keyboard Function Keys +// Keyboard Function Keys #define KEY_SPACE 32 #define KEY_ESCAPE 256 #define KEY_ENTER 257 @@ -177,8 +177,10 @@ // Types and Structures Definition //---------------------------------------------------------------------------------- +#ifndef __cplusplus // Boolean type typedef enum { false, true } bool; +#endif // byte type typedef unsigned char byte; @@ -196,6 +198,14 @@ typedef struct Vector3 { float z; } Vector3; +// Matrix type (OpenGL style 4x4 - right handed, column major) +typedef struct Matrix { + float m0, m4, m8, m12; + float m1, m5, m9, m13; + float m2, m6, m10, m14; + float m3, m7, m11, m15; +} Matrix; + // Color type, RGBA (32bit) typedef struct Color { unsigned char r; @@ -215,17 +225,21 @@ typedef struct Rectangle { // Image type, bpp always RGBA (32bit) // NOTE: Data stored in CPU memory (RAM) typedef struct Image { - Color *pixels; - int width; - int height; + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (TextureFormat) } Image; // Texture2D type, bpp always RGBA (32bit) // NOTE: Data stored in GPU memory typedef struct Texture2D { - unsigned int id; // OpenGL id - int width; - int height; + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (TextureFormat) } Texture2D; // Character type (one font glyph) @@ -251,25 +265,60 @@ typedef struct Camera { Vector3 up; } Camera; +// Camera modes +typedef enum { CAMERA_CUSTOM = 0, CAMERA_FREE, CAMERA_ORBITAL, CAMERA_FIRST_PERSON, CAMERA_THIRD_PERSON } CameraMode; + // Vertex data definning a mesh +// NOTE: If using OpenGL 1.1, data loaded in CPU; if OpenGL 3.3+ data loaded in GPU (vaoId) typedef struct VertexData { int vertexCount; float *vertices; // 3 components per vertex float *texcoords; // 2 components per vertex float *normals; // 3 components per vertex unsigned char *colors; // 4 components per vertex -} VertexData; - -// 3d Model type -// NOTE: If using OpenGL 1.1, loaded in CPU (mesh); if OpenGL 3.3+ loaded in GPU (vaoId) -typedef struct Model { - VertexData mesh; unsigned int vaoId; unsigned int vboId[4]; - unsigned int textureId; - //Matrix transform; +} VertexData; + +// Shader type +typedef struct Shader { + unsigned int id; // Shader program id + + unsigned int texDiffuseId; // Diffuse texture id + unsigned int texNormalId; // Normal texture id + unsigned int texSpecularId; // Specular texture id + + // Variable attributes + int vertexLoc; // Vertex attribute location point (vertex shader) + int texcoordLoc; // Texcoord attribute location point (vertex shader) + int normalLoc; // Normal attribute location point (vertex shader) + int colorLoc; // Color attibute location point (vertex shader) + + // Uniforms + int projectionLoc; // Projection matrix uniform location point (vertex shader) + int modelviewLoc; // ModeView matrix uniform location point (vertex shader) + + int tintColorLoc; // Color uniform location point (fragment shader) + + int mapDiffuseLoc; // Diffuse map texture uniform location point (fragment shader) + int mapNormalLoc; // Normal map texture uniform location point (fragment shader) + int mapSpecularLoc; // Specular map texture uniform location point (fragment shader) +} Shader; + +// 3d Model type +typedef struct Model { + VertexData mesh; + Matrix transform; + Texture2D texture; // Only for OpenGL 1.1, on newer versions this should be in the shader + Shader shader; } Model; +// Ray type (useful for raycast) +typedef struct Ray { + Vector3 position; + Vector3 direction; +} Ray; + // Sound source type typedef struct Sound { unsigned int source; @@ -285,6 +334,44 @@ typedef struct Wave { short channels; } Wave; +// Texture formats +// NOTE: Support depends on OpenGL version and platform +typedef enum { + UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + UNCOMPRESSED_GRAY_ALPHA, // 16 bpp (2 channels) + UNCOMPRESSED_R5G6B5, // 16 bpp + UNCOMPRESSED_R8G8B8, // 24 bpp + UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + UNCOMPRESSED_R8G8B8A8, // 32 bpp + COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + COMPRESSED_DXT3_RGBA, // 8 bpp + COMPRESSED_DXT5_RGBA, // 8 bpp + COMPRESSED_ETC1_RGB, // 4 bpp + COMPRESSED_ETC2_RGB, // 4 bpp + COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + COMPRESSED_PVRT_RGB, // 4 bpp + COMPRESSED_PVRT_RGBA, // 4 bpp + COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} TextureFormat; + +// Gestures type +typedef enum { + GESTURE_NONE = 0, + GESTURE_TAP, + GESTURE_DOUBLETAP, + GESTURE_HOLD, + GESTURE_DRAG, + GESTURE_SWIPE_RIGHT, + GESTURE_SWIPE_LEFT, + GESTURE_SWIPE_UP, + GESTURE_SWIPE_DOWN, + GESTURE_PINCH_IN, + GESTURE_PINCH_OUT +} Gestures; + #ifdef __cplusplus extern "C" { // Prevents name mangling of functions #endif @@ -312,7 +399,6 @@ void SetExitKey(int key); // Set a custom key #endif int GetScreenWidth(void); // Get current screen width int GetScreenHeight(void); // Get current screen height -int GetKeyPressed(void); // Get latest key pressed void ClearBackground(Color color); // Sets Background Color void BeginDrawing(void); // Setup drawing canvas to start drawing @@ -331,9 +417,35 @@ int GetHexValue(Color color); // Returns hexadecim int GetRandomValue(int min, int max); // Returns a random value between min and max (both included) Color Fade(Color color, float alpha); // Color fade-in or fade-out, alpha goes from 0.0f to 1.0f -void SetupFlags(char flags); // Enable some window configurations +void SetConfigFlags(char flags); // Enable some window configurations void ShowLogo(void); // Activates raylib logo at startup (can be done with flags) +void SetPostproShader(Shader shader); // Set fullscreen postproduction shader +void SetCustomShader(Shader shader); // Set custom shader to be used in batch draw +void SetDefaultShader(void); // Set default shader to be used in batch draw + +Ray GetMouseRay(Vector2 mousePosition, Camera camera); // Gives the rayTrace from mouse position + +// Camera modes setup and control functions (module: camera) +void SetCameraMode(int mode); // Select camera mode (multiple camera modes available) +Camera UpdateCamera(Vector3 *position); // Update camera with position + +void SetCameraControls(int front, int left, int back, int right, int up, int down); +void SetMouseSensitivity(float sensitivity); +void SetResetPosition(Vector3 resetPosition); +void SetResetControl(int resetKey); +void SetPawnControl(int pawnControlKey); +void SetFnControl(int fnControlKey); +void SetSmoothZoomControl(int smoothZoomControlKey); +void SetOrbitalTarget(Vector3 target); + +int GetShaderLocation(Shader shader, const char *uniformName); +void SetShaderValue(Shader shader, int uniformLoc, float *value, int size); + +void SetShaderMapDiffuse(Shader *shader, Texture2D texture); +void SetShaderMapNormal(Shader *shader, const char *uniformName, Texture2D texture); +void SetShaderMapSpecular(Shader *shader, const char *uniformName, Texture2D texture); + //------------------------------------------------------------------------------------ // Input Handling Functions (Module: core) //------------------------------------------------------------------------------------ @@ -342,6 +454,7 @@ bool IsKeyPressed(int key); // Detect if a key has b bool IsKeyDown(int key); // Detect if a key is being pressed bool IsKeyReleased(int key); // Detect if a key has been released once bool IsKeyUp(int key); // Detect if a key is NOT being pressed +int GetKeyPressed(void); // Get latest key pressed bool IsMouseButtonPressed(int button); // Detect if a mouse button has been pressed once bool IsMouseButtonDown(int button); // Detect if a mouse button is being pressed @@ -352,9 +465,13 @@ int GetMouseY(void); // Returns mouse positio Vector2 GetMousePosition(void); // Returns mouse position XY void SetMousePosition(Vector2 position); // Set mouse position XY int GetMouseWheelMove(void); // Returns mouse wheel movement Y + +void ShowCursor(void); // Shows cursor +void HideCursor(void); // Hides cursor +bool IsCursorHidden(void); // Returns true if cursor is not visible #endif -#if defined(PLATFORM_DESKTOP) +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) bool IsGamepadAvailable(int gamepad); // Detect if a gamepad is available Vector2 GetGamepadMovement(int gamepad); // Return axis movement vector for a gamepad bool IsGamepadButtonPressed(int gamepad, int button); // Detect if a gamepad button has been pressed once @@ -363,11 +480,20 @@ bool IsGamepadButtonReleased(int gamepad, int button); // Detect if a gamepad b bool IsGamepadButtonUp(int gamepad, int button); // Detect if a gamepad button is NOT being pressed #endif -#if defined(PLATFORM_ANDROID) -bool IsScreenTouched(void); // Detect screen touch event +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) int GetTouchX(void); // Returns touch position X int GetTouchY(void); // Returns touch position Y Vector2 GetTouchPosition(void); // Returns touch position XY + +// Gestures System (module: gestures) +bool IsGestureDetected(void); +int GetGestureType(void); +float GetDragIntensity(void); +float GetDragAngle(void); +Vector2 GetDragVector(void); +int GetHoldDuration(void); // Hold time in frames +float GetPinchDelta(void); +float GetPinchAngle(void); #endif //------------------------------------------------------------------------------------ @@ -405,13 +531,15 @@ bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 //------------------------------------------------------------------------------------ Image LoadImage(const char *fileName); // Load an image into CPU memory (RAM) Image LoadImageFromRES(const char *rresName, int resId); // Load an image from rRES file (raylib Resource) +Image LoadImageFromData(Color *pixels, int width, int height, int format); // Load image from Color array data Texture2D LoadTexture(const char *fileName); // Load an image as texture into GPU memory +Texture2D LoadTextureEx(void *data, int width, int height, int textureFormat, int mipmapCount, bool genMipmaps); // Load a texture from raw data into GPU memory Texture2D LoadTextureFromRES(const char *rresName, int resId); // Load an image as texture from rRES file (raylib Resource) Texture2D LoadTextureFromImage(Image image, bool genMipmaps); // Load a texture from image data (and generate mipmaps) -Texture2D CreateTexture(Image image, bool genMipmaps); // [DEPRECATED] Same as LoadTextureFromImage() void UnloadImage(Image image); // Unload image from CPU memory (RAM) void UnloadTexture(Texture2D texture); // Unload texture from GPU memory void ConvertToPOT(Image *image, Color fillColor); // Convert image to POT (power-of-two) +Color *GetPixelData(Image image); // Get pixel data from image as a Color struct array void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 @@ -448,31 +576,40 @@ void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires -void DrawQuad(Vector3 vertices[4], Vector2 textcoords[4], Vector3 normals[4], Color colors[4]); // Draw a quad -void DrawPlane(Vector3 centerPos, Vector2 size, Vector3 rotation, Color color); // Draw a plane -void DrawPlaneEx(Vector3 centerPos, Vector2 size, Vector3 rotation, int slicesX, int slicesZ, Color color); // Draw a plane with divisions +void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ +void DrawQuad(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, Color color); // Draw a quad +void DrawRay(Ray ray, Color color); // Draw a ray line void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) void DrawGizmo(Vector3 position); // Draw simple gizmo -void DrawGizmoEx(Vector3 position, Vector3 rotation, float scale); // Draw gizmo with extended parameters //DrawTorus(), DrawTeapot() are useless... //------------------------------------------------------------------------------------ // Model 3d Loading and Drawing Functions (Module: models) //------------------------------------------------------------------------------------ Model LoadModel(const char *fileName); // Load a 3d model (.OBJ) +Model LoadModelEx(VertexData data); // Load a 3d model (from vertex data) //Model LoadModelFromRES(const char *rresName, int resId); // TODO: Load a 3d model from rRES file (raylib Resource) Model LoadHeightmap(Image heightmap, float maxHeight); // Load a heightmap image as a 3d model Model LoadCubicmap(Image cubicmap); // Load a map image as a 3d model (cubes based) void UnloadModel(Model model); // Unload 3d model from memory void SetModelTexture(Model *model, Texture2D texture); // Link a texture to a model +void SetModelShader(Model *model, Shader shader); // Link a shader to a model (not available on OpenGL 1.1) void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) -void DrawModelEx(Model model, Vector3 position, Vector3 rotation, Vector3 scale, Color tint); // Draw a model with extended parameters +void DrawModelEx(Model model, Vector3 position, float rotationAngle, Vector3 rotationAxis, Vector3 scale, Color tint); // Draw a model with extended parameters void DrawModelWires(Model model, Vector3 position, float scale, Color color); // Draw a model wires (with texture if set) void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, Color tint); // Draw a billboard texture void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vector3 center, float size, Color tint); // Draw a billboard texture defined by sourceRec +Shader LoadShader(char *vsFileName, char *fsFileName); // Load a custom shader (vertex shader + fragment shader) +void UnloadShader(Shader shader); // Unload a custom shader from memory + +bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB); +bool CheckCollisionBoxes(Vector3 minBBox1, Vector3 maxBBox1, Vector3 minBBox2, Vector3 maxBBox2); +bool CheckCollisionBoxSphere(Vector3 minBBox, Vector3 maxBBox, Vector3 centerSphere, float radiusSphere); +Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *playerPosition, float radius); // Return the normal vector of the impacted surface + //------------------------------------------------------------------------------------ // Audio Loading and Playing Functions (Module: audio) //------------------------------------------------------------------------------------ diff --git a/src/raymath.c b/src/raymath.c index ed45ee920..f5e30833c 100644 --- a/src/raymath.c +++ b/src/raymath.c @@ -346,8 +346,6 @@ void MatrixInvert(Matrix *mat) temp.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; temp.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; - PrintMatrix(temp); - *mat = temp; } @@ -433,72 +431,78 @@ Matrix MatrixSubstract(Matrix left, Matrix right) } // Returns translation matrix -// TODO: Review this function Matrix MatrixTranslate(float x, float y, float z) { -/* - For OpenGL - 1, 0, 0, 0 - 0, 1, 0, 0 - 0, 0, 1, 0 - x, y, z, 1 - Is the correct Translation Matrix. Why? Opengl Uses column-major matrix ordering. - Which is the Transpose of the Matrix you initially presented, which is in row-major ordering. - Row major is used in most math text-books and also DirectX, so it is a common - point of confusion for those new to OpenGL. - - * matrix notation used in opengl documentation does not describe in-memory layout for OpenGL matrices - - Translation matrix should be laid out in memory like this: - { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, trabsX, transY, transZ, 1 } - - - 9.005 Are OpenGL matrices column-major or row-major? - - For programming purposes, OpenGL matrices are 16-value arrays with base vectors laid out - contiguously in memory. The translation components occupy the 13th, 14th, and 15th elements - of the 16-element matrix, where indices are numbered from 1 to 16 as described in section - 2.11.2 of the OpenGL 2.1 Specification. - - Column-major versus row-major is purely a notational convention. Note that post-multiplying - with column-major matrices produces the same result as pre-multiplying with row-major matrices. - The OpenGL Specification and the OpenGL Reference Manual both use column-major notation. - You can use any notation, as long as it's clearly stated. - - Sadly, the use of column-major format in the spec and blue book has resulted in endless confusion - in the OpenGL programming community. Column-major notation suggests that matrices - are not laid out in memory as a programmer would expect. -*/ - Matrix result = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1 }; return result; } -// Returns rotation matrix -// TODO: Review this function -Matrix MatrixRotate(float angleX, float angleY, float angleZ) +// Create rotation matrix from axis and angle +// NOTE: Angle should be provided in radians +Matrix MatrixRotate(float angle, Vector3 axis) { Matrix result; - Matrix rotX = MatrixRotateX(angleX); - Matrix rotY = MatrixRotateY(angleY); - Matrix rotZ = MatrixRotateZ(angleZ); + Matrix mat = MatrixIdentity(); - result = MatrixMultiply(MatrixMultiply(rotX, rotY), rotZ); + float x = axis.x, y = axis.y, z = axis.z; + + float length = sqrt(x*x + y*y + z*z); + + if ((length != 1) && (length != 0)) + { + length = 1/length; + x *= length; + y *= length; + z *= length; + } + + float s = sinf(angle); + float c = cosf(angle); + float t = 1.0f - c; + + // Cache some matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + + // Construct the elements of the rotation matrix + float b00 = x*x*t + c, b01 = y*x*t + z*s, b02 = z*x*t - y*s; + float b10 = x*y*t - z*s, b11 = y*y*t + c, b12 = z*y*t + x*s; + float b20 = x*z*t + y*s, b21 = y*z*t - x*s, b22 = z*z*t + c; + + // Perform rotation-specific matrix multiplication + result.m0 = a00*b00 + a10*b01 + a20*b02; + result.m1 = a01*b00 + a11*b01 + a21*b02; + result.m2 = a02*b00 + a12*b01 + a22*b02; + result.m3 = a03*b00 + a13*b01 + a23*b02; + result.m4 = a00*b10 + a10*b11 + a20*b12; + result.m5 = a01*b10 + a11*b11 + a21*b12; + result.m6 = a02*b10 + a12*b11 + a22*b12; + result.m7 = a03*b10 + a13*b11 + a23*b12; + result.m8 = a00*b20 + a10*b21 + a20*b22; + result.m9 = a01*b20 + a11*b21 + a21*b22; + result.m10 = a02*b20 + a12*b21 + a22*b22; + result.m11 = a03*b20 + a13*b21 + a23*b22; + result.m12 = mat.m12; + result.m13 = mat.m13; + result.m14 = mat.m14; + result.m15 = mat.m15; return result; } /* +// Another implementation for MatrixRotate... Matrix MatrixRotate(float angle, float x, float y, float z) { Matrix result = MatrixIdentity(); - float c = cosf(angle*DEG2RAD); // cosine - float s = sinf(angle*DEG2RAD); // sine - float c1 = 1.0f - c; // 1 - c - + float c = cosf(angle); // cosine + float s = sinf(angle); // sine + float c1 = 1.0f - c; // 1 - c + float m0 = result.m0, m4 = result.m4, m8 = result.m8, m12 = result.m12, m1 = result.m1, m5 = result.m5, m9 = result.m9, m13 = result.m13, m2 = result.m2, m6 = result.m6, m10 = result.m10, m14 = result.m14; @@ -532,124 +536,6 @@ Matrix MatrixRotate(float angle, float x, float y, float z) } */ -// Create rotation matrix from axis and angle -// TODO: Test this function -// NOTE: NO prototype defined! -Matrix MatrixFromAxisAngle(Vector3 axis, float angle) -{ - Matrix result; - - Matrix mat = MatrixIdentity(); - - float x = axis.x, y = axis.y, z = axis.z; - - float length = sqrt(x*x + y*y + z*z); - - if ((length != 1) && (length != 0)) - { - length = 1 / length; - x *= length; - y *= length; - z *= length; - } - - float s = sin(angle); - float c = cos(angle); - float t = 1-c; - - // Cache some matrix values (speed optimization) - float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; - float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; - float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; - - // Construct the elements of the rotation matrix - float b00 = x*x*t + c, b01 = y*x*t + z*s, b02 = z*x*t - y*s; - float b10 = x*y*t - z*s, b11 = y*y*t + c, b12 = z*y*t + x*s; - float b20 = x*z*t + y*s, b21 = y*z*t - x*s, b22 = z*z*t + c; - - // Perform rotation-specific matrix multiplication - result.m0 = a00*b00 + a10*b01 + a20*b02; - result.m1 = a01*b00 + a11*b01 + a21*b02; - result.m2 = a02*b00 + a12*b01 + a22*b02; - result.m3 = a03*b00 + a13*b01 + a23*b02; - result.m4 = a00*b10 + a10*b11 + a20*b12; - result.m5 = a01*b10 + a11*b11 + a21*b12; - result.m6 = a02*b10 + a12*b11 + a22*b12; - result.m7 = a03*b10 + a13*b11 + a23*b12; - result.m8 = a00*b20 + a10*b21 + a20*b22; - result.m9 = a01*b20 + a11*b21 + a21*b22; - result.m10 = a02*b20 + a12*b21 + a22*b22; - result.m11 = a03*b20 + a13*b21 + a23*b22; - result.m12 = mat.m12; - result.m13 = mat.m13; - result.m14 = mat.m14; - result.m15 = mat.m15; - - return result; -}; - -// Create rotation matrix from axis and angle (version 2) -// TODO: Test this function -// NOTE: NO prototype defined! -Matrix MatrixFromAxisAngle2(Vector3 axis, float angle) -{ - Matrix result; - - VectorNormalize(&axis); - float axisX = axis.x, axisY = axis.y, axisZ = axis.y; - - // Calculate angles - float cosres = (float)cos(angle); - float sinres = (float)sin(angle); - float t = 1.0f - cosres; - - // Do the conversion math once - float tXX = t * axisX * axisX; - float tXY = t * axisX * axisY; - float tXZ = t * axisX * axisZ; - float tYY = t * axisY * axisY; - float tYZ = t * axisY * axisZ; - float tZZ = t * axisZ * axisZ; - - float sinX = sinres * axisX; - float sinY = sinres * axisY; - float sinZ = sinres * axisZ; - - result.m0 = tXX + cosres; - result.m1 = tXY + sinZ; - result.m2 = tXZ - sinY; - result.m3 = 0; - result.m4 = tXY - sinZ; - result.m5 = tYY + cosres; - result.m6 = tYZ + sinX; - result.m7 = 0; - result.m8 = tXZ + sinY; - result.m9 = tYZ - sinX; - result.m10 = tZZ + cosres; - result.m11 = 0; - result.m12 = 0; - result.m13 = 0; - result.m14 = 0; - result.m15 = 1; - - return result; -} - -// Returns rotation matrix for a given quaternion -Matrix MatrixFromQuaternion(Quaternion q) -{ - Matrix result = MatrixIdentity(); - - Vector3 axis; - float angle; - - QuaternionToAxisAngle(q, &axis, &angle); - - result = MatrixFromAxisAngle2(axis, angle); - - return result; -} - // Returns x-rotation matrix (angle in radians) Matrix MatrixRotateX(float angle) { @@ -671,8 +557,8 @@ Matrix MatrixRotateY(float angle) { Matrix result = MatrixIdentity(); - float cosres = (float)cos(angle); - float sinres = (float)sin(angle); + float cosres = cosf(angle); + float sinres = sinf(angle); result.m0 = cosres; result.m2 = sinres; @@ -706,22 +592,6 @@ Matrix MatrixScale(float x, float y, float z) return result; } -// Returns transformation matrix for a given translation, rotation and scale -// NOTE: Transformation order is rotation -> scale -> translation -// NOTE: Rotation angles should come in radians -Matrix MatrixTransform(Vector3 translation, Vector3 rotation, Vector3 scale) -{ - Matrix result = MatrixIdentity(); - - Matrix mRotation = MatrixRotate(rotation.x, rotation.y, rotation.z); - Matrix mScale = MatrixScale(scale.x, scale.y, scale.z); - Matrix mTranslate = MatrixTranslate(translation.x, translation.y, translation.z); - - result = MatrixMultiply(MatrixMultiply(mRotation, mScale), mTranslate); - - return result; -} - // Returns two matrix multiplication // NOTE: When multiplying matrices... the order matters! Matrix MatrixMultiply(Matrix left, Matrix right) @@ -876,7 +746,7 @@ void PrintMatrix(Matrix m) // Module Functions Definition - Quaternion math //---------------------------------------------------------------------------------- -// Calculates the length of a quaternion +// Computes the length of a quaternion float QuaternionLength(Quaternion quat) { return sqrt(quat.x*quat.x + quat.y*quat.y + quat.z*quat.z + quat.w*quat.w); @@ -922,13 +792,13 @@ Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; - if (abs(cosHalfTheta) >= 1.0f) result = q1; + if (fabs(cosHalfTheta) >= 1.0f) result = q1; else { float halfTheta = acos(cosHalfTheta); float sinHalfTheta = sqrt(1.0f - cosHalfTheta*cosHalfTheta); - if (abs(sinHalfTheta) < 0.001f) + if (fabs(sinHalfTheta) < 0.001f) { result.x = (q1.x*0.5f + q2.x*0.5f); result.y = (q1.y*0.5f + q2.y*0.5f); @@ -950,7 +820,7 @@ Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) return result; } -// Returns a quaternion from a given rotation matrix +// Returns a quaternion for a given rotation matrix Quaternion QuaternionFromMatrix(Matrix matrix) { Quaternion result; @@ -1006,29 +876,7 @@ Quaternion QuaternionFromMatrix(Matrix matrix) return result; } -// Returns rotation quaternion for an angle around an axis -// NOTE: angle must be provided in radians -Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) -{ - Quaternion result = { 0, 0, 0, 1 }; - - if (VectorLength(axis) != 0.0) - - angle *= 0.5; - - VectorNormalize(&axis); - - result.x = axis.x * (float)sin(angle); - result.y = axis.y * (float)sin(angle); - result.z = axis.z * (float)sin(angle); - result.w = (float)cos(angle); - - QuaternionNormalize(&result); - - return result; -} - -// Calculates the matrix from the given quaternion +// Returns a matrix for a given quaternion Matrix QuaternionToMatrix(Quaternion q) { Matrix result; @@ -1067,14 +915,36 @@ Matrix QuaternionToMatrix(Quaternion q) result.m13 = 0; result.m14 = 0; result.m15 = 1; + + return result; +} + +// Returns rotation quaternion for an angle and axis +// NOTE: angle must be provided in radians +Quaternion QuaternionFromAxisAngle(float angle, Vector3 axis) +{ + Quaternion result = { 0, 0, 0, 1 }; + + if (VectorLength(axis) != 0.0) + + angle *= 0.5; + + VectorNormalize(&axis); + + result.x = axis.x * (float)sin(angle); + result.y = axis.y * (float)sin(angle); + result.z = axis.z * (float)sin(angle); + result.w = (float)cos(angle); + + QuaternionNormalize(&result); return result; } -// Returns the axis and the angle for a given quaternion -void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) +// Returns the rotation angle and axis for a given quaternion +void QuaternionToAxisAngle(Quaternion q, float *outAngle, Vector3 *outAxis) { - if (abs(q.w) > 1.0f) QuaternionNormalize(&q); + if (fabs(q.w) > 1.0f) QuaternionNormalize(&q); Vector3 resAxis = { 0, 0, 0 }; float resAngle = 0; @@ -1097,4 +967,18 @@ void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) *outAxis = resAxis; *outAngle = resAngle; +} + +// Transform a quaternion given a transformation matrix +void QuaternionTransform(Quaternion *q, Matrix mat) +{ + float x = q->x; + float y = q->y; + float z = q->z; + float w = q->w; + + q->x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12*w; + q->y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13*w; + q->z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14*w; + q->w = mat.m3*x + mat.m7*y + mat.m11*z + mat.m15*w; } \ No newline at end of file diff --git a/src/raymath.h b/src/raymath.h index c8c1a26c3..d93c324c5 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -53,15 +53,15 @@ float y; float z; } Vector3; -#endif -// Matrix type (OpenGL style 4x4 - right handed, column major) -typedef struct Matrix { - float m0, m4, m8, m12; - float m1, m5, m9, m13; - float m2, m6, m10, m14; - float m3, m7, m11, m15; -} Matrix; + // Matrix type (OpenGL style 4x4 - right handed, column major) + typedef struct Matrix { + float m0, m4, m8, m12; + float m1, m5, m9, m13; + float m2, m6, m10, m14; + float m3, m7, m11, m15; + } Matrix; +#endif // Quaternion type typedef struct Quaternion { @@ -91,7 +91,7 @@ void VectorNormalize(Vector3 *v); // Normalize provided ve float VectorDistance(Vector3 v1, Vector3 v2); // Calculate distance between two points Vector3 VectorLerp(Vector3 v1, Vector3 v2, float amount); // Calculate linear interpolation between two vectors Vector3 VectorReflect(Vector3 vector, Vector3 normal); // Calculate reflected vector to normal -void VectorTransform(Vector3 *v, Matrix mat); // Transforms a Vector3 with a given Matrix +void VectorTransform(Vector3 *v, Matrix mat); // Transforms a Vector3 by a given Matrix Vector3 VectorZero(void); // Return a Vector3 init to zero //------------------------------------------------------------------------------------ @@ -107,15 +107,11 @@ Matrix MatrixIdentity(void); // Returns identity matr Matrix MatrixAdd(Matrix left, Matrix right); // Add two matrices Matrix MatrixSubstract(Matrix left, Matrix right); // Substract two matrices (left - right) Matrix MatrixTranslate(float x, float y, float z); // Returns translation matrix -Matrix MatrixRotate(float axisX, float axisY, float axisZ); // Returns rotation matrix -Matrix MatrixFromAxisAngle(Vector3 axis, float angle); // Returns rotation matrix for an angle around an specified axis -Matrix MatrixFromAxisAngle2(Vector3 axis, float angle); // Returns rotation matrix for an angle around an specified axis (test another implemntation) -Matrix MatrixFromQuaternion(Quaternion q); // Returns rotation matrix for a given quaternion +Matrix MatrixRotate(float angle, Vector3 axis); // Returns rotation matrix for an angle around an specified axis (angle in radians) Matrix MatrixRotateX(float angle); // Returns x-rotation matrix (angle in radians) Matrix MatrixRotateY(float angle); // Returns y-rotation matrix (angle in radians) Matrix MatrixRotateZ(float angle); // Returns z-rotation matrix (angle in radians) Matrix MatrixScale(float x, float y, float z); // Returns scaling matrix -Matrix MatrixTransform(Vector3 translation, Vector3 rotation, Vector3 scale); // Returns transformation matrix for a given translation, rotation and scale Matrix MatrixMultiply(Matrix left, Matrix right); // Returns two matrix multiplication Matrix MatrixFrustum(double left, double right, double bottom, double top, double near, double far); // Returns perspective projection matrix Matrix MatrixPerspective(double fovy, double aspect, double near, double far); // Returns perspective projection matrix @@ -126,14 +122,15 @@ void PrintMatrix(Matrix m); // Print matrix utility //------------------------------------------------------------------------------------ // Functions Declaration to work with Quaternions //------------------------------------------------------------------------------------ -float QuaternionLength(Quaternion quat); // Calculates the length of a quaternion +float QuaternionLength(Quaternion quat); // Compute the length of a quaternion void QuaternionNormalize(Quaternion *q); // Normalize provided quaternion Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2); // Calculate two quaternion multiplication Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float slerp); // Calculates spherical linear interpolation between two quaternions -Quaternion QuaternionFromMatrix(Matrix matrix); // Returns a quaternion from a given rotation matrix -Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle); // Returns rotation quaternion for an angle around an axis -Matrix QuaternionToMatrix(Quaternion q); // Calculates the matrix from the given quaternion -void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle); // Returns the axis and the angle for a given quaternion +Quaternion QuaternionFromMatrix(Matrix matrix); // Returns a quaternion for a given rotation matrix +Matrix QuaternionToMatrix(Quaternion q); // Returns a matrix for a given quaternion +Quaternion QuaternionFromAxisAngle(float angle, Vector3 axis); // Returns rotation quaternion for an angle and axis +void QuaternionToAxisAngle(Quaternion q, float *outAngle, Vector3 *outAxis); // Returns the rotation angle and axis for a given quaternion +void QuaternionTransform(Quaternion *q, Matrix mat); // Transform a quaternion given a transformation matrix #ifdef __cplusplus } diff --git a/src/rlgl.c b/src/rlgl.c index 50ef1efd3..157d8f20e 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -30,22 +30,23 @@ #include // Standard input / output lib #include // Declares malloc() and free() for memory management, rand() +#include // Declares strcmp(), strlen(), strtok() #if defined(GRAPHICS_API_OPENGL_11) - #ifdef __APPLE__ // OpenGL include for OSX - #include - #else - #include // Basic OpenGL include - #endif + #ifdef __APPLE__ // OpenGL include for OSX + #include + #else + #include // Basic OpenGL include + #endif #endif #if defined(GRAPHICS_API_OPENGL_33) #define GLEW_STATIC - #ifdef __APPLE__ // OpenGL include for OSX + #ifdef __APPLE__ // OpenGL include for OSX #include - #else - #include // Extensions loading lib - //#include "glad.h" // TODO: Other extensions loading lib? --> REVIEW + #else + #include // Extensions loading lib + //#include "glad.h" // TODO: Other extensions loading lib? --> REVIEW #endif #endif @@ -63,6 +64,49 @@ #define TEMP_VERTEX_BUFFER_SIZE 4096 // Temporal Vertex Buffer (required for vertex-transformations) // NOTE: Every vertex are 3 floats (12 bytes) +#ifndef GL_SHADING_LANGUAGE_VERSION + #define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#endif + +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif +#ifndef GL_ETC1_RGB8_OES + #define GL_ETC1_RGB8_OES 0x8D64 +#endif +#ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 +#endif +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#endif +#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#endif +#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR + #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR + #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#endif //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- @@ -121,6 +165,7 @@ typedef struct { typedef struct { GLuint textureId; int vertexCount; + // TODO: DrawState state -> Blending mode, shader } DrawCall; // pixel type (same as Color type) @@ -151,18 +196,9 @@ static VertexPositionColorBuffer lines; // No texture support static VertexPositionColorBuffer triangles; // No texture support static VertexPositionColorTextureIndexBuffer quads; -// Vetex-Fragment Shader Program ID -static GLuint defaultShaderProgram, simpleShaderProgram; - -// Default Shader program attibutes binding locations -static GLuint defaultVertexLoc, defaultTexcoordLoc, defaultColorLoc; -static GLuint defaultProjectionMatrixLoc, defaultModelviewMatrixLoc; -static GLuint defaultTextureLoc; - -// Simple Shader program attibutes binding locations -static GLuint simpleVertexLoc, simpleTexcoordLoc, simpleNormalLoc, simpleColorLoc; -static GLuint simpleProjectionMatrixLoc, simpleModelviewMatrixLoc; -static GLuint simpleTextureLoc; +// Shader Programs +static Shader defaultShader, simpleShader; +static Shader currentShader; // By default, defaultShader // Vertex Array Objects (VAO) static GLuint vaoLines, vaoTriangles, vaoQuads; @@ -180,17 +216,28 @@ static Vector3 *tempBuffer; static int tempBufferCount = 0; static bool useTempBuffer = false; -// Support for VAOs (OpenGL ES2 could not support VAO extensions) -static bool vaoSupported = false; +// Flags for supported extensions +static bool vaoSupported = false; // VAO support (OpenGL ES2 could not support VAO extension) +static bool npotSupported = false; // NPOT textures full support + +// Compressed textures support flags +static bool texCompDXTSupported = false; // DDS texture compression support +static bool texCompETC1Supported = false; // ETC1 texture compression support +static bool texCompETC2Supported = false; // ETC2/EAC texture compression support +static bool texCompPVRTSupported = false; // PVR texture compression support +static bool texCompASTCSupported = false; // ASTC texture compression support + +// Framebuffer object and texture +static GLuint fbo, fboColorTexture, fboDepthTexture; +static Model postproQuad; #endif #if defined(GRAPHICS_API_OPENGL_ES2) // NOTE: VAO functionality is exposed through extensions (OES) -// emscripten does not support VAOs static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays; static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray; static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays; -static PFNGLISVERTEXARRAYOESPROC glIsVertexArray; +//static PFNGLISVERTEXARRAYOESPROC glIsVertexArray; // NOTE: Fails in WebGL, omitted #endif // White texture useful for plain color polys (required by shader) @@ -201,15 +248,14 @@ unsigned int whiteTexture; // Module specific Functions Declaration //---------------------------------------------------------------------------------- #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -static GLuint LoadDefaultShader(void); -static GLuint LoadSimpleShader(void); +static Shader LoadDefaultShader(void); +static Shader LoadSimpleShader(void); static void InitializeBuffers(void); static void InitializeBuffersGPU(void); static void UpdateBuffers(void); - -// Custom shader files loading (external) -static GLuint LoadCustomShader(char *vertexFileName, char *fragmentFileName); static char *TextFileRead(char *fn); + +static void LoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compressedFormat); #endif #if defined(GRAPHICS_API_OPENGL_11) @@ -217,6 +263,8 @@ static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight); static pixel *GenNextMipmap(pixel *srcData, int srcWidth, int srcHeight); #endif +static char** StringSplit(char *baseString, const char delimiter, int *numExt); + //---------------------------------------------------------------------------------- // Module Functions Definition - Matrix operations //---------------------------------------------------------------------------------- @@ -310,27 +358,15 @@ void rlTranslatef(float x, float y, float z) // Multiply the current matrix by a rotation matrix void rlRotatef(float angleDeg, float x, float y, float z) { - // TODO: Support rotation in multiple axes - Matrix rot = MatrixIdentity(); + Matrix rotation = MatrixIdentity(); - // OPTION 1: It works... - if (x == 1) rot = MatrixRotateX(angleDeg*DEG2RAD); - else if (y == 1) rot = MatrixRotateY(angleDeg*DEG2RAD); - else if (z == 1) rot = MatrixRotateZ(angleDeg*DEG2RAD); - - // OPTION 2: Requires review... - //Vector3 vec = (Vector3){ 0, 1, 0 }; - //VectorNormalize(&vec); - //rot = MatrixFromAxisAngle(vec, angleDeg*DEG2RAD); // Working? - - // OPTION 3: TODO: Review, it doesn't work! - //Vector3 vec = (Vector3){ x, y, z }; - //VectorNormalize(&vec); - //rot = MatrixRotate(angleDeg*vec.x, angleDeg*vec.x, angleDeg*vec.x); - - MatrixTranspose(&rot); + Vector3 axis = (Vector3){ x, y, z }; + VectorNormalize(&axis); + rotation = MatrixRotate(angleDeg*DEG2RAD, axis); - *currentMatrix = MatrixMultiply(*currentMatrix, rot); + MatrixTranspose(&rotation); + + *currentMatrix = MatrixMultiply(*currentMatrix, rotation); } // Multiply the current matrix by a scaling matrix @@ -345,7 +381,7 @@ void rlScalef(float x, float y, float z) // Multiply the current matrix by another matrix void rlMultMatrixf(float *m) { - // TODO: review Matrix creation from array + // Matrix creation from array Matrix mat = { m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], @@ -503,7 +539,7 @@ void rlEnd(void) } } - // TODO: Make sure normals count match vertex count + // TODO: Make sure normals count match vertex count... if normals support is added in a future... :P } break; default: break; @@ -696,6 +732,22 @@ void rlDeleteTextures(unsigned int id) glDeleteTextures(1, &id); } +// Enable rendering to postprocessing FBO +void rlEnableFBO(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindFramebuffer(GL_FRAMEBUFFER, fbo); +#endif +} + +// Unload shader from GPU memory +void rlDeleteShader(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteProgram(id); +#endif +} + // Unload vertex data (VAO) from GPU memory void rlDeleteVertexArrays(unsigned int id) { @@ -750,93 +802,142 @@ int rlGetVersion(void) // Init OpenGL 3.3+ required data void rlglInit(void) { -#if defined(GRAPHICS_API_OPENGL_33) - // Loading extensions the hard way (Example) -/* - GLint numExt; - glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); - - for (int i = 0; i < numExt; i++) - { - const GLubyte *extensionName = glGetStringi(GL_EXTENSIONS, i); - if (strcmp(extensionName, (const GLubyte *)"GL_ARB_vertex_array_object") == 0) - { - // The extension is supported by our hardware and driver, try to get related functions popinters - glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)wglGetProcAddress("glGenVertexArrays"); - glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)wglGetProcAddress("glBindVertexArray"); - glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)wglGetProcAddress("glDeleteVertexArrays"); - glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)wglGetProcAddress("glIsVertexArray"); - } - } -*/ - - // 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 extensions supported"); - - // NOTE: GLEW is a big library that loads ALL extensions, using glad we can only load required ones... - //if (!gladLoadGL()) TraceLog("ERROR: Failed to initialize glad\n"); - - vaoSupported = true; -#endif - -#if defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... -#if !defined(PLATFORM_WEB) - glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)eglGetProcAddress("glGenVertexArraysOES"); - glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)eglGetProcAddress("glBindVertexArrayOES"); - glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)eglGetProcAddress("glDeleteVertexArraysOES"); - glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)eglGetProcAddress("glIsVertexArrayOES"); -#endif - - if (glGenVertexArrays == NULL) TraceLog(WARNING, "Could not initialize VAO extensions, VAOs not supported"); - else - { - vaoSupported = true; - TraceLog(INFO, "VAO extensions initialized successfully"); - } -#endif - + // Check OpenGL information and capabilities + //------------------------------------------------------------------------------ + // Print current OpenGL and GLSL version TraceLog(INFO, "GPU: Vendor: %s", glGetString(GL_VENDOR)); TraceLog(INFO, "GPU: Renderer: %s", glGetString(GL_RENDERER)); TraceLog(INFO, "GPU: Version: %s", glGetString(GL_VERSION)); - TraceLog(INFO, "GPU: GLSL: %s", glGetString(0x8B8C)); //GL_SHADING_LANGUAGE_VERSION + TraceLog(INFO, "GPU: GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); // NOTE: We can get a bunch of extra information about GPU capabilities (glGet*) //int maxTexSize; //glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); //TraceLog(INFO, "GL_MAX_TEXTURE_SIZE: %i", maxTexSize); + + //GL_MAX_TEXTURE_IMAGE_UNITS + //GL_MAX_VIEWPORT_DIMS //int numAuxBuffers; //glGetIntegerv(GL_AUX_BUFFERS, &numAuxBuffers); //TraceLog(INFO, "GL_AUX_BUFFERS: %i", numAuxBuffers); + + //GLint numComp = 0; + //GLint format[32] = { 0 }; + //glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numComp); + //glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, format); + //for (int i = 0; i < numComp; i++) TraceLog(INFO, "Supported compressed format: 0x%x", format[i]); - // Show supported extensions // NOTE: We don't need that much data on screen... right now... -/* -#if defined(GRAPHICS_API_OPENGL_33) - GLint numExt; - glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); - - for (int i = 0; i < numExt; i++) - { - TraceLog(INFO, "Supported extension: %s", glGetStringi(GL_EXTENSIONS, i)); - } -#elif defined(GRAPHICS_API_OPENGL_ES2) - char *extensions = (char *)glGetString(GL_EXTENSIONS); // One big string - - // NOTE: String could be splitted using strtok() function (string.h) - TraceLog(INFO, "Supported extension: %s", extensions); -#endif -*/ #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Get supported extensions list + GLint numExt; + +#if defined(GRAPHICS_API_OPENGL_33) + // 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"); + + vaoSupported = true; + npotSupported = true; + } + + // NOTE: GLEW is a big library that loads ALL extensions, we can use some alternative to load only required ones + // Alternatives: glLoadGen, glad, libepoxy + //if (!gladLoadGL()) TraceLog("ERROR: Failed to initialize glad\n"); + + // 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: We don't need to check again supported extensions but we do (in case GLEW is replaced sometime) + // We get a list of available extensions and we check for some of them (compressed textures) + glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + const char *ext[numExt]; + + for (int i = 0; i < numExt; i++) ext[i] = (char *)glGetStringi(GL_EXTENSIONS, i); + +#elif defined(GRAPHICS_API_OPENGL_ES2) + char *extensions = (char *)glGetString(GL_EXTENSIONS); // One big string + + // NOTE: String could be splitted using strtok() function (string.h) + char **ext = StringSplit(extensions, ' ', &numExt); +#endif + + TraceLog(INFO, "Number of supported extensions: %i", numExt); + + // Show supported extensions + //for (int i = 0; i < numExt; i++) TraceLog(INFO, "Supported extension: %s", ext[i]); + + // Check required extensions + for (int i = 0; i < numExt; i++) + { +#if defined(GRAPHICS_API_OPENGL_ES2) + // Check VAO support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature + if (strcmp(ext[i], (const char *)"GL_OES_vertex_array_object") == 0) + { + vaoSupported = true; + + // The extension is supported by our hardware and driver, try to get related functions pointers + // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... + glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)eglGetProcAddress("glGenVertexArraysOES"); + glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)eglGetProcAddress("glBindVertexArrayOES"); + glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)eglGetProcAddress("glDeleteVertexArraysOES"); + //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)eglGetProcAddress("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted + } + + // Check NPOT textures support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature + if (strcmp(ext[i], (const char *)"GL_OES_texture_npot") == 0) npotSupported = true; +#endif + + // DDS texture compression support + if (strcmp(ext[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) texCompDXTSupported = true; + + // ETC1 texture compression support + if (strcmp(ext[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) texCompETC1Supported = true; + + // ETC2/EAC texture compression support + if (strcmp(ext[i], (const char *)"GL_ARB_ES3_compatibility") == 0) texCompETC2Supported = true; + + // PVR texture compression support + if (strcmp(ext[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) texCompPVRTSupported = true; + + // ASTC texture compression support + if (strcmp(ext[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) texCompASTCSupported = true; + } + +#if defined(GRAPHICS_API_OPENGL_ES2) + if (vaoSupported) TraceLog(INFO, "[EXTENSION] VAO extension detected, VAO functions initialized successfully"); + else TraceLog(WARNING, "[EXTENSION] VAO extension not found, VAO usage not supported"); + + if (npotSupported) TraceLog(INFO, "[EXTENSION] NPOT textures extension detected, full NPOT textures supported"); + else TraceLog(WARNING, "[EXTENSION] NPOT textures extension not found, NPOT textures not supported"); + + // Once supported extensions have been checked, we should free strings memory + free(ext); +#endif + + if (texCompDXTSupported) TraceLog(INFO, "[EXTENSION] DXT compressed textures supported"); + if (texCompETC1Supported) TraceLog(INFO, "[EXTENSION] ETC1 compressed textures supported"); + if (texCompETC2Supported) TraceLog(INFO, "[EXTENSION] ETC2/EAC compressed textures supported"); + if (texCompPVRTSupported) TraceLog(INFO, "[EXTENSION] PVRT compressed textures supported"); + if (texCompASTCSupported) TraceLog(INFO, "[EXTENSION] ASTC compressed textures supported"); + + // Initialize buffers, default shaders and default textures + //---------------------------------------------------------- + // Set default draw mode currentDrawMode = RL_TRIANGLES; @@ -849,38 +950,11 @@ void rlglInit(void) for (int i = 0; i < MATRIX_STACK_SIZE; i++) stack[i] = MatrixIdentity(); // Init default Shader (GLSL 110) -> Common for GL 3.3+ and ES2 - defaultShaderProgram = LoadDefaultShader(); - simpleShaderProgram = LoadSimpleShader(); - //customShaderProgram = LoadShaders("simple150.vert", "simple150.frag"); - - // Get handles to GLSL input vars locations for defaultShaderProgram - //------------------------------------------------------------------- - defaultVertexLoc = glGetAttribLocation(defaultShaderProgram, "vertexPosition"); - defaultTexcoordLoc = glGetAttribLocation(defaultShaderProgram, "vertexTexCoord"); - defaultColorLoc = glGetAttribLocation(defaultShaderProgram, "vertexColor"); - - // Get handles to GLSL uniform vars locations (vertex-shader) - defaultModelviewMatrixLoc = glGetUniformLocation(defaultShaderProgram, "modelviewMatrix"); - defaultProjectionMatrixLoc = glGetUniformLocation(defaultShaderProgram, "projectionMatrix"); - - // Get handles to GLSL uniform vars locations (fragment-shader) - defaultTextureLoc = glGetUniformLocation(defaultShaderProgram, "texture0"); - //-------------------------------------------------------------------- + defaultShader = LoadDefaultShader(); + simpleShader = LoadSimpleShader(); + //customShader = rlglLoadShader("custom.vs", "custom.fs"); // Works ok - // Get handles to GLSL input vars locations for simpleShaderProgram - //------------------------------------------------------------------- - simpleVertexLoc = glGetAttribLocation(simpleShaderProgram, "vertexPosition"); - simpleTexcoordLoc = glGetAttribLocation(simpleShaderProgram, "vertexTexCoord"); - simpleNormalLoc = glGetAttribLocation(defaultShaderProgram, "vertexNormal"); - - // Get handles to GLSL uniform vars locations (vertex-shader) - simpleModelviewMatrixLoc = glGetUniformLocation(simpleShaderProgram, "modelviewMatrix"); - simpleProjectionMatrixLoc = glGetUniformLocation(simpleShaderProgram, "projectionMatrix"); - - // Get handles to GLSL uniform vars locations (fragment-shader) - simpleTextureLoc = glGetUniformLocation(simpleShaderProgram, "texture0"); - simpleColorLoc = glGetUniformLocation(simpleShaderProgram, "fragColor"); - //-------------------------------------------------------------------- + currentShader = defaultShader; InitializeBuffers(); // Init vertex arrays InitializeBuffersGPU(); // Init VBO and VAO @@ -893,7 +967,7 @@ void rlglInit(void) // Create default white texture for plain colors (required by shader) unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) - whiteTexture = rlglLoadTexture(pixels, 1, 1, false); + whiteTexture = rlglLoadTexture(pixels, 1, 1, UNCOMPRESSED_R8G8B8A8, 1, false); if (whiteTexture != 0) TraceLog(INFO, "[TEX ID %i] Base white texture loaded successfully", whiteTexture); else TraceLog(WARNING, "Base white texture could not be loaded"); @@ -912,6 +986,88 @@ void rlglInit(void) #endif } +// Init postpro system +void rlglInitPostpro(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Create the texture that will serve as the color attachment for the framebuffer + glGenTextures(1, &fboColorTexture); + glBindTexture(GL_TEXTURE_2D, fboColorTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, GetScreenWidth(), GetScreenHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + // Create the texture that will serve as the depth attachment for the framebuffer. + glGenTextures(1, &fboDepthTexture); + glBindTexture(GL_TEXTURE_2D, fboDepthTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GetScreenWidth(), GetScreenHeight(), 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + // Create the framebuffer object + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + // Attach color texture and depth texture to FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboColorTexture, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, fboDepthTexture, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) TraceLog(WARNING, "Framebuffer object could not be created..."); + else TraceLog(INFO, "[FBO ID %i] Framebuffer object created successfully", fbo); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Create a simple quad model to render fbo texture + VertexData quadData; + + quadData.vertexCount = 6; + + float w = GetScreenWidth(); + float h = GetScreenHeight(); + + float quadPositions[6*3] = { w, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, h, 0.0, 0, h, 0.0, w, h, 0.0, w, 0.0, 0.0 }; + float quadTexcoords[6*2] = { 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0 }; + float quadNormals[6*3] = { 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0 }; + unsigned char quadColors[6*4] = { 255 }; + + quadData.vertices = quadPositions; + quadData.texcoords = quadTexcoords; + quadData.normals = quadNormals; + quadData.colors = quadColors; + + postproQuad = rlglLoadModel(quadData); + + // NOTE: fboColorTexture id must be assigned to postproQuad model shader +#endif +} + +// Set postprocessing shader +void rlglSetPostproShader(Shader shader) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlglSetModelShader(&postproQuad, shader); + + Texture2D texture; + texture.id = fboColorTexture; + texture.width = GetScreenWidth(); + texture.height = GetScreenHeight(); + + SetShaderMapDiffuse(&postproQuad.shader, texture); + + //TraceLog(INFO, "Postproquad texture id: %i", postproQuad.texture.id); + //TraceLog(INFO, "Postproquad shader diffuse map id: %i", postproQuad.shader.texDiffuseId); + //TraceLog(INFO, "Shader diffuse map id: %i", shader.texDiffuseId); +#endif +} + // Vertex Buffer Object deinitialization (memory free) void rlglClose(void) { @@ -949,7 +1105,8 @@ void rlglClose(void) //glDetachShader(defaultShaderProgram, f); //glDeleteShader(v); //glDeleteShader(f); - glDeleteProgram(defaultShaderProgram); + glDeleteProgram(defaultShader.id); + glDeleteProgram(simpleShader.id); // Free vertex arrays memory free(lines.vertices); @@ -966,10 +1123,18 @@ void rlglClose(void) // Free GPU texture glDeleteTextures(1, &whiteTexture); + if (fbo != 0) + { + glDeleteFramebuffers(1, &fbo); + + UnloadModel(postproQuad); + } + free(draws); #endif } +// Drawing batches: triangles, quads, lines void rlglDraw(void) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) @@ -977,13 +1142,13 @@ void rlglDraw(void) if ((lines.vCounter > 0) || (triangles.vCounter > 0) || (quads.vCounter > 0)) { - glUseProgram(defaultShaderProgram); // Use our shader + glUseProgram(currentShader.id); - glUniformMatrix4fv(defaultProjectionMatrixLoc, 1, false, GetMatrixVector(projection)); - glUniformMatrix4fv(defaultModelviewMatrixLoc, 1, false, GetMatrixVector(modelview)); - glUniform1i(defaultTextureLoc, 0); + glUniformMatrix4fv(currentShader.projectionLoc, 1, false, GetMatrixVector(projection)); + glUniformMatrix4fv(currentShader.modelviewLoc, 1, false, GetMatrixVector(modelview)); + glUniform1i(currentShader.mapDiffuseLoc, 0); } - + // NOTE: We draw in this order: triangle shapes, textured quads and lines if (triangles.vCounter > 0) @@ -997,12 +1162,15 @@ void rlglDraw(void) else { glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultVertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); - glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(defaultColorLoc); + if (currentShader.colorLoc != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + } } glDrawArrays(GL_TRIANGLES, 0, triangles.vCounter); @@ -1025,17 +1193,20 @@ void rlglDraw(void) { // Enable vertex attributes glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultVertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]); - glVertexAttribPointer(defaultTexcoordLoc, 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultTexcoordLoc); - - glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(defaultColorLoc); + glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.texcoordLoc); + if (currentShader.colorLoc != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + } + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadsBuffer[3]); } @@ -1082,12 +1253,15 @@ void rlglDraw(void) else { glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultVertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); - glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(defaultColorLoc); + if (currentShader.colorLoc != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + } } glDrawArrays(GL_LINES, 0, lines.vCounter); @@ -1098,6 +1272,8 @@ void rlglDraw(void) if (vaoSupported) glBindVertexArray(0); // Unbind VAO + glUseProgram(0); // Unbind shader program + // Reset draws counter drawsCounter = 1; draws[0].textureId = whiteTexture; @@ -1116,8 +1292,19 @@ void rlglDraw(void) #endif } +// Draw with postprocessing shader +void rlglDrawPostpro(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + rlglDrawModel(postproQuad, (Vector3){0,0,0}, 0.0f, (Vector3){0,0,0}, (Vector3){1.0f, 1.0f, 1.0f}, WHITE, false); +#endif +} + // Draw a 3d model -void rlglDrawModel(Model model, Vector3 position, Vector3 rotation, Vector3 scale, Color color, bool wires) +// NOTE: Model transform can come within model struct +void rlglDrawModel(Model model, Vector3 position, float rotationAngle, Vector3 rotationAxis, Vector3 scale, Color color, bool wires) { #if defined (GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) // NOTE: glPolygonMode() not available on OpenGL ES @@ -1126,7 +1313,7 @@ void rlglDrawModel(Model model, Vector3 position, Vector3 rotation, Vector3 scal #if defined(GRAPHICS_API_OPENGL_11) glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, model.textureId); + glBindTexture(GL_TEXTURE_2D, model.texture.id); // NOTE: On OpenGL 1.1 we use Vertex Arrays to draw model glEnableClientState(GL_VERTEX_ARRAY); // Enable vertex array @@ -1141,9 +1328,7 @@ void rlglDrawModel(Model model, Vector3 position, Vector3 rotation, Vector3 scal rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); rlScalef(scale.x, scale.y, scale.z); - rlRotatef(rotation.y, 0, 1, 0); - - // TODO: If rotate in multiple axis, get rotation matrix and use rlMultMatrix() + rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z); rlColor4ub(color.r, color.g, color.b, color.a); @@ -1159,55 +1344,67 @@ void rlglDrawModel(Model model, Vector3 position, Vector3 rotation, Vector3 scal #endif #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glUseProgram(simpleShaderProgram); // Use our simple shader + glUseProgram(model.shader.id); - VectorScale(&rotation, DEG2RAD); - + // Apply transformation provided in model.transform matrix + Matrix modelviewworld = MatrixMultiply(model.transform, modelview); // World-space transformation + + // Apply transformations provided in function // Get transform matrix (rotation -> scale -> translation) - Matrix transform = MatrixTransform(position, rotation, scale); - Matrix modelviewworld = MatrixMultiply(transform, modelview); + Matrix rotation = MatrixRotate(rotationAngle*DEG2RAD, rotationAxis); + Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); + Matrix translation = MatrixTranslate(position.x, position.y, position.z); + + Matrix transform = MatrixMultiply(MatrixMultiply(rotation, matScale), translation); // Object-space transformation matrix + modelviewworld = MatrixMultiply(transform, modelview); // World-space transformation + + // Projection: Screen-space transformation // NOTE: Drawing in OpenGL 3.3+, transform is passed to shader - glUniformMatrix4fv(simpleProjectionMatrixLoc, 1, false, GetMatrixVector(projection)); - glUniformMatrix4fv(simpleModelviewMatrixLoc, 1, false, GetMatrixVector(modelviewworld)); - glUniform1i(simpleTextureLoc, 0); - + glUniformMatrix4fv(model.shader.projectionLoc, 1, false, GetMatrixVector(projection)); + glUniformMatrix4fv(model.shader.modelviewLoc, 1, false, GetMatrixVector(modelviewworld)); + // Apply color tinting to model // NOTE: Just update one uniform on fragment shader float vColor[4] = { (float)color.r/255, (float)color.g/255, (float)color.b/255, (float)color.a/255 }; - glUniform4fv(simpleColorLoc, 1, vColor); + glUniform4fv(model.shader.tintColorLoc, 1, vColor); + + // Set shader textures (diffuse, normal, specular) + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, model.shader.texDiffuseId); + //glUniform1i(model.shader.mapDiffuseLoc, 0); // Diffuse texture fits in texture unit 0 if (vaoSupported) { - glBindVertexArray(model.vaoId); + glBindVertexArray(model.mesh.vaoId); } else { // Bind model VBOs data - glBindBuffer(GL_ARRAY_BUFFER, model.vboId[0]); - glVertexAttribPointer(simpleVertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(simpleVertexLoc); + glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[0]); + glVertexAttribPointer(model.shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(model.shader.vertexLoc); - glBindBuffer(GL_ARRAY_BUFFER, model.vboId[1]); - glVertexAttribPointer(simpleTexcoordLoc, 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(simpleTexcoordLoc); + glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[1]); + glVertexAttribPointer(model.shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(model.shader.texcoordLoc); // Add normals support - glBindBuffer(GL_ARRAY_BUFFER, model.vboId[2]); - glVertexAttribPointer(simpleNormalLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(simpleNormalLoc); + glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[2]); + glVertexAttribPointer(model.shader.normalLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(model.shader.normalLoc); } - glBindTexture(GL_TEXTURE_2D, model.textureId); - + // Draw call! glDrawArrays(GL_TRIANGLES, 0, model.mesh.vertexCount); glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures + glActiveTexture(GL_TEXTURE0); // Set shader active texture to default 0 if (vaoSupported) glBindVertexArray(0); // Unbind VAO else glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind VBOs - - glUseProgram(0); + + glUseProgram(0); // Unbind shader program #endif #if defined (GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) @@ -1243,7 +1440,7 @@ void rlglInitGraphics(int offsetX, int offsetY, int width, int height) rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix rlLoadIdentity(); // Reset current matrix (PROJECTION) - rlOrtho(0, width - offsetX, height - offsetY, 0, 0, 1); // Config orthographic mode: top-left corner --> (0,0) + rlOrtho(0, width - offsetX, height - offsetY, 0, 0, 1); // Config orthographic mode: top-left corner --> (0,0) rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix rlLoadIdentity(); // Reset current matrix (MODELVIEW) @@ -1262,64 +1459,331 @@ void rlglInitGraphics(int offsetX, int offsetY, int width, int height) TraceLog(INFO, "OpenGL Graphics initialized successfully"); } -// Convert image data to OpenGL texture (returns OpenGL valid Id) -unsigned int rlglLoadTexture(unsigned char *data, int width, int height, bool genMipmaps) +// Get world coordinates from screen coordinates +// TODO: It doesn't work! It drives me crazy! +Vector3 rlglUnproject(Vector3 source, Matrix proj, Matrix view) { - glBindTexture(GL_TEXTURE_2D,0); // Free any old binding + //GLint viewport[4]; + //glGetIntegerv(GL_VIEWPORT, viewport); - GLuint id; - glGenTextures(1, &id); // Generate Pointer to the texture + // Viewport data +/* + int x = 0; + int y = 0; + int width = GetScreenWidth(); + int height = GetScreenHeight(); + float minDepth = 0.0f; + float maxDepth = 1.0f; +*/ +/* + Matrix modelviewprojection = MatrixMultiply(modelview, projection); + MatrixInvert(&modelviewprojection); + + Vector3 vector; + + vector.x = (((source.x - x) / ((float)width)) * 2.0f) - 1.0f; + vector.y = -((((source.y - y) / ((float)height)) * 2.0f) - 1.0f); + vector.z = (source.z - minDepth) / (maxDepth - minDepth); + + //float a = (((vector.x * matrix.M14) + (vector.y * matrix.M24)) + (vector.z * matrix.M34)) + matrix.M44; + //float a = (((vector.x * modelviewprojection.m3) + (vector.y * modelviewprojection.m7)) + (vector.z * modelviewprojection.m11)) + modelviewprojection.m15; + VectorTransform(&vector, modelviewprojection); + + //if (!MathUtil.IsOne(a)) vector = (vector / a); + //VectorScale(&vector, 1/a); + + return vector; +*/ +/* + Vector3 worldPoint; + + // Transformation matrices + Matrix modelviewprojection = MatrixIdentity(); + Quaternion quat; + + // Calculation for inverting a matrix, compute projection x modelview + modelviewprojection = MatrixMultiply(proj, view); + MatrixInvert(&modelviewprojection); + + // Transformation of normalized coordinates between -1 and 1 + quat.x = ((source.x - (float)x)/(float)width*2.0) - 1.0f; + quat.y = ((source.y - (float)y)/(float)height*2.0) - 1.0f; + quat.z = 2.0*source.z - 1.0; + quat.w = 1.0; + + // Objects coordinates + QuaternionTransform(&quat, modelviewprojection); + + //if (quat.w == 0.0) return 0; + + worldPoint.x = quat.x/quat.w; + worldPoint.y = quat.y/quat.w; + worldPoint.z = quat.z/quat.w; + + return worldPoint; + */ +/* + Quaternion quat; + Vector3 vec; + + quat.x = 2.0f * GetMousePosition().x / (float)width - 1; + quat.y = -(2.0f * GetMousePosition().y / (float)height - 1); + quat.z = 0; + quat.w = 1; + + Matrix invView; + MatrixInvert(&view); + Matrix invProj; + MatrixInvert(&proj); + + quat.x = invProj.m0 * quat.x + invProj.m4 * quat.y + invProj.m8 * quat.z + invProj.m12 * quat.w; + quat.y = invProj.m1 * quat.x + invProj.m5 * quat.y + invProj.m9 * quat.z + invProj.m13 * quat.w; + quat.z = invProj.m2 * quat.x + invProj.m6 * quat.y + invProj.m10 * quat.z + invProj.m14 * quat.w; + quat.w = invProj.m3 * quat.x + invProj.m7 * quat.y + invProj.m11 * quat.z + invProj.m15 * quat.w; + + quat.x = invView.m0 * quat.x + invView.m4 * quat.y + invView.m8 * quat.z + invView.m12 * quat.w; + quat.y = invView.m1 * quat.x + invView.m5 * quat.y + invView.m9 * quat.z + invView.m13 * quat.w; + quat.z = invView.m2 * quat.x + invView.m6 * quat.y + invView.m10 * quat.z + invView.m14 * quat.w; + quat.w = invView.m3 * quat.x + invView.m7 * quat.y + invView.m11 * quat.z + invView.m15 * quat.w; + + vec.x /= quat.w; + vec.y /= quat.w; + vec.z /= quat.w; + + return vec; + */ +/* + Vector3 worldPoint; + + // Transformation matrices + Matrix modelviewprojection; + Quaternion quat; + + // Calculation for inverting a matrix, compute projection x modelview + modelviewprojection = MatrixMultiply(view, proj); + + // Now compute the inverse of matrix A + MatrixInvert(&modelviewprojection); + + // Transformation of normalized coordinates between -1 and 1 + quat.x = ((source.x - (float)x)/(float)width*2.0) - 1.0f; + quat.y = ((source.y - (float)y)/(float)height*2.0) - 1.0f; + quat.z = 2.0*source.z - 1.0; + quat.w = 1.0; + + // Traspose quaternion and multiply + Quaternion result; + result.x = modelviewprojection.m0 * quad.x + modelviewprojection.m4 * quad.y + modelviewprojection.m8 * quad.z + modelviewprojection.m12 * quad.w; + result.y = modelviewprojection.m1 * quad.x + modelviewprojection.m5 * quad.y + modelviewprojection.m9 * quad.z + modelviewprojection.m13 * quad.w; + result.z = modelviewprojection.m2 * quad.x + modelviewprojection.m6 * quad.y + modelviewprojection.m10 * quad.z + modelviewprojection.m14 * quad.w; + result.w = modelviewprojection.m3 * quad.x + modelviewprojection.m7 * quad.y + modelviewprojection.m11 * quad.z + modelviewprojection.m15 * quad.w; + + // Invert + result.w = 1.0f / result.w; + + //if (quat.w == 0.0) return 0; + + worldPoint.x = quat.x * quat.w; + worldPoint.y = quat.y * quat.w; + worldPoint.z = quat.z * quat.w; + + return worldPoint; + */ +/* + // Needed Vectors + Vector3 normalDeviceCoordinates; + Quaternion rayClip; + Quaternion rayEye; + Vector3 rayWorld; + + // Getting normal device coordinates + float x = (2.0 * mousePosition.x) / GetScreenWidth() - 1.0; + float y = 1.0 - (2.0 * mousePosition.y) / GetScreenHeight(); + float z = 1.0; + normalDeviceCoordinates = (Vector3){ x, y, z }; + + // Getting clip vector + rayClip = (Quaternion){ normalDeviceCoordinates.x, normalDeviceCoordinates.y, -1, 1 }; + + Matrix invProjection = projection; + MatrixInvert(&invProjection); + + rayEye = MatrixQuaternionMultiply(invProjection, rayClip); + rayEye = (Quaternion){ rayEye.x, rayEye.y, -1, 0 }; + + Matrix invModelview = modelview; + MatrixInvert(&invModelview); + + rayWorld = MatrixVector3Multiply(invModelview, (Vector3){rayEye.x, rayEye.y, rayEye.z} ); + VectorNormalize(&rayWorld); + + return rayWorld; +*/ + return (Vector3){ 0, 0, 0 }; +} + +// Convert image data to OpenGL texture (returns OpenGL valid Id) +unsigned int rlglLoadTexture(void *data, int width, int height, int textureFormat, int mipmapCount, bool genMipmaps) +{ + glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding + + GLuint id = 0; + + // Check texture format support by OpenGL 1.1 (compressed textures not supported) + if ((rlGetVersion() == OPENGL_11) && (textureFormat >= 8)) + { + TraceLog(WARNING, "OpenGL 1.1 does not support GPU compressed texture formats"); + return id; + } + + if ((!texCompDXTSupported) && ((textureFormat == COMPRESSED_DXT1_RGB) || (textureFormat == COMPRESSED_DXT1_RGBA) || + (textureFormat == COMPRESSED_DXT3_RGBA) || (textureFormat == COMPRESSED_DXT5_RGBA))) + { + TraceLog(WARNING, "DXT compressed texture format not supported"); + return id; + } + + if ((!texCompETC1Supported) && (textureFormat == COMPRESSED_ETC1_RGB)) + { + TraceLog(WARNING, "ETC1 compressed texture format not supported"); + return id; + } + + if ((!texCompETC2Supported) && ((textureFormat == COMPRESSED_ETC2_RGB) || (textureFormat == COMPRESSED_ETC2_EAC_RGBA))) + { + TraceLog(WARNING, "ETC2 compressed texture format not supported"); + return id; + } + + if ((!texCompPVRTSupported) && ((textureFormat == COMPRESSED_PVRT_RGB) || (textureFormat == COMPRESSED_PVRT_RGBA))) + { + TraceLog(WARNING, "PVRT compressed texture format not supported"); + return id; + } + + if ((!texCompASTCSupported) && ((textureFormat == COMPRESSED_ASTC_4x4_RGBA) || (textureFormat == COMPRESSED_ASTC_8x8_RGBA))) + { + TraceLog(WARNING, "ASTC compressed texture format not supported"); + return id; + } + + glGenTextures(1, &id); // Generate Pointer to the texture + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + //glActiveTexture(GL_TEXTURE0); // If not defined, using GL_TEXTURE0 by default (shader texture) +#endif - //glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, id); - // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used! - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repead on x-axis - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repead on y-axis +#if defined(GRAPHICS_API_OPENGL_33) + // NOTE: We define internal (GPU) format as GL_RGBA8 (probably BGRA8 in practice, driver takes care) + // NOTE: On embedded systems, we let the driver choose the best internal format + // Support for multiple color modes (16bit color modes and grayscale) + // (sized)internalFormat format type + // GL_R GL_RED GL_UNSIGNED_BYTE + // GL_RGB565 GL_RGB GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_5_6_5 + // GL_RGB5_A1 GL_RGBA GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_5_5_5_1 + // GL_RGBA4 GL_RGBA GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_4_4_4_4 + // GL_RGBA8 GL_RGBA GL_UNSIGNED_BYTE + // GL_RGB8 GL_RGB GL_UNSIGNED_BYTE + + switch (textureFormat) + { + case UNCOMPRESSED_GRAYSCALE: + { + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, (unsigned char *)data); + + // With swizzleMask we define how a one channel texture will be mapped to RGBA + // Required GL >= 3.3 or EXT_texture_swizzle/ARB_texture_swizzle + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + + TraceLog(INFO, "[TEX ID %i] Grayscale texture loaded and swizzled", id); + } break; + case UNCOMPRESSED_GRAY_ALPHA: + { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, width, height, 0, GL_RG, GL_UNSIGNED_BYTE, (unsigned char *)data); + + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } break; + + case UNCOMPRESSED_R5G6B5: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB565, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (unsigned short *)data); break; + case UNCOMPRESSED_R8G8B8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, (unsigned char *)data); break; + case UNCOMPRESSED_R5G5B5A1: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (unsigned short *)data); break; + case UNCOMPRESSED_R4G4B4A4: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA4, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (unsigned short *)data); break; + case UNCOMPRESSED_R8G8B8A8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)data); break; + case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_S3TC_DXT1_EXT); break; + case COMPRESSED_DXT1_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT); break; + case COMPRESSED_DXT3_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT); break; + case COMPRESSED_DXT5_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT); break; + case COMPRESSED_ETC1_RGB: if (texCompETC1Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_ETC1_RGB8_OES); break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case COMPRESSED_ETC2_RGB: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB8_ETC2); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case COMPRESSED_ETC2_EAC_RGBA: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA8_ETC2_EAC); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case COMPRESSED_PVRT_RGB: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU + case COMPRESSED_PVRT_RGBA: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU + case COMPRESSED_ASTC_4x4_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_4x4_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case COMPRESSED_ASTC_8x8_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_8x8_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + default: TraceLog(WARNING, "Texture format not recognized"); break; + } +#elif defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA + switch (textureFormat) + { + case UNCOMPRESSED_GRAYSCALE: glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, (unsigned char *)data); break; + case UNCOMPRESSED_GRAY_ALPHA: glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (unsigned char *)data); break; + case UNCOMPRESSED_R5G6B5: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (unsigned short *)data); break; + case UNCOMPRESSED_R8G8B8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, (unsigned char *)data); break; + case UNCOMPRESSED_R5G5B5A1: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (unsigned short *)data); break; + case UNCOMPRESSED_R4G4B4A4: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (unsigned short *)data); break; + case UNCOMPRESSED_R8G8B8A8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)data); break; +#if defined(GRAPHICS_API_OPENGL_ES2) + case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_S3TC_DXT1_EXT); break; + case COMPRESSED_DXT1_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT); break; + case COMPRESSED_DXT3_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT); break; // NOTE: Not supported by WebGL + case COMPRESSED_DXT5_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT); break; // NOTE: Not supported by WebGL + case COMPRESSED_ETC1_RGB: if (texCompETC1Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_ETC1_RGB8_OES); break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case COMPRESSED_ETC2_RGB: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB8_ETC2); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case COMPRESSED_ETC2_EAC_RGBA: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA8_ETC2_EAC); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case COMPRESSED_PVRT_RGB: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU + case COMPRESSED_PVRT_RGBA: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU + case COMPRESSED_ASTC_4x4_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_4x4_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case COMPRESSED_ASTC_8x8_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_8x8_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 +#endif + default: TraceLog(WARNING, "Texture format not supported"); break; + } +#endif + + // Check if texture is power-of-two (POT) to enable mipmap generation bool texIsPOT = false; - // Check if width and height are power-of-two (POT) if (((width > 0) && ((width & (width - 1)) == 0)) && ((height > 0) && ((height & (height - 1)) == 0))) texIsPOT = true; if (genMipmaps && !texIsPOT) { TraceLog(WARNING, "[TEX ID %i] Texture is not power-of-two, mipmaps can not be generated", id); - genMipmaps = false; } - // If mipmaps are being used, we configure mag-min filters accordingly - if (genMipmaps) - { - // Trilinear filtering with mipmaps - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate use of mipmaps (must be available) - } - else - { - // Not using mipmappings - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR - } - + // Generate mipmaps if required + // TODO: Improve mipmaps support #if defined(GRAPHICS_API_OPENGL_11) if (genMipmaps) { - TraceLog(WARNING, "[TEX ID %i] Mipmaps generated manually on CPU side", id); - // Compute required mipmaps // NOTE: data size is reallocated to fit mipmaps data int mipmapCount = GenerateMipmaps(data, width, height); - int offset = 0; - int size = 0; + // TODO: Adjust mipmap size depending on texture format! + int size = width*height*4; + int offset = size; - int mipWidth = width; - int mipHeight = height; + int mipWidth = width/2; + int mipHeight = height/2; // Load the mipmaps - for (int level = 0; level < mipmapCount; level++) + for (int level = 1; level < mipmapCount; level++) { glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, mipWidth, mipHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data + offset); @@ -1329,33 +1793,47 @@ unsigned int rlglLoadTexture(unsigned char *data, int width, int height, bool ge mipWidth /= 2; mipHeight /= 2; } + + TraceLog(WARNING, "[TEX ID %i] Mipmaps generated manually on CPU side", id); } - else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); -#endif - -#if defined(GRAPHICS_API_OPENGL_33) - // NOTE: We define internal (GPU) format as GL_RGBA8 (probably BGRA8 in practice, driver takes care) - //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); // OpenGL - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); // WebGL -#elif defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: On embedded systems, we let the driver choose the best internal format - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); -#endif - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (genMipmaps) +#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((mipmapCount == 1) && (genMipmaps)) { glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically TraceLog(INFO, "[TEX ID %i] Mipmaps generated automatically for new texture", id); } #endif - // At this point we have the image converted to texture and uploaded to GPU + // Texture parameters configuration + // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis +#else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis +#endif + + // Magnification and minification filters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR + +#if defined(GRAPHICS_API_OPENGL_33) + if ((mipmapCount > 1) || (genMipmaps)) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate Trilinear filtering for mipmaps (must be available) + } +#endif + + // At this point we have the texture loaded in GPU, with mipmaps generated (if desired) and texture parameters configured // Unbind current texture glBindTexture(GL_TEXTURE_2D, 0); - TraceLog(INFO, "[TEX ID %i] Texture created successfully (%ix%i)", id, width, height); + if (id > 0) TraceLog(INFO, "[TEX ID %i] Texture created successfully (%ix%i)", id, width, height); + else TraceLog(WARNING, "Texture could not be created"); return id; } @@ -1366,19 +1844,23 @@ Model rlglLoadModel(VertexData mesh) Model model; model.mesh = mesh; + model.transform = MatrixIdentity(); #if defined(GRAPHICS_API_OPENGL_11) - model.textureId = 0; // No texture required - model.vaoId = 0; // Vertex Array Object - model.vboId[0] = 0; // Vertex position VBO - model.vboId[1] = 0; // Texcoords VBO - model.vboId[2] = 0; // Normals VBO + model.mesh.vaoId = 0; // Vertex Array Object + model.mesh.vboId[0] = 0; // Vertex position VBO + model.mesh.vboId[1] = 0; // Texcoords VBO + model.mesh.vboId[2] = 0; // Normals VBO + model.texture.id = 0; // No texture required + model.shader.id = 0; // No shader used #elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - model.textureId = 1; // Default whiteTexture + model.texture.id = whiteTexture; // Default whiteTexture + model.texture.width = 1; // Default whiteTexture width + model.texture.height = 1; // Default whiteTexture height - GLuint vaoModel = 0; // Vertex Array Objects (VAO) - GLuint vertexBuffer[3]; // Vertex Buffer Objects (VBO) + GLuint vaoModel = 0; // Vertex Array Objects (VAO) + GLuint vertexBuffer[3]; // Vertex Buffer Objects (VBO) if (vaoSupported) { @@ -1393,121 +1875,145 @@ Model rlglLoadModel(VertexData mesh) // Enable vertex attributes: position glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh.vertexCount, mesh.vertices, GL_STATIC_DRAW); - glEnableVertexAttribArray(simpleVertexLoc); - glVertexAttribPointer(simpleVertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(simpleShader.vertexLoc); + glVertexAttribPointer(simpleShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); // Enable vertex attributes: texcoords glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh.vertexCount, mesh.texcoords, GL_STATIC_DRAW); - glEnableVertexAttribArray(simpleTexcoordLoc); - glVertexAttribPointer(simpleTexcoordLoc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(simpleShader.texcoordLoc); + glVertexAttribPointer(simpleShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); // Enable vertex attributes: normals glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[2]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh.vertexCount, mesh.normals, GL_STATIC_DRAW); - glEnableVertexAttribArray(simpleNormalLoc); - glVertexAttribPointer(simpleNormalLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(simpleShader.normalLoc); + glVertexAttribPointer(simpleShader.normalLoc, 3, GL_FLOAT, 0, 0, 0); + + model.shader = simpleShader; // By default, simple shader will be used + + model.mesh.vboId[0] = vertexBuffer[0]; // Vertex position VBO + model.mesh.vboId[1] = vertexBuffer[1]; // Texcoords VBO + model.mesh.vboId[2] = vertexBuffer[2]; // Normals VBO - model.vboId[0] = vertexBuffer[0]; // Vertex position VBO - model.vboId[1] = vertexBuffer[1]; // Texcoords VBO - model.vboId[2] = vertexBuffer[2]; // Normals VBO - if (vaoSupported) { if (vaoModel > 0) { - model.vaoId = vaoModel; + model.mesh.vaoId = vaoModel; TraceLog(INFO, "[VAO ID %i] Model uploaded successfully to VRAM (GPU)", vaoModel); } else TraceLog(WARNING, "Model could not be uploaded to VRAM (GPU)"); } else { - TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i] Model uploaded successfully to VRAM (GPU)", model.vboId[0], model.vboId[1], model.vboId[2]); + TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i] Model uploaded successfully to VRAM (GPU)", model.mesh.vboId[0], model.mesh.vboId[1], model.mesh.vboId[2]); } #endif return model; } -// Convert image data to OpenGL texture (returns OpenGL valid Id) -// NOTE: Expected compressed image data and POT image -unsigned int rlglLoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compFormat) + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Load a shader (vertex shader + fragment shader) from text data +unsigned int rlglLoadShaderFromText(char *vShaderStr, char *fShaderStr) { - GLuint id; + unsigned int program; + GLuint vertexShader; + GLuint fragmentShader; -#if defined(GRAPHICS_API_OPENGL_11) - id = 0; - TraceLog(WARNING, "GPU compressed textures not supported on OpenGL 1.1"); + vertexShader = glCreateShader(GL_VERTEX_SHADER); + fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); -#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - TraceLog(DEBUG, "Compressed texture width: %i", width); - TraceLog(DEBUG, "Compressed texture height: %i", height); - TraceLog(DEBUG, "Compressed texture mipmap levels: %i", mipmapCount); - TraceLog(DEBUG, "Compressed texture format: 0x%x", compFormat); + const char *pvs = vShaderStr; + const char *pfs = fShaderStr; - if (compFormat == 0) + glShaderSource(vertexShader, 1, &pvs, NULL); + glShaderSource(fragmentShader, 1, &pfs, NULL); + + GLint success = 0; + + glCompileShader(vertexShader); + + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); + + if (success != GL_TRUE) { - id = 0; - TraceLog(WARNING, "Texture compressed format not recognized", id); + TraceLog(WARNING, "[VSHDR ID %i] Failed to compile vertex shader...", vertexShader); + + int maxLength = 0; + int length; + + glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &maxLength); + + char log[maxLength]; + + glGetShaderInfoLog(vertexShader, maxLength, &length, log); + + TraceLog(INFO, "%s", log); } - else + else TraceLog(INFO, "[VSHDR ID %i] Vertex shader compiled successfully", vertexShader); + + glCompileShader(fragmentShader); + + glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); + + if (success != GL_TRUE) { - glGenTextures(1, &id); + TraceLog(WARNING, "[FSHDR ID %i] Failed to compile fragment shader...", fragmentShader); - // Bind the texture - glBindTexture(GL_TEXTURE_2D, id); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + int maxLength = 0; + int length; - // Set texture parameters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &maxLength); - // If mipmaps are being used, we configure mag-min filters accordingly - if (mipmapCount > 1) - { - // Trilinear filtering with mipmaps - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate use of mipmaps (must be available) - } - else - { - // Not using mipmappings - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR - } + char log[maxLength]; - int blockSize = 0; - int offset = 0; + glGetShaderInfoLog(fragmentShader, maxLength, &length, log); - if (compFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) blockSize = 8; - else blockSize = 16; - - // Load the mipmaps - for (int level = 0; level < mipmapCount && (width || height); level++) - { - unsigned int size = 0; - - // NOTE: size specifies the number of bytes of image data (S3TC/DXTC) - if (compFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) size = ((width + 3)/4)*((height + 3)/4)*blockSize; // S3TC/DXTC -#if defined(GRAPHICS_API_OPENGL_ES2) - else if (compFormat == GL_ETC1_RGB8_OES) size = 8*((width + 3) >> 2)*((height + 3) >> 2); // ETC1 -#endif - glCompressedTexImage2D(GL_TEXTURE_2D, level, compFormat, width, height, 0, size, data + offset); - - offset += size; - width /= 2; - height /= 2; - - // Security check for NPOT textures - if (width < 1) width = 1; - if (height < 1) height = 1; - } + TraceLog(INFO, "%s", log); } -#endif + else TraceLog(INFO, "[FSHDR ID %i] Fragment shader compiled successfully", fragmentShader); - return id; + program = glCreateProgram(); + + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TraceLog(WARNING, "[SHDR ID %i] Failed to link shader program...", program); + + int maxLength = 0; + int length; + + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + char log[maxLength]; + + glGetProgramInfoLog(program, maxLength, &length, log); + + TraceLog(INFO, "%s", log); + + glDeleteProgram(program); + + program = 0; + } + else TraceLog(INFO, "[SHDR ID %i] Shader program loaded successfully", program); + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + return program; } +#endif // Read screen pixel data (color buffer) unsigned char *rlglReadScreenPixels(int width, int height) @@ -1533,18 +2039,216 @@ unsigned char *rlglReadScreenPixels(int width, int height) return imgData; // NOTE: image data should be freed } -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Load a shader (vertex shader + fragment shader) from files +Shader rlglLoadShader(char *vsFileName, char *fsFileName) +{ + Shader shader; -void PrintProjectionMatrix() +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Shaders loading from external text file + char *vShaderStr = TextFileRead(vsFileName); + char *fShaderStr = TextFileRead(fsFileName); + + shader.id = rlglLoadShaderFromText(vShaderStr, fShaderStr); + + if (shader.id != 0) TraceLog(INFO, "[SHDR ID %i] Custom shader loaded successfully", shader.id); + else TraceLog(WARNING, "[SHDR ID %i] Custom shader could not be loaded", shader.id); + + // Shader strings must be freed + free(vShaderStr); + free(fShaderStr); + + // Set shader textures ids (all 0 by default) + shader.texDiffuseId = 0; + shader.texNormalId = 0; + shader.texSpecularId = 0; + + // Get handles to GLSL input attibute locations + //------------------------------------------------------------------- + shader.vertexLoc = glGetAttribLocation(shader.id, "vertexPosition"); + shader.texcoordLoc = glGetAttribLocation(shader.id, "vertexTexCoord"); + shader.normalLoc = glGetAttribLocation(shader.id, "vertexNormal"); + // NOTE: custom shader does not use colorLoc + shader.colorLoc = -1; + + // Get handles to GLSL uniform locations (vertex shader) + shader.modelviewLoc = glGetUniformLocation(shader.id, "modelviewMatrix"); + shader.projectionLoc = glGetUniformLocation(shader.id, "projectionMatrix"); + + // Get handles to GLSL uniform locations (fragment shader) + shader.tintColorLoc = glGetUniformLocation(shader.id, "tintColor"); + shader.mapDiffuseLoc = glGetUniformLocation(shader.id, "texture0"); + shader.mapNormalLoc = -1; // It can be set later + shader.mapSpecularLoc = -1; // It can be set later + //-------------------------------------------------------------------- +#endif + + return shader; +} + +// Link shader to model +void rlglSetModelShader(Model *model, Shader shader) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + model->shader = shader; + + if (vaoSupported) glBindVertexArray(model->mesh.vaoId); + + // Enable vertex attributes: position + glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[0]); + glEnableVertexAttribArray(shader.vertexLoc); + glVertexAttribPointer(shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + + // Enable vertex attributes: texcoords + glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[1]); + glEnableVertexAttribArray(shader.texcoordLoc); + glVertexAttribPointer(shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + + // Enable vertex attributes: normals + glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[2]); + glEnableVertexAttribArray(shader.normalLoc); + glVertexAttribPointer(shader.normalLoc, 3, GL_FLOAT, 0, 0, 0); + + if (vaoSupported) glBindVertexArray(0); // Unbind VAO + + //if (model->texture.id > 0) model->shader.texDiffuseId = model->texture.id; +#endif +} + +// Set custom shader to be used on batch draw +void rlglSetCustomShader(Shader shader) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (currentShader.id != shader.id) + { + rlglDraw(); + + currentShader = shader; +/* + if (vaoSupported) glBindVertexArray(vaoQuads); + + // Enable vertex attributes: position + glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]); + glEnableVertexAttribArray(currentShader.vertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + + // Enable vertex attributes: texcoords + glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]); + glEnableVertexAttribArray(currentShader.texcoordLoc); + glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + + // Enable vertex attributes: colors + glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); + glEnableVertexAttribArray(currentShader.colorLoc); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + if (vaoSupported) glBindVertexArray(0); // Unbind VAO +*/ + } +#endif +} + +// Set default shader to be used on batch draw +void rlglSetDefaultShader(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlglSetCustomShader(defaultShader); + rlglSetPostproShader(defaultShader); +#endif +} + +int GetShaderLocation(Shader shader, const char *uniformName) +{ + int location = glGetUniformLocation(shader.id, uniformName); + + if (location == -1) TraceLog(WARNING, "[SHDR ID %i] Shader location for %s could not be found", shader.id, uniformName); + + return location; +} + +void SetShaderValue(Shader shader, int uniformLoc, float *value, int size) +{ + glUseProgram(shader.id); + + if (size == 1) glUniform1fv(uniformLoc, 1, value); // Shader uniform type: float + else if (size == 2) glUniform2fv(uniformLoc, 1, value); // Shader uniform type: vec2 + else if (size == 3) glUniform3fv(uniformLoc, 1, value); // Shader uniform type: vec3 + else if (size == 4) glUniform4fv(uniformLoc, 1, value); // Shader uniform type: vec4 + else TraceLog(WARNING, "Shader value float array size not recognized"); + + glUseProgram(0); +} + +void SetShaderMapDiffuse(Shader *shader, Texture2D texture) +{ + shader->texDiffuseId = texture.id; + + glUseProgram(shader->id); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, shader->texDiffuseId); + + glUniform1i(shader->mapDiffuseLoc, 0); // Texture fits in active texture unit 0 + + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glUseProgram(0); +} + +void SetShaderMapNormal(Shader *shader, const char *uniformName, Texture2D texture) +{ + shader->mapNormalLoc = glGetUniformLocation(shader->id, uniformName); + + if (shader->mapNormalLoc == -1) TraceLog(WARNING, "[SHDR ID %i] Shader location for %s could not be found", shader->id, uniformName); + else + { + shader->texNormalId = texture.id; + + glUseProgram(shader->id); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, shader->texNormalId); + + glUniform1i(shader->mapNormalLoc, 1); // Texture fits in active texture unit 1 + + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glUseProgram(0); + } +} + +void SetShaderMapSpecular(Shader *shader, const char *uniformName, Texture2D texture) +{ + shader->mapSpecularLoc = glGetUniformLocation(shader->id, uniformName); + + if (shader->mapSpecularLoc == -1) TraceLog(WARNING, "[SHDR ID %i] Shader location for %s could not be found", shader->id, uniformName); + else + { + shader->texSpecularId = texture.id; + + glUseProgram(shader->id); + + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, shader->texSpecularId); + + glUniform1i(shader->mapSpecularLoc, 2); // Texture fits in active texture unit 0 + + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glUseProgram(0); + } +} + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +void PrintProjectionMatrix(void) { PrintMatrix(projection); } -void PrintModelviewMatrix() +void PrintModelviewMatrix(void) { PrintMatrix(modelview); } - #endif //---------------------------------------------------------------------------------- @@ -1552,257 +2256,206 @@ void PrintModelviewMatrix() //---------------------------------------------------------------------------------- #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Convert image data to OpenGL texture (returns OpenGL valid Id) +// NOTE: Expected compressed image data and POT image +static void LoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compressedFormat) +{ + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + int blockSize = 0; // Bytes every block + int offset = 0; + + if ((compressedFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT) || + (compressedFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) || +#if defined(GRAPHICS_API_OPENGL_ES2) + (compressedFormat == GL_ETC1_RGB8_OES) || +#endif + (compressedFormat == GL_COMPRESSED_RGB8_ETC2)) blockSize = 8; + else blockSize = 16; + + // Load the mipmap levels + for (int level = 0; level < mipmapCount && (width || height); level++) + { + unsigned int size = 0; + + size = ((width + 3)/4)*((height + 3)/4)*blockSize; + + glCompressedTexImage2D(GL_TEXTURE_2D, level, compressedFormat, width, height, 0, size, data + offset); + + offset += size; + width /= 2; + height /= 2; + + // Security check for NPOT textures + if (width < 1) width = 1; + if (height < 1) height = 1; + } +} // Load Shader (Vertex and Fragment) // NOTE: This shader program is used only for batch buffers (lines, triangles, quads) -static GLuint LoadDefaultShader(void) +static Shader LoadDefaultShader(void) { + Shader shader; + // NOTE: Shaders are written using GLSL 110 (desktop), that is equivalent to GLSL 100 on ES2 + // NOTE: Detected an error on ATI cards if defined #version 110 while OpenGL 3.3+ + // Just defined #version 330 despite shader is #version 110 // Vertex shader directly defined, no external file required #if defined(GRAPHICS_API_OPENGL_33) - char vShaderStr[] = " #version 110 \n" // NOTE: Equivalent to version 100 on ES2 + char vShaderStr[] = " #version 330 \n" + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 tintColor; \n" #elif defined(GRAPHICS_API_OPENGL_ES2) - char vShaderStr[] = " #version 100 \n" // NOTE: Must be defined this way! 110 doesn't work! -#endif - "uniform mat4 projectionMatrix; \n" - "uniform mat4 modelviewMatrix; \n" + char vShaderStr[] = " #version 100 \n" "attribute vec3 vertexPosition; \n" "attribute vec2 vertexTexCoord; \n" "attribute vec4 vertexColor; \n" "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" + "varying vec4 tintColor; \n" +#endif + "uniform mat4 projectionMatrix; \n" + "uniform mat4 modelviewMatrix; \n" "void main() \n" "{ \n" " fragTexCoord = vertexTexCoord; \n" - " fragColor = vertexColor; \n" - " gl_Position = projectionMatrix * modelviewMatrix * vec4(vertexPosition, 1.0); \n" + " tintColor = vertexColor; \n" + " gl_Position = projectionMatrix*modelviewMatrix*vec4(vertexPosition, 1.0); \n" "} \n"; // Fragment shader directly defined, no external file required #if defined(GRAPHICS_API_OPENGL_33) - char fShaderStr[] = " #version 110 \n" // NOTE: Equivalent to version 100 on ES2 + char fShaderStr[] = " #version 330 \n" + "in vec2 fragTexCoord; \n" + "in vec4 tintColor; \n" #elif defined(GRAPHICS_API_OPENGL_ES2) - char fShaderStr[] = " #version 100 \n" // NOTE: Must be defined this way! 110 doesn't work! - "precision mediump float; \n" // WebGL, required for emscripten + char fShaderStr[] = " #version 100 \n" + "precision mediump float; \n" // precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" + "varying vec4 tintColor; \n" #endif "uniform sampler2D texture0; \n" - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" "void main() \n" "{ \n" - " gl_FragColor = texture2D(texture0, fragTexCoord) * fragColor; \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" // NOTE: texture2D() is deprecated on OpenGL 3.3 and ES 3.0, use texture() instead + " gl_FragColor = texelColor*tintColor; \n" "} \n"; - GLuint program; - GLuint vertexShader; - GLuint fragmentShader; + shader.id = rlglLoadShaderFromText(vShaderStr, fShaderStr); - vertexShader = glCreateShader(GL_VERTEX_SHADER); - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + if (shader.id != 0) TraceLog(INFO, "[SHDR ID %i] Default shader loaded successfully", shader.id); + else TraceLog(WARNING, "[SHDR ID %i] Default shader could not be loaded", shader.id); - const char *pvs = vShaderStr; - const char *pfs = fShaderStr; + // Get handles to GLSL input attibute locations + //------------------------------------------------------------------- + shader.vertexLoc = glGetAttribLocation(shader.id, "vertexPosition"); + shader.texcoordLoc = glGetAttribLocation(shader.id, "vertexTexCoord"); + shader.colorLoc = glGetAttribLocation(shader.id, "vertexColor"); + // NOTE: default shader does not use normalLoc + shader.normalLoc = -1; - glShaderSource(vertexShader, 1, &pvs, NULL); - glShaderSource(fragmentShader, 1, &pfs, NULL); + // Get handles to GLSL uniform locations (vertex shader) + shader.modelviewLoc = glGetUniformLocation(shader.id, "modelviewMatrix"); + shader.projectionLoc = glGetUniformLocation(shader.id, "projectionMatrix"); - GLint success = 0; + // Get handles to GLSL uniform locations (fragment shader) + shader.tintColorLoc = -1; + shader.mapDiffuseLoc = glGetUniformLocation(shader.id, "texture0"); + shader.mapNormalLoc = -1; // It can be set later + shader.mapSpecularLoc = -1; // It can be set later + //-------------------------------------------------------------------- - glCompileShader(vertexShader); - - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); - - if (success != GL_TRUE) TraceLog(WARNING, "[VSHDR ID %i] Failed to compile default vertex shader...", vertexShader); - else TraceLog(INFO, "[VSHDR ID %i] Default vertex shader compiled successfully", vertexShader); - - glCompileShader(fragmentShader); - - glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); - - if (success != GL_TRUE) TraceLog(WARNING, "[FSHDR ID %i] Failed to compile default fragment shader...", fragmentShader); - else TraceLog(INFO, "[FSHDR ID %i] Default fragment shader compiled successfully", fragmentShader); - - program = glCreateProgram(); - - glAttachShader(program, vertexShader); - glAttachShader(program, fragmentShader); - - glLinkProgram(program); - - glGetProgramiv(program, GL_LINK_STATUS, &success); - - if (success == GL_FALSE) - { - int maxLength; - int length; - - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); - - char log[maxLength]; - - glGetProgramInfoLog(program, maxLength, &length, log); - - TraceLog(INFO, "Shader program fail log: %s", log); - } - else TraceLog(INFO, "[SHDR ID %i] Default shader program loaded successfully", program); - - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); - - return program; + return shader; } // Load Simple Shader (Vertex and Fragment) // NOTE: This shader program is used to render models -static GLuint LoadSimpleShader(void) +static Shader LoadSimpleShader(void) { + Shader shader; + // NOTE: Shaders are written using GLSL 110 (desktop), that is equivalent to GLSL 100 on ES2 + // NOTE: Detected an error on ATI cards if defined #version 110 while OpenGL 3.3+ + // Just defined #version 330 despite shader is #version 110 // Vertex shader directly defined, no external file required #if defined(GRAPHICS_API_OPENGL_33) - char vShaderStr[] = " #version 110 \n" // NOTE: Equivalent to version 100 on ES2 + char vShaderStr[] = " #version 330 \n" + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec3 vertexNormal; \n" + "out vec2 fragTexCoord; \n" #elif defined(GRAPHICS_API_OPENGL_ES2) - char vShaderStr[] = " #version 100 \n" // NOTE: Must be defined this way! 110 doesn't work! -#endif - "uniform mat4 projectionMatrix; \n" - "uniform mat4 modelviewMatrix; \n" + char vShaderStr[] = " #version 100 \n" "attribute vec3 vertexPosition; \n" "attribute vec2 vertexTexCoord; \n" "attribute vec3 vertexNormal; \n" "varying vec2 fragTexCoord; \n" +#endif + "uniform mat4 projectionMatrix; \n" + "uniform mat4 modelviewMatrix; \n" "void main() \n" "{ \n" " fragTexCoord = vertexTexCoord; \n" - " gl_Position = projectionMatrix * modelviewMatrix * vec4(vertexPosition, 1.0); \n" + " gl_Position = projectionMatrix*modelviewMatrix*vec4(vertexPosition, 1.0); \n" "} \n"; // Fragment shader directly defined, no external file required #if defined(GRAPHICS_API_OPENGL_33) - char fShaderStr[] = " #version 110 \n" // NOTE: Equivalent to version 100 on ES2 + char fShaderStr[] = " #version 330 \n" + "in vec2 fragTexCoord; \n" #elif defined(GRAPHICS_API_OPENGL_ES2) - char fShaderStr[] = " #version 100 \n" // NOTE: Must be defined this way! 110 doesn't work! - "precision mediump float; \n" // WebGL, required for emscripten + char fShaderStr[] = " #version 100 \n" + "precision mediump float; \n" // precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" #endif "uniform sampler2D texture0; \n" - "varying vec2 fragTexCoord; \n" - "uniform vec4 fragColor; \n" + "uniform vec4 tintColor; \n" "void main() \n" "{ \n" - " gl_FragColor = texture2D(texture0, fragTexCoord) * fragColor; \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" // NOTE: texture2D() is deprecated on OpenGL 3.3 and ES 3.0, use texture() instead + " gl_FragColor = texelColor*tintColor; \n" "} \n"; - GLuint program; - GLuint vertexShader; - GLuint fragmentShader; + shader.id = rlglLoadShaderFromText(vShaderStr, fShaderStr); - vertexShader = glCreateShader(GL_VERTEX_SHADER); - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + if (shader.id != 0) TraceLog(INFO, "[SHDR ID %i] Simple shader loaded successfully", shader.id); + else TraceLog(WARNING, "[SHDR ID %i] Simple shader could not be loaded", shader.id); - const char *pvs = vShaderStr; - const char *pfs = fShaderStr; + // Get handles to GLSL input attibute locations + //------------------------------------------------------------------- + shader.vertexLoc = glGetAttribLocation(shader.id, "vertexPosition"); + shader.texcoordLoc = glGetAttribLocation(shader.id, "vertexTexCoord"); + shader.normalLoc = glGetAttribLocation(shader.id, "vertexNormal"); + // NOTE: simple shader does not use colorLoc + shader.colorLoc = -1; - glShaderSource(vertexShader, 1, &pvs, NULL); - glShaderSource(fragmentShader, 1, &pfs, NULL); + // Get handles to GLSL uniform locations (vertex shader) + shader.modelviewLoc = glGetUniformLocation(shader.id, "modelviewMatrix"); + shader.projectionLoc = glGetUniformLocation(shader.id, "projectionMatrix"); - GLint success = 0; + // Get handles to GLSL uniform locations (fragment shader) + shader.tintColorLoc = glGetUniformLocation(shader.id, "tintColor"); + shader.mapDiffuseLoc = glGetUniformLocation(shader.id, "texture0"); + shader.mapNormalLoc = -1; // It can be set later + shader.mapSpecularLoc = -1; // It can be set later + //-------------------------------------------------------------------- - glCompileShader(vertexShader); - - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); - - if (success != GL_TRUE) TraceLog(WARNING, "[VSHDR ID %i] Failed to compile simple vertex shader...", vertexShader); - else TraceLog(INFO, "[VSHDR ID %i] Simple vertex shader compiled successfully", vertexShader); - - glCompileShader(fragmentShader); - - glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); - - if (success != GL_TRUE) TraceLog(WARNING, "[FSHDR ID %i] Failed to compile simple fragment shader...", fragmentShader); - else TraceLog(INFO, "[FSHDR ID %i] Simple fragment shader compiled successfully", fragmentShader); - - program = glCreateProgram(); - - glAttachShader(program, vertexShader); - glAttachShader(program, fragmentShader); - - glLinkProgram(program); - - glGetProgramiv(program, GL_LINK_STATUS, &success); - - if (success == GL_FALSE) - { - int maxLength; - int length; - - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); - - char log[maxLength]; - - glGetProgramInfoLog(program, maxLength, &length, log); - - TraceLog(INFO, "Shader program fail log: %s", log); - } - else TraceLog(INFO, "[SHDR ID %i] Simple shader program loaded successfully", program); - - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); - - return program; + return shader; } -// Load shaders from text files -static GLuint LoadCustomShader(char *vertexFileName, char *fragmentFileName) -{ - // Shaders loading from external text file - char *vShaderStr = TextFileRead(vertexFileName); - char *fShaderStr = TextFileRead(fragmentFileName); - - GLuint program; - GLuint vertexShader; - GLuint fragmentShader; - - vertexShader = glCreateShader(GL_VERTEX_SHADER); - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - - const char *pvs = vShaderStr; - const char *pfs = fShaderStr; - - glShaderSource(vertexShader, 1, &pvs, NULL); - glShaderSource(fragmentShader, 1, &pfs, NULL); - - glCompileShader(vertexShader); - glCompileShader(fragmentShader); - - TraceLog(INFO, "[VSHDR ID %i] Vertex shader compiled successfully", vertexShader); - TraceLog(INFO, "[FSHDR ID %i] Fragment shader compiled successfully", fragmentShader); - - program = glCreateProgram(); - - glAttachShader(program, vertexShader); - glAttachShader(program, fragmentShader); - - glLinkProgram(program); - - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); - - free(vShaderStr); - free(fShaderStr); - - TraceLog(INFO, "[SHDR ID %i] Shader program loaded successfully", program); - - return program; -} - -// Read shader text file +// Read text file // NOTE: text chars array should be freed manually static char *TextFileRead(char *fileName) { FILE *textFile; char *text = NULL; - int count=0; + int count = 0; if (fileName != NULL) { @@ -1816,7 +2469,7 @@ static char *TextFileRead(char *fileName) if (count > 0) { - text = (char *)malloc(sizeof(char) * (count+1)); + text = (char *)malloc(sizeof(char) * (count + 1)); count = fread(text, sizeof(char), count, textFile); text[count] = '\0'; } @@ -1825,7 +2478,7 @@ static char *TextFileRead(char *fileName) } else TraceLog(WARNING, "[%s] Text file could not be opened", fileName); } - + return text; } @@ -1890,6 +2543,7 @@ static void InitializeBuffers(void) } // Initialize Vertex Array Objects (Contain VBO) +// NOTE: lines, triangles and quads buffers use currentShader static void InitializeBuffersGPU(void) { if (vaoSupported) @@ -1905,14 +2559,14 @@ static void InitializeBuffersGPU(void) // Lines - Vertex positions buffer binding and attributes enable glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*2*MAX_LINES_BATCH, lines.vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultVertexLoc); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); // Lines - colors buffer glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*2*MAX_LINES_BATCH, lines.colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultColorLoc); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Lines VAO initialized successfully", vaoLines); else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Lines VBOs initialized successfully", linesBuffer[0], linesBuffer[1]); @@ -1924,20 +2578,20 @@ static void InitializeBuffersGPU(void) glGenVertexArrays(1, &vaoTriangles); glBindVertexArray(vaoTriangles); } - + // Create buffers for our vertex data glGenBuffers(2, trianglesBuffer); // Enable vertex attributes glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*3*MAX_TRIANGLES_BATCH, triangles.vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultVertexLoc); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*3*MAX_TRIANGLES_BATCH, triangles.colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultColorLoc); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Triangles VAO initialized successfully", vaoTriangles); else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Triangles VBOs initialized successfully", trianglesBuffer[0], trianglesBuffer[1]); @@ -1949,25 +2603,25 @@ static void InitializeBuffersGPU(void) glGenVertexArrays(1, &vaoQuads); glBindVertexArray(vaoQuads); } - + // Create buffers for our vertex data glGenBuffers(4, quadsBuffer); // Enable vertex attributes glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_QUADS_BATCH, quads.vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultVertexLoc); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*MAX_QUADS_BATCH, quads.texcoords, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultTexcoordLoc); - glVertexAttribPointer(defaultTexcoordLoc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.texcoordLoc); + glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*4*MAX_QUADS_BATCH, quads.colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultColorLoc); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); // Fill index buffer glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadsBuffer[3]); @@ -1985,15 +2639,15 @@ static void InitializeBuffersGPU(void) } // Update VBOs with vertex array data -// TODO: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) -// TODO: If no data changed on the CPU arrays --> No need to update GPU arrays every frame! +// NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) +// TODO: If no data changed on the CPU arrays --> No need to update GPU arrays static void UpdateBuffers(void) { if (lines.vCounter > 0) { // Activate Lines VAO if (vaoSupported) glBindVertexArray(vaoLines); - + // Lines - vertex positions buffer glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]); //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*2*MAX_LINES_BATCH, lines.vertices, GL_DYNAMIC_DRAW); @@ -2010,7 +2664,7 @@ static void UpdateBuffers(void) { // Activate Triangles VAO if (vaoSupported) glBindVertexArray(vaoTriangles); - + // Triangles - vertex positions buffer glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]); //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*3*MAX_TRIANGLES_BATCH, triangles.vertices, GL_DYNAMIC_DRAW); @@ -2053,11 +2707,9 @@ static void UpdateBuffers(void) // Unbind the current VAO if (vaoSupported) glBindVertexArray(0); } - #endif //defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) #if defined(GRAPHICS_API_OPENGL_11) - // Mipmaps data is generated after image data static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight) { @@ -2182,7 +2834,6 @@ static pixel *GenNextMipmap(pixel *srcData, int srcWidth, int srcHeight) return mipmap; } - #endif #if defined(RLGL_STANDALONE) @@ -2212,4 +2863,54 @@ void TraceLog(int msgType, const char *text, ...) if (msgType == ERROR) exit(1); } -#endif \ No newline at end of file +#endif + +static char **StringSplit(char *baseString, const char delimiter, int *numExt) +{ + char **result = 0; + int count = 0; + char *tmp = baseString; + char *lastComma = 0; + char delim[2]; + + delim[0] = delimiter; + delim[1] = 0; + + // Count how many elements will be extracted + while (*tmp) + { + if (delimiter == *tmp) + { + count++; + lastComma = tmp; + } + + tmp++; + } + + // Add space for trailing token + count += lastComma < (baseString + strlen(baseString) - 1); + + // Add space for terminating null string + count++; + + result = malloc(sizeof(char *)*count); + + if (result) + { + int idx = 0; + char *token = strtok(baseString, delim); + + while (token) + { + *(result + idx++) = token; + token = strtok(0, delim); + } + + *(result + idx) = 0; + } + + *numExt = (count - 1); + + return result; +} \ No newline at end of file diff --git a/src/rlgl.h b/src/rlgl.h index b42b388b0..41c13ef73 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -32,7 +32,7 @@ //#define RLGL_STANDALONE // NOTE: To use rlgl as standalone lib, just uncomment this line #ifndef RLGL_STANDALONE - #include "raylib.h" // Required for typedef: Model + #include "raylib.h" // Required for typedef(s): Model, Shader, Texture2D #include "utils.h" // Required for function TraceLog() #endif @@ -89,21 +89,69 @@ typedef enum { RL_LINES, RL_TRIANGLES, RL_QUADS } DrawMode; typedef enum { OPENGL_11 = 1, OPENGL_33, OPENGL_ES_20 } GlVersion; #ifdef RLGL_STANDALONE - typedef struct { + // Texture formats (support depends on OpenGL version) + typedef enum { + UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + UNCOMPRESSED_R5G6B5, // 16 bpp + UNCOMPRESSED_R8G8B8, // 24 bpp + UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + UNCOMPRESSED_R8G8B8A8, // 32 bpp + COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + COMPRESSED_DXT3_RGBA, // 8 bpp + COMPRESSED_DXT5_RGBA, // 8 bpp + COMPRESSED_ETC1_RGB, // 4 bpp + COMPRESSED_ETC2_RGB, // 4 bpp + COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + COMPRESSED_PVRT_RGB, // 4 bpp + COMPRESSED_PVRT_RGBA, // 4 bpp + /*COMPRESSED_ASTC_RGBA_4x4*/ // 8 bpp + } TextureFormat; + + // VertexData type + // NOTE: If using OpenGL 1.1, data loaded in CPU; if OpenGL 3.3+ data loaded in GPU (vaoId) + typedef struct VertexData { int vertexCount; float *vertices; // 3 components per vertex float *texcoords; // 2 components per vertex float *normals; // 3 components per vertex unsigned char *colors; - } VertexData; - - typedef struct Model { - VertexData mesh; unsigned int vaoId; unsigned int vboId[4]; - unsigned int textureId; - //Matrix transform; + } VertexData; + + // Shader type + typedef struct Shader { + unsigned int id; // Shader program id + + // Variable attributes + unsigned int vertexLoc; // Vertex attribute location point (vertex shader) + unsigned int texcoordLoc; // Texcoord attribute location point (vertex shader) + unsigned int normalLoc; // Normal attribute location point (vertex shader) + unsigned int colorLoc; // Color attibute location point (vertex shader) + + // Uniforms + unsigned int projectionLoc; // Projection matrix uniform location point (vertex shader) + unsigned int modelviewLoc; // ModeView matrix uniform location point (vertex shader) + unsigned int textureLoc; // Texture uniform location point (fragment shader) + unsigned int tintColorLoc; // Color uniform location point (fragment shader) + } Shader; + + // 3d Model type + typedef struct Model { + VertexData mesh; + Matrix transform; + Texture2D texture; + Shader shader; } Model; + + // Texture2D type + typedef struct Texture2D { + unsigned int id; // Texture id + int width; + int height; + } Texture2D; #endif #ifdef __cplusplus @@ -145,11 +193,13 @@ void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) void rlEnableTexture(unsigned int id); // Enable texture usage void rlDisableTexture(void); // Disable texture usage void rlDeleteTextures(unsigned int id); // Delete OpenGL texture from GPU +void rlDeleteShader(unsigned int id); // Delete OpenGL shader program from GPU void rlDeleteVertexArrays(unsigned int id); // Unload vertex data (VAO) from GPU memory void rlDeleteBuffers(unsigned int id); // Unload vertex data (VBO) from GPU memory void rlClearColor(byte r, byte g, byte b, byte a); // Clear color buffer with color void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) int rlGetVersion(void); // Returns current OpenGL version +void rlEnableFBO(void); // Enable rendering to postprocessing FBO //------------------------------------------------------------------------------------ // Functions Declaration - rlgl functionality @@ -159,11 +209,20 @@ void rlglClose(void); // De-init rlgl void rlglDraw(void); // Draw VAO/VBO void rlglInitGraphics(int offsetX, int offsetY, int width, int height); // Initialize Graphics (OpenGL stuff) -unsigned int rlglLoadTexture(unsigned char *data, int width, int height, bool genMipmaps); // Load in GPU OpenGL texture -unsigned int rlglLoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int format); +unsigned int rlglLoadTexture(void *data, int width, int height, int textureFormat, int mipmapCount, bool genMipmaps); // Load in GPU OpenGL texture +Shader rlglLoadShader(char *vsFileName, char *fsFileName); // Load a shader (vertex shader + fragment shader) from files +unsigned int rlglLoadShaderFromText(char *vShaderStr, char *fShaderStr); // Load a shader from text data +void rlglInitPostpro(void); // Initialize postprocessing system +void rlglDrawPostpro(void); // Draw with postprocessing shader +void rlglSetPostproShader(Shader shader); // Set postprocessing shader +void rlglSetModelShader(Model *model, Shader shader); // Set shader for a model +void rlglSetCustomShader(Shader shader); // Set custom shader to be used on batch draw +void rlglSetDefaultShader(void); // Set default shader to be used on batch draw Model rlglLoadModel(VertexData mesh); // Upload vertex data into GPU and provided VAO/VBO ids -void rlglDrawModel(Model model, Vector3 position, Vector3 rotation, Vector3 scale, Color color, bool wires); +void rlglDrawModel(Model model, Vector3 position, float rotationAngle, Vector3 rotationAxis, Vector3 scale, Color color, bool wires); + +Vector3 rlglUnproject(Vector3 source, Matrix proj, Matrix view); // Get world coordinates from screen coordinates byte *rlglReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) diff --git a/src/shapes.c b/src/shapes.c index d872eacfe..6e6c9dd76 100644 --- a/src/shapes.c +++ b/src/shapes.c @@ -98,7 +98,7 @@ void DrawLineV(Vector2 startPos, Vector2 endPos, Color color) // Draw a color-filled circle void DrawCircle(int centerX, int centerY, float radius, Color color) { - DrawPoly((Vector2){centerX, centerY}, 360, radius, 0, color); + DrawPoly((Vector2){ centerX, centerY }, 36, radius, 0, color); } // Draw a gradient-filled circle @@ -106,14 +106,14 @@ void DrawCircle(int centerX, int centerY, float radius, Color color) void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2) { rlBegin(RL_TRIANGLES); - for (int i=0; i < 360; i += 2) + for (int i = 0; i < 360; i += 10) { rlColor4ub(color1.r, color1.g, color1.b, color1.a); rlVertex2i(centerX, centerY); rlColor4ub(color2.r, color2.g, color2.b, color2.a); - rlVertex2f(centerX + sin(DEG2RAD*i) * radius, centerY + cos(DEG2RAD*i) * radius); + rlVertex2f(centerX + sin(DEG2RAD*i)*radius, centerY + cos(DEG2RAD*i)*radius); rlColor4ub(color2.r, color2.g, color2.b, color2.a); - rlVertex2f(centerX + sin(DEG2RAD*(i+2)) * radius, centerY + cos(DEG2RAD*(i+2)) * radius); + rlVertex2f(centerX + sin(DEG2RAD*(i + 10)) * radius, centerY + cos(DEG2RAD*(i + 10))*radius); } rlEnd(); } @@ -122,12 +122,12 @@ void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Co void DrawCircleV(Vector2 center, float radius, Color color) { rlBegin(RL_TRIANGLES); - for (int i=0; i < 360; i += 2) + for (int i = 0; i < 360; i += 10) { rlColor4ub(color.r, color.g, color.b, color.a); rlVertex2i(center.x, center.y); - rlVertex2f(center.x + sin(DEG2RAD*i) * radius, center.y + cos(DEG2RAD*i) * radius); - rlVertex2f(center.x + sin(DEG2RAD*(i+2)) * radius, center.y + cos(DEG2RAD*(i+2)) * radius); + rlVertex2f(center.x + sin(DEG2RAD*i)*radius, center.y + cos(DEG2RAD*i)*radius); + rlVertex2f(center.x + sin(DEG2RAD*(i + 10)) * radius, center.y + cos(DEG2RAD*(i + 10)) * radius); } rlEnd(); } @@ -139,10 +139,10 @@ void DrawCircleLines(int centerX, int centerY, float radius, Color color) rlColor4ub(color.r, color.g, color.b, color.a); // NOTE: Circle outline is drawn pixel by pixel every degree (0 to 360) - for (int i=0; i < 360; i++) + for (int i = 0; i < 360; i += 10) { - rlVertex2f(centerX + sin(DEG2RAD*i) * radius, centerY + cos(DEG2RAD*i) * radius); - rlVertex2f(centerX + sin(DEG2RAD*(i+1)) * radius, centerY + cos(DEG2RAD*(i+1)) * radius); + rlVertex2f(centerX + sin(DEG2RAD*i)*radius, centerY + cos(DEG2RAD*i)*radius); + rlVertex2f(centerX + sin(DEG2RAD*(i + 10)) * radius, centerY + cos(DEG2RAD*(i + 10))*radius); } rlEnd(); } @@ -201,7 +201,7 @@ void DrawRectangleV(Vector2 position, Vector2 size, Color color) rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); - rlNormal3f(0.0f, 0.0f, 1.0f); // Normal Pointing Towards Viewer + rlNormal3f(0.0f, 0.0f, 1.0f); rlTexCoord2f(0.0f, 0.0f); rlVertex2f(position.x, position.y); @@ -275,13 +275,13 @@ void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color col rlRotatef(rotation, 0, 0, 1); rlBegin(RL_TRIANGLES); - for (int i=0; i < 360; i += 360/sides) + for (int i = 0; i < 360; i += 360/sides) { rlColor4ub(color.r, color.g, color.b, color.a); rlVertex2i(0, 0); - rlVertex2f(sin(DEG2RAD*i) * radius, cos(DEG2RAD*i) * radius); - rlVertex2f(sin(DEG2RAD*(i+360/sides)) * radius, cos(DEG2RAD*(i+360/sides)) * radius); + rlVertex2f(sin(DEG2RAD*i)*radius, cos(DEG2RAD*i)*radius); + rlVertex2f(sin(DEG2RAD*(i + 360/sides))*radius, cos(DEG2RAD*(i + 360/sides))*radius); } rlEnd(); rlPopMatrix(); @@ -299,8 +299,8 @@ void DrawPolyEx(Vector2 *points, int numPoints, Color color) for (int i = 0; i < numPoints - 2; i++) { rlVertex2f(points[i].x, points[i].y); - rlVertex2f(points[i+1].x, points[i+1].y); - rlVertex2f(points[i+2].x, points[i+2].y); + rlVertex2f(points[i + 1].x, points[i + 1].y); + rlVertex2f(points[i + 2].x, points[i + 2].y); } rlEnd(); } @@ -318,7 +318,7 @@ void DrawPolyExLines(Vector2 *points, int numPoints, Color color) for (int i = 0; i < numPoints - 1; i++) { rlVertex2f(points[i].x, points[i].y); - rlVertex2f(points[i+1].x, points[i+1].y); + rlVertex2f(points[i + 1].x, points[i + 1].y); } rlEnd(); } @@ -367,10 +367,10 @@ bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2) { bool collision = false; - int dx = abs((rec1.x + rec1.width / 2) - (rec2.x + rec2.width / 2)); - int dy = abs((rec1.y + rec1.height / 2) - (rec2.y + rec2.height / 2)); + int dx = abs((rec1.x + rec1.width/2) - (rec2.x + rec2.width/2)); + int dy = abs((rec1.y + rec1.height/2) - (rec2.y + rec2.height/2)); - if ((dx <= (rec1.width / 2 + rec2.width / 2)) && ((dy <= (rec1.height / 2 + rec2.height / 2)))) collision = true; + if ((dx <= (rec1.width/2 + rec2.width/2)) && ((dy <= (rec1.height/2 + rec2.height/2)))) collision = true; return collision; } @@ -395,10 +395,10 @@ bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) { bool collision = false; - float dx = abs((rec.x + rec.width / 2) - center.x); - float dy = abs((rec.y + rec.height / 2) - center.y); + float dx = fabs((rec.x + rec.width/2) - center.x); + float dy = fabs((rec.y + rec.height/2) - center.y); - if ((dx <= (rec.width / 2 + radius)) && (dy <= (rec.height / 2 + radius))) collision = true; + if ((dx <= (rec.width/2 + radius)) && (dy <= (rec.height/2 + radius))) collision = true; return collision; } diff --git a/src/simple150.frag b/src/simple150.frag deleted file mode 100644 index 74a727dde..000000000 --- a/src/simple150.frag +++ /dev/null @@ -1,14 +0,0 @@ -#version 150 - -uniform sampler2D texture0; - -in vec2 fragTexCoord; -in vec4 fragColor; - -out vec4 pixelColor; - -void main() -{ - // Output pixel color - pixelColor = texture(texture0, fragTexCoord) * fragColor; -} \ No newline at end of file diff --git a/src/simple150.vert b/src/simple150.vert deleted file mode 100644 index 8c23b7316..000000000 --- a/src/simple150.vert +++ /dev/null @@ -1,21 +0,0 @@ -#version 150 - -uniform mat4 projectionMatrix; -uniform mat4 modelviewMatrix; - -in vec3 vertexPosition; -in vec2 vertexTexCoord; -in vec4 vertexColor; - -out vec2 fragTexCoord; -out vec4 fragColor; - -void main() -{ - // Pass some variables to the fragment shader - fragTexCoord = vertexTexCoord; - fragColor = vertexColor; - - // Apply all matrix transformations to vertex - gl_Position = projectionMatrix * modelviewMatrix * vec4(vertexPosition, 1.0); -} \ No newline at end of file diff --git a/src/stb_image.h b/src/stb_image.h index 39cbb7ad6..1249c3039 100644 --- a/src/stb_image.h +++ b/src/stb_image.h @@ -1,4 +1,4 @@ -/* stb_image - v2.00b - public domain image loader - http://nothings.org/stb_image.h +/* stb_image - v2.05 - public domain image loader - http://nothings.org/stb_image.h no warranty implied; use at your own risk Do this: @@ -143,6 +143,13 @@ Latest revision history: + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) additional corruption checking + stbi_set_flip_vertically_on_load + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD progressive JPEG @@ -154,8 +161,6 @@ 1.47 (2014-12-14) 1/2/4-bit PNG support (both grayscale and paletted) optimize PNG fix bug in interlaced PNG with user-specified channel count - 1.46 (2014-08-26) fix broken tRNS chunk in non-paletted PNG - 1.45 (2014-08-16) workaround MSVC-ARM internal compiler error by wrapping malloc See end of file for full revision history. @@ -178,7 +183,7 @@ James "moose2000" Brown (iPhone PNG) Roy Eltham Ben "Disch" Wenger (io callbacks) Luke Graham Omar Cornut (1/2/4-bit PNG) Thomas Ruf - John Bartholomew + Nicolas Guillemot (vertical flip) John Bartholomew Ken Hamada Optimizations & bugfixes Cort Stratton Fabian "ryg" Giesen Blazej Dariusz Roszkowski @@ -191,6 +196,12 @@ Ronny Chevalier Michal Cichon Tero Hanninen + Sergio Gonzalez + Cass Everitt + Engin Manap + Martins Mozeiko + Joseph Thomson + Phil Jordan License: This software is in the public domain. Where that dedication is not @@ -371,6 +382,7 @@ License: // and only if iPhone convert-to-rgb processing is on). // + #define STBI_NO_HDR // RaySan: not required by raylib #define STBI_NO_SIMD // RaySan: issues when compiling with GCC 4.7.2 @@ -489,6 +501,8 @@ STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultipl // or just pass them through "as-is" STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); // ZLIB client - used by PNG, available for other purposes @@ -626,7 +640,38 @@ typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; #define STBI_FREE(p) free(p) #endif -#if !defined(STBI_NO_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)) +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && defined(STBI__X86_TARGET) #define STBI_SSE2 #include @@ -879,7 +924,14 @@ static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); #endif -static unsigned char *stbi_load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) { #ifndef STBI_NO_JPEG if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); @@ -919,6 +971,53 @@ static unsigned char *stbi_load_main(stbi__context *s, int *x, int *y, int *comp return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); } +static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result = stbi__load_main(s, x, y, comp, req_comp); + + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + stbi_uc temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } + + return result; +} + +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } +} + + #ifndef STBI_NO_STDIO static FILE *stbi__fopen(char const *filename, char const *mode) @@ -949,7 +1048,7 @@ STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req unsigned char *result; stbi__context s; stbi__start_file(&s,f); - result = stbi_load_main(&s,x,y,comp,req_comp); + result = stbi__load_flip(&s,x,y,comp,req_comp); if (result) { // need to 'unget' all the characters in the IO buffer fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); @@ -962,25 +1061,29 @@ STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, i { stbi__context s; stbi__start_mem(&s,buffer,len); - return stbi_load_main(&s,x,y,comp,req_comp); + return stbi__load_flip(&s,x,y,comp,req_comp); } STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi_load_main(&s,x,y,comp,req_comp); + return stbi__load_flip(&s,x,y,comp,req_comp); } #ifndef STBI_NO_LINEAR -static float *stbi_loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) { unsigned char *data; #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) - return stbi__hdr_load(s,x,y,comp,req_comp); + if (stbi__hdr_test(s)) { + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } #endif - data = stbi_load_main(s, x, y, comp, req_comp); + data = stbi__load_flip(s, x, y, comp, req_comp); if (data) return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); @@ -990,14 +1093,14 @@ STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, in { stbi__context s; stbi__start_mem(&s,buffer,len); - return stbi_loadf_main(&s,x,y,comp,req_comp); + return stbi__loadf_main(&s,x,y,comp,req_comp); } STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi_loadf_main(&s,x,y,comp,req_comp); + return stbi__loadf_main(&s,x,y,comp,req_comp); } #ifndef STBI_NO_STDIO @@ -1015,7 +1118,7 @@ STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_ { stbi__context s; stbi__start_file(&s,f); - return stbi_loadf_main(&s,x,y,comp,req_comp); + return stbi__loadf_main(&s,x,y,comp,req_comp); } #endif // !STBI_NO_STDIO @@ -1138,6 +1241,10 @@ stbi_inline static int stbi__at_eof(stbi__context *s) static void stbi__skip(stbi__context *s, int n) { + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } if (s->io.read) { int blen = (int) (s->img_buffer_end - s->img_buffer); if (blen < n) { @@ -1546,6 +1653,7 @@ stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n) sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; j->code_bits -= n; @@ -1730,15 +1838,12 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__ short *p = &data[stbi__jpeg_dezigzag[k]]; if (*p != 0) if (stbi__jpeg_get_bit(j)) - { - if ((*p & bit)==0) - { + if ((*p & bit)==0) { if (*p > 0) *p += bit; else *p -= bit; } - } } } else { k = j->spec_start; @@ -1754,8 +1859,11 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__ if (r) j->eob_run += stbi__jpeg_get_bits(j, r); r = 64; // force end of block - } else - r = 16; // r=15 is the code for 16 0s + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } } else { if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); // sign bit @@ -1767,27 +1875,21 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__ // advance by r while (k <= j->spec_end) { - short *p = &data[stbi__jpeg_dezigzag[k]]; + short *p = &data[stbi__jpeg_dezigzag[k++]]; if (*p != 0) { if (stbi__jpeg_get_bit(j)) - { - if ((*p & bit)==0) - { + if ((*p & bit)==0) { if (*p > 0) *p += bit; else *p -= bit; } - } - ++k; } else { if (r == 0) { - if (s) - data[stbi__jpeg_dezigzag[k++]] = s; + *p = (short) s; break; } --r; - ++k; } } } while (k <= j->spec_end); @@ -2207,7 +2309,7 @@ static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) // pass 1 dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 dct_trn16(row2, row3); - dct_trn16(row4, row5); + dct_trn16(row4, row5); dct_trn16(row6, row7); // pass 2 @@ -2434,7 +2536,6 @@ static int stbi__parse_entropy_coded_data(stbi__jpeg *z) for (x=0; x < z->img_comp[n].h; ++x) { int x2 = (i*z->img_comp[n].h + x); int y2 = (j*z->img_comp[n].v + y); - //int ha = z->img_comp[n].ha; // RaySan: Unused, commented to avoid warning short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) return 0; @@ -2701,6 +2802,10 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) static int stbi__decode_jpeg_image(stbi__jpeg *j) { int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } j->restart_interval = 0; if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; m = stbi__get_marker(j); @@ -3013,7 +3118,7 @@ static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc cons __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); - __m128i y_bias = _mm_set1_epi8((char) 128); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); __m128i xw = _mm_set1_epi16(255); // alpha channel for (; i+7 < count; i += 8) { @@ -3380,7 +3485,8 @@ static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) ++sizes[sizelist[i]]; sizes[0] = 0; for (i=1; i < 16; ++i) - STBI_ASSERT(sizes[i] <= (1 << i)); + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); code = 0; for (i=1; i < 16; ++i) { next_code[i] = code; @@ -3388,7 +3494,7 @@ static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) z->firstsymbol[i] = (stbi__uint16) k; code = (code + sizes[i]); if (sizes[i]) - if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt JPEG"); + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); z->maxcode[i] = code << (16-i); // preshift for inner loop code <<= 1; k += sizes[i]; @@ -3557,9 +3663,9 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) p = (stbi_uc *) (zout - dist); if (dist == 1) { // run of one byte; common in images. stbi_uc v = *p; - do *zout++ = v; while (--len); + if (len) { do *zout++ = v; while (--len); } } else { - do *zout++ = *p++; while (--len); + if (len) { do *zout++ = *p++; while (--len); } } } } @@ -3587,7 +3693,7 @@ static int stbi__compute_huffman_codes(stbi__zbuf *a) n = 0; while (n < hlit + hdist) { int c = stbi__zhuffman_decode(a, &z_codelength); - STBI_ASSERT(c >= 0 && c < 19); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); if (c < 16) lencodes[n++] = (stbi_uc) c; else if (c == 16) { @@ -4019,7 +4125,7 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r cur[i*2+0] = cur[i]; } } else { - assert(img_n == 3); + STBI_ASSERT(img_n == 3); for (i=x-1; i >= 0; --i) { cur[i*4+3] = 255; cur[i*4+2] = cur[i*3+2]; @@ -4284,6 +4390,7 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; if (ioff + c.length > idata_limit) { stbi_uc *p; if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; @@ -4643,7 +4750,7 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int } } else { for (i=0; i < (int) s->img_x; ++i) { - stbi__uint32 v = (stbi__uint32) (bpp == 16 ? stbi__get16le(s) : stbi__get32le(s)); + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); int a; out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); @@ -4800,7 +4907,7 @@ static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int *y = tga_height; if (comp) *comp = tga_comp; - tga_data = (unsigned char*)stbi__malloc( tga_width * tga_height * tga_comp ); + tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp ); if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); // skip to the data's starting position (offset usually = 0) @@ -5461,6 +5568,7 @@ static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) stbi__gif_lzw *p; lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; clear = 1 << lzw_cs; first = 1; codesize = lzw_cs + 1; @@ -6130,7 +6238,7 @@ static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) #ifndef STBI_NO_PSD if (stbi__psd_info(s, x, y, comp)) return 1; #endif - + #ifndef STBI_NO_PIC if (stbi__pic_info(s, x, y, comp)) return 1; #endif @@ -6192,6 +6300,13 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int /* revision history: + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) progressive JPEG (stb) @@ -6276,7 +6391,7 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int 1.21 fix use of 'stbi_uc' in header (reported by jon blow) 1.20 added support for Softimage PIC, by Tom Seddon 1.19 bug in interlaced PNG corruption check (found by ryg) - 1.18 2008-08-02 + 1.18 (2008-08-02) fix a threading bug (local mutable static) 1.17 support interlaced PNG 1.16 major bugfix - stbi__convert_format converted one too many pixels @@ -6321,5 +6436,6 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments 0.51 obey req_comp requests, 1-component jpegs return as 1-component, on 'test' only check type, not whether we support this variant - 0.50 first released version + 0.50 (2006-11-19) + first released version */ diff --git a/src/stb_image_write.h b/src/stb_image_write.h index da3f7e22e..104897075 100644 --- a/src/stb_image_write.h +++ b/src/stb_image_write.h @@ -1,16 +1,15 @@ -/* stb_image_write - v0.95 - public domain - http://nothings.org/stb/stb_image_write.h +/* stb_image_write - v0.98 - public domain - http://nothings.org/stb/stb_image_write.h writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010 no warranty implied; use at your own risk -Before including, + Before #including, - #define STB_IMAGE_WRITE_IMPLEMENTATION + #define STB_IMAGE_WRITE_IMPLEMENTATION -in the file that you want to have the implementation. - -Will probably not work correctly with strict-aliasing optimizations. + in the file that you want to have the implementation. + Will probably not work correctly with strict-aliasing optimizations. ABOUT: @@ -22,16 +21,24 @@ ABOUT: for source code compactness and simplicitly, not optimal image file size or run-time performance. +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can define STBIW_MEMMOVE() to replace memmove() + USAGE: - There are three functions, one for each image file format: + There are four functions, one for each image file format: int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const void *data); Each function returns 0 on failure and non-0 on success. - + The functions create an image file defined by the parameters. The image is a rectangle of pixels stored from left-to-right, top-to-bottom. Each pixel contains 'comp' channels of data stored interleaved with 8-bits @@ -44,13 +51,30 @@ USAGE: PNG creates output files with the same number of components as the input. The BMP format expands Y to RGB in the file format and does not output alpha. - + PNG supports writing rectangles of data even when the bytes storing rows of data are not consecutive in memory (e.g. sub-rectangles of a larger image), by supplying the stride between the beginning of adjacent rows. The other formats do not. (Thus you cannot write a native-format BMP through the BMP writer, both because it is in BGR order and because it may have padding at the end of the line.) + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + +CREDITS: + + PNG/BMP/TGA + Sean Barrett + HDR + Baldur Karlsson + TGA monochrome: + Jean-Sebastien Guay + misc enhancements: + Tim Kelsey + bugfixes: + github:Chribba */ #ifndef INCLUDE_STB_IMAGE_WRITE_H @@ -60,9 +84,10 @@ USAGE: extern "C" { #endif -extern int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); -extern int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); -extern int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +extern int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +extern int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +extern int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +extern int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); #ifdef __cplusplus } @@ -76,7 +101,30 @@ extern int stbi_write_tga(char const *filename, int w, int h, int comp, const vo #include #include #include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && defined(STBIW_REALLOC) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,sz) realloc(p,sz) +#define STBIW_FREE(p) free(p) +#endif +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT #include +#define STBIW_ASSERT(x) assert(x) +#endif typedef unsigned int stbiw_uint32; typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; @@ -95,7 +143,7 @@ static void writefv(FILE *f, const char *fmt, va_list v) b[2]=(unsigned char)(x>>16); b[3]=(unsigned char)(x>>24); fwrite(b,4,1,f); break; } default: - assert(0); + STBIW_ASSERT(0); return; } } @@ -108,7 +156,7 @@ static void write3(FILE *f, unsigned char a, unsigned char b, unsigned char c) fwrite(arr, 3, 1, f); } -static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad) +static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) { unsigned char bg[3] = { 255, 0, 255}, px[3]; stbiw_uint32 zero = 0; @@ -117,7 +165,7 @@ static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, if (y <= 0) return; - if (vdir < 0) + if (vdir < 0) j_end = -1, j = y-1; else j_end = y, j = 0; @@ -128,8 +176,12 @@ static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, if (write_alpha < 0) fwrite(&d[comp-1], 1, 1, f); switch (comp) { - case 1: - case 2: fwrite(d, 1, 1, f); + case 1: fwrite(d, 1, 1, f); + break; + case 2: if (expand_mono) + write3(f, d[0],d[0],d[0]); // monochrome bmp + else + fwrite(d, 1, 1, f); // monochrome TGA break; case 4: if (!write_alpha) { @@ -151,7 +203,7 @@ static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, } } -static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, const char *fmt, ...) +static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) { FILE *f; if (y < 0 || x < 0) return 0; @@ -161,7 +213,7 @@ static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, in va_start(v, fmt); writefv(f, fmt, v); va_end(v); - write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad); + write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad,expand_mono); fclose(f); } return f != NULL; @@ -170,7 +222,7 @@ static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, in int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) { int pad = (-x*3) & 3; - return outfile(filename,-1,-1,x,y,comp,(void *) data,0,pad, + return outfile(filename,-1,-1,x,y,comp,1,(void *) data,0,pad, "11 4 22 4" "4 44 22 444444", 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header @@ -181,10 +233,159 @@ int stbi_write_tga(char const *filename, int x, int y, int comp, const void *dat int has_alpha = (comp == 2 || comp == 4); int colorbytes = has_alpha ? comp-1 : comp; int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 - return outfile(filename, -1,-1, x, y, comp, (void *) data, has_alpha, 0, + return outfile(filename, -1,-1, x, y, comp, 0, (void *) data, has_alpha, 0, "111 221 2222 11", 0,0,format, 0,0,0, 0,0,x,y, (colorbytes+has_alpha)*8, has_alpha*8); } +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +void stbiw__write_run_data(FILE *f, int length, unsigned char databyte) +{ + unsigned char lengthbyte = (unsigned char) (length+128); + STBIW_ASSERT(length+128 <= 255); + fwrite(&lengthbyte, 1, 1, f); + fwrite(&databyte, 1, 1, f); +} + +void stbiw__write_dump_data(FILE *f, int length, unsigned char *data) +{ + unsigned char lengthbyte = (unsigned char )(length & 0xff); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + fwrite(&lengthbyte, 1, 1, f); + fwrite(data, length, 1, f); +} + +void stbiw__write_hdr_scanline(FILE *f, int width, int comp, unsigned char *scratch, const float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (comp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*comp + 2]; + linear[1] = scanline[x*comp + 1]; + linear[0] = scanline[x*comp + 0]; + break; + case 2: /* fallthrough */ + case 1: linear[0] = linear[1] = linear[2] = scanline[x*comp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + fwrite(rgbe, 4, 1, f); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(comp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*comp + 2]; + linear[1] = scanline[x*comp + 1]; + linear[0] = scanline[x*comp + 0]; + break; + case 2: /* fallthrough */ + case 1: linear[0] = linear[1] = linear[2] = scanline[x*comp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + fwrite(scanlineheader, 4, 1, f); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(f, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(f, len, comp[x]); + x += len; + } + } + } + } + } +} + +int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + int i; + FILE *f; + if (y <= 0 || x <= 0 || data == NULL) return 0; + f = fopen(filename, "wb"); + if (f) { + /* Each component is stored separately. Allocate scratch space for full output scanline. */ + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + fprintf(f, "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n" ); + fprintf(f, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n" , y, x); + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(f, x, comp, scratch, data + comp*i*x); + STBIW_FREE(scratch); + fclose(f); + } + return f != NULL; +} + +///////////////////////////////////////////////////////// +// PNG + // stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() #define stbiw__sbraw(a) ((int *) (a) - 2) #define stbiw__sbm(a) stbiw__sbraw(a)[0] @@ -196,13 +397,13 @@ int stbi_write_tga(char const *filename, int x, int y, int comp, const void *dat #define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) #define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) -#define stbiw__sbfree(a) ((a) ? free(stbiw__sbraw(a)),0 : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) { int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; - void *p = realloc(*arr ? stbiw__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2); - assert(p); + void *p = STBIW_REALLOC(*arr ? stbiw__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); if (p) { if (!*arr) ((int *) p)[1] = 0; *arr = (void *) ((int *) p + 2); @@ -287,7 +488,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l i=0; while (i < data_len-3) { - // hash next 3 bytes of data to be compressed + // hash next 3 bytes of data to be compressed int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; unsigned char *bestloc = 0; unsigned char **hlist = hash_table[h]; @@ -300,7 +501,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l } // when hash table entry is too long, delete half the entries if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { - memcpy(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); stbiw__sbn(hash_table[h]) = quality; } stbiw__sbpush(hash_table[h],data+i); @@ -323,7 +524,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l if (bestloc) { int d = (int) (data+i - bestloc); // distance back - assert(d <= 32767 && best <= 258); + STBIW_ASSERT(d <= 32767 && best <= 258); for (j=0; best > lengthc[j+1]-1; ++j); stbiw__zlib_huff(j+257); if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); @@ -364,7 +565,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l } *out_len = stbiw__sbn(out); // make returned pointer freeable - memmove(stbiw__sbraw(out), out, *out_len); + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); return (unsigned char *) stbiw__sbraw(out); } @@ -411,8 +612,8 @@ unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, in if (stride_bytes == 0) stride_bytes = x * n; - filt = (unsigned char *) malloc((x*n+1) * y); if (!filt) return 0; - line_buffer = (signed char *) malloc(x * n); if (!line_buffer) { free(filt); return 0; } + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } for (j=0; j < y; ++j) { static int mapping[] = { 0,1,2,3,4 }; static int firstmap[] = { 0,1,0,5,6 }; @@ -451,20 +652,20 @@ unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, in } // when we get here, best contains the filter type, and line_buffer contains the data filt[j*(x*n+1)] = (unsigned char) best; - memcpy(filt+j*(x*n+1)+1, line_buffer, x*n); + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); } - free(line_buffer); + STBIW_FREE(line_buffer); zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory - free(filt); + STBIW_FREE(filt); if (!zlib) return 0; // each tag requires 12 bytes of overhead - out = (unsigned char *) malloc(8 + 12+13 + 12+zlen + 12); + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); if (!out) return 0; *out_len = 8 + 12+13 + 12+zlen + 12; o=out; - memcpy(o,sig,8); o+= 8; + STBIW_MEMMOVE(o,sig,8); o+= 8; stbiw__wp32(o, 13); // header length stbiw__wptag(o, "IHDR"); stbiw__wp32(o, x); @@ -478,14 +679,16 @@ unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, in stbiw__wp32(o, zlen); stbiw__wptag(o, "IDAT"); - memcpy(o, zlib, zlen); o += zlen; free(zlib); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); stbiw__wpcrc(&o, zlen); stbiw__wp32(o,0); stbiw__wptag(o, "IEND"); stbiw__wpcrc(&o,0); - assert(o == out + *out_len); + STBIW_ASSERT(o == out + *out_len); return out; } @@ -497,16 +700,22 @@ int stbi_write_png(char const *filename, int x, int y, int comp, const void *dat unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); if (!png) return 0; f = fopen(filename, "wb"); - if (!f) { free(png); return 0; } + if (!f) { STBIW_FREE(png); return 0; } fwrite(png, 1, len, f); fclose(f); - free(png); + STBIW_FREE(png); return 1; } #endif // STB_IMAGE_WRITE_IMPLEMENTATION /* Revision history - + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP 0.95 (2014-08-17) add monochrome TGA output 0.94 (2014-05-31) diff --git a/src/stb_rect_pack.h b/src/stb_rect_pack.h new file mode 100644 index 000000000..63a5b1594 --- /dev/null +++ b/src/stb_rect_pack.h @@ -0,0 +1,560 @@ +// stb_rect_pack.h - v0.06 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// Bugfixes / warning fixes +// [your name could be here] +// +// Version history: +// +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF void stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight, +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +enum +{ + STBRP__INIT_skyline = 1, +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height < c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + stbrp_node *L1 = NULL, *L2 = NULL; + int count=0; + cur = context->active_head; + while (cur) { + L1 = cur; + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + L2 = cur; + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int rect_height_compare(const void *a, const void *b) +{ + stbrp_rect *p = (stbrp_rect *) a; + stbrp_rect *q = (stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int rect_width_compare(const void *a, const void *b) +{ + stbrp_rect *p = (stbrp_rect *) a; + stbrp_rect *q = (stbrp_rect *) b; + if (p->w > q->w) + return -1; + if (p->w < q->w) + return 1; + return (p->h > q->h) ? -1 : (p->h < q->h); +} + +static int rect_original_order(const void *a, const void *b) +{ + stbrp_rect *p = (stbrp_rect *) a; + stbrp_rect *q = (stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF void stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + #ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); + #endif + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags + for (i=0; i < num_rects; ++i) + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); +} +#endif diff --git a/src/stb_truetype.h b/src/stb_truetype.h new file mode 100644 index 000000000..ae60212e0 --- /dev/null +++ b/src/stb_truetype.h @@ -0,0 +1,2632 @@ +// stb_truetype.h - v1.05 - public domain +// authored from 2009-2014 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// David Gow +// David Given +// Ivan-Assen Ivanov +// Anthony Pesch +// Johan Duparc +// Hou Qiming +// Fabian "ryg" Giesen +// Martins Mozeiko +// Cap Petschulat +// Omar Cornut +// github:aloucks +// Peter LaValle +// +// Misc other: +// Ryan Gordon +// +// VERSION HISTORY +// +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// +// LICENSE +// +// This software is in the public domain. Where that dedication is not +// recognized, you are granted a perpetual, irrevokable license to copy +// and modify this file as you see fit. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversample() -- for improved quality on small fonts +// stbtt_PackFontRanges() +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- use for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_sort() to override this to avoid qsort + #ifndef STBTT_sort + #include + #define STBTT_sort(data,num_items,item_size,compare_func) qsort(data,num_items,item_size,compare_func) + #endif + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is weight x height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is // the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_char_in_range; + int num_chars_in_range; + stbtt_packedchar *chardata_for_range; // output +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. + + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s). The default (no oversampling) is achieved by +// h_oversample=1, v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. You can just skip +// this step if you know it's that kind of font. + + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +typedef struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph +} stbtt_fontinfo; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata); + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE) + + #define ttUSHORT(p) (* (stbtt_uint16 *) (p)) + #define ttSHORT(p) (* (stbtt_int16 *) (p)) + #define ttULONG(p) (* (stbtt_uint32 *) (p)) + #define ttLONG(p) (* (stbtt_int32 *) (p)) + +#else + + static stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + static stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + static stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + static stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#endif + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(const stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*14); + } + } + return -1; +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart) +{ + stbtt_uint8 *data = (stbtt_uint8 *) data2; + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx) + return 0; + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0,y0,x1,y1; + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + +typedef struct stbtt__active_edge +{ + int x,dx; + float ey; + struct stbtt__active_edge *next; + int valid; +} stbtt__active_edge; + +#define FIXSHIFT 10 +#define FIX (1 << FIXSHIFT) +#define FIXMASK (FIX-1) + +static stbtt__active_edge *new_active(stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) STBTT_malloc(sizeof(*z), userdata); // @TODO: make a pool of these!!! + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(e->y0 <= start_point); + if (!z) return z; + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = -STBTT_ifloor(FIX * -dxdy); + else + z->dx = STBTT_ifloor(FIX * dxdy); + z->x = STBTT_ifloor(FIX * (e->x0 + dxdy * (start_point - e->y0))); + z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->valid = e->invert ? 1 : -1; + return z; +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->valid; + } else { + int x1 = e->x; w += e->valid; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> FIXSHIFT; + int j = x1 >> FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((FIX - (x0 & FIXMASK)) * max_weight) >> FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & FIXMASK) * max_weight) >> FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->valid); + z->valid = 0; + STBTT_free(z, userdata); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = new_active(e, off_x, scan_y, userdata); + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + while (active) { + stbtt__active_edge *z = active; + active = active->next; + STBTT_free(z, userdata); + } + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +static int stbtt__edge_compare(const void *p, const void *q) +{ + stbtt__edge *a = (stbtt__edge *) p; + stbtt__edge *b = (stbtt__edge *) q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; + int vsubsample = result->h < 8 ? 15 : 5; + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +typedef struct +{ + stbrp_coord x,y; + int id,w,h,was_packed; +} stbrp_rect; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + float recip_h = 1.0f / spc->h_oversample; + float recip_v = 1.0f / spc->v_oversample; + float sub_x = stbtt__oversample_shift(spc->h_oversample); + float sub_y = stbtt__oversample_shift(spc->v_oversample); + int i,j,k,n, return_value = 1; + stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars_in_range; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars_in_range; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(&info, fh) : stbtt_ScaleForMappingEmToPixels(&info, -fh); + for (j=0; j < ranges[i].num_chars_in_range; ++j) { + int x0,y0,x1,y1; + stbtt_GetCodepointBitmapBoxSubpixel(&info, ranges[i].first_unicode_char_in_range + j, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + stbrp_pack_rects(context, rects, k); + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(&info, fh) : stbtt_ScaleForMappingEmToPixels(&info, -fh); + for (j=0; j < ranges[i].num_chars_in_range; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int glyph = stbtt_FindGlyphIndex(&info, ranges[i].first_unicode_char_in_range + j); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(&info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(&info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_char_in_range = first_unicode_char_in_range; + range.num_chars_in_range = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#endif // STB_TRUETYPE_IMPLEMENTATION diff --git a/src/stb_vorbis.c b/src/stb_vorbis.c index 8bee7e1a9..e27001576 100644 --- a/src/stb_vorbis.c +++ b/src/stb_vorbis.c @@ -1,4 +1,3 @@ - #include "stb_vorbis.h" #ifndef STB_VORBIS_HEADER_ONLY @@ -180,7 +179,7 @@ #define NULL 0 #endif -#ifndef _MSC_VER +#if !defined(_MSC_VER) && !(defined(__MINGW32__) && defined(__forceinline)) #if __GNUC__ #define __forceinline inline #else @@ -3562,7 +3561,7 @@ static int start_decoder(vorb *f) g->sorted_order[j] = (uint8) p[j].y; // precompute the neighbors for (j=2; j < g->values; ++j) { - int low = 0,hi = 0; + int low,hi; neighbors(g->Xlist, j, &low,&hi); g->neighbors[j][0] = low; g->neighbors[j][1] = hi; @@ -5024,6 +5023,7 @@ int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, in #endif // STB_VORBIS_NO_PULLDATA_API /* Version history + 1.05 - 2015/04/19 - don't define __forceinline if it's redundant 1.04 - 2014/08/27 - fix missing const-correct case in API 1.03 - 2014/08/07 - Warning fixes 1.02 - 2014/07/09 - Declare qsort compare function _cdecl on windows diff --git a/src/stb_vorbis.h b/src/stb_vorbis.h index eb3afe3b2..2a1460e19 100644 --- a/src/stb_vorbis.h +++ b/src/stb_vorbis.h @@ -1,4 +1,4 @@ -// Ogg Vorbis audio decoder - v1.04 - public domain +// Ogg Vorbis audio decoder - v1.05 - public domain // http://nothings.org/stb_vorbis/ // // Written by Sean Barrett in 2007, last updated in 2014 @@ -24,12 +24,13 @@ // Casey Muratori John Bolton Gargaj // Laurent Gomila Marc LeBlanc Ronny Chevalier // Bernhard Wodo Evan Balster "alxprd"@github -// Tom Beaumont Ingo Leitgeb +// Tom Beaumont Ingo Leitgeb Nicolas Guillemot // (If you reported a bug but do not appear in this list, it is because // someone else reported the bug before you. There were too many of you to // list them all because I was lax about updating for a long time, sorry.) // // Partial history: +// 1.05 - 2015/04/19 - don't define __forceinline if it's redundant // 1.04 - 2014/08/27 - fix missing const-correct case in API // 1.03 - 2014/08/07 - warning fixes // 1.02 - 2014/07/09 - declare qsort comparison as explicitly _cdecl in Windows @@ -58,6 +59,12 @@ #include #endif +// NOTE: Added to work with raylib on Android +#if defined(PLATFORM_ANDROID) + #include "utils.h" // Android fopen function map +#endif + +// RaySan: Added for Linux #ifdef __linux #include #endif diff --git a/src/text.c b/src/text.c index 944818578..1647070c7 100644 --- a/src/text.c +++ b/src/text.c @@ -30,15 +30,20 @@ #include // Used for functions with variable number of parameters (FormatText()) #include // Standard input / output lib -#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 -#include "utils.h" // Required for function GetExtendion() +#include "utils.h" // Required for function GetExtension() + +// Following libs are used on LoadTTF() +#define STB_TRUETYPE_IMPLEMENTATION +#define STB_RECT_PACK_IMPLEMENTATION +#include "stb_rect_pack.h" +#include "stb_truetype.h" //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- #define FONT_FIRST_CHAR 32 #define MAX_FONTCHARS 128 -#define MAX_FORMATTEXT_LENGTH 50 +#define MAX_FORMATTEXT_LENGTH 64 #define BIT_CHECK(a,b) ((a) & (1<<(b))) @@ -64,6 +69,7 @@ static SpriteFont defaultFont; // Default font provided by raylib static bool PixelIsMagenta(Color p); // Check if a pixel is magenta static int ParseImageData(Color *imgDataPixel, int imgWidth, int imgHeight, Character **charSet); // Parse image pixel data to obtain character set measures static SpriteFont LoadRBMF(const char *fileName); // Load a rBMF font file (raylib BitMap Font) +static SpriteFont LoadTTF(const char *fileName, int fontSize); // Generate a sprite font image from TTF data (font size required) extern void LoadDefaultFont(void); extern void UnloadDefaultFont(void); @@ -73,16 +79,15 @@ extern void UnloadDefaultFont(void); //---------------------------------------------------------------------------------- extern void LoadDefaultFont(void) { - defaultFont.numChars = 96; // We know our default font has 94 chars + // NOTE: Using UTF8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement + // http://www.utf8-chartable.de/unicode-utf8-table.pl - Image image; - image.width = 128; // We know our default font image is 128 pixels width - image.height = 64; // We know our default font image is 64 pixels height + defaultFont.numChars = 224; // Number of chars included in our default font // Default font is directly defined here (data generated from a sprite font image) // This way, we reconstruct SpriteFont without creating large global variables // This data is automatically allocated to Stack and automatically deallocated at the end of this function - int defaultFontData[256] = { + int defaultFontData[512] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200020, 0x0001b000, 0x00000000, 0x00000000, 0x8ef92520, 0x00020a00, 0x7dbe8000, 0x1f7df45f, 0x4a2bf2a0, 0x0852091e, 0x41224000, 0x10041450, 0x2e292020, 0x08220812, 0x41222000, 0x10041450, 0x10f92020, 0x3efa084c, 0x7d22103c, 0x107df7de, 0xe8a12020, 0x08220832, 0x05220800, 0x10450410, 0xa4a3f000, 0x08520832, 0x05220400, 0x10450410, 0xe2f92020, 0x0002085e, 0x7d3e0281, 0x107df41f, @@ -98,42 +103,78 @@ extern void LoadDefaultFont(void) 0x04000404, 0x4100203c, 0x00000000, 0x00000800, 0xf7df7df0, 0x514bef85, 0xbefbefbe, 0x04513bef, 0x14414500, 0x494a2885, 0xa28a28aa, 0x04510820, 0xf44145f0, 0x474a289d, 0xa28a28aa, 0x04510be0, 0x14414510, 0x494a2884, 0xa28a28aa, 0x02910a00, 0xf7df7df0, 0xd14a2f85, 0xbefbe8aa, 0x011f7be0, 0x00000000, 0x00400804, 0x20080000, 0x00000000, 0x00000000, 0x00600f84, 0x20080000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0xac000000, 0x00000f01, 0x00000000, 0x00000000, 0x24000000, 0x00000901, 0x00000000, 0x00000000, 0x24000000, 0x00000901, 0x00000000, 0x00000000, - 0x24fa28a2, 0x00000901, 0x00000000, 0x00000000, 0x2242252a, 0x00000952, 0x00000000, 0x00000000, 0x2422222a, 0x00000929, 0x00000000, 0x00000000, - 0x2412252a, 0x00000901, 0x00000000, 0x00000000, 0x24fbe8be, 0x00000901, 0x00000000, 0x00000000, 0xac020000, 0x00000f01, 0x00000000, 0x00000000, - 0x0003e000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xac000000, 0x00000f01, 0x00000000, 0x00000000, 0x24000000, 0x00000901, 0x00000000, 0x06000000, 0x24000000, 0x00000901, 0x00000000, 0x09108000, + 0x24fa28a2, 0x00000901, 0x00000000, 0x013e0000, 0x2242252a, 0x00000952, 0x00000000, 0x038a8000, 0x2422222a, 0x00000929, 0x00000000, 0x010a8000, + 0x2412252a, 0x00000901, 0x00000000, 0x010a8000, 0x24fbe8be, 0x00000901, 0x00000000, 0x0ebe8000, 0xac020000, 0x00000f01, 0x00000000, 0x00048000, + 0x0003e000, 0x00000000, 0x00000000, 0x00008000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000038, 0x8443b80e, 0x00203a03, + 0x02bea080, 0xf0000020, 0xc452208a, 0x04202b02, 0xf8029122, 0x07f0003b, 0xe44b388e, 0x02203a02, 0x081e8a1c, 0x0411e92a, 0xf4420be0, 0x01248202, + 0xe8140414, 0x05d104ba, 0xe7c3b880, 0x00893a0a, 0x283c0e1c, 0x04500902, 0xc4400080, 0x00448002, 0xe8208422, 0x04500002, 0x80400000, 0x05200002, + 0x083e8e00, 0x04100002, 0x804003e0, 0x07000042, 0xf8008400, 0x07f00003, 0x80400000, 0x04000022, 0x00000000, 0x00000000, 0x80400000, 0x04000002, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00800702, 0x1848a0c2, 0x84010000, 0x02920921, 0x01042642, 0x00005121, 0x42023f7f, 0x00291002, + 0xefc01422, 0x7efdfbf7, 0xefdfa109, 0x03bbbbf7, 0x28440f12, 0x42850a14, 0x20408109, 0x01111010, 0x28440408, 0x42850a14, 0x2040817f, 0x01111010, + 0xefc78204, 0x7efdfbf7, 0xe7cf8109, 0x011111f3, 0x2850a932, 0x42850a14, 0x2040a109, 0x01111010, 0x2850b840, 0x42850a14, 0xefdfbf79, 0x03bbbbf7, + 0x001fa020, 0x00000000, 0x00001000, 0x00000000, 0x00002070, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08022800, 0x00012283, 0x02430802, 0x01010001, 0x8404147c, 0x20000144, 0x80048404, 0x00823f08, 0xdfbf4284, 0x7e03f7ef, 0x142850a1, 0x0000210a, + 0x50a14684, 0x528a1428, 0x142850a1, 0x03efa17a, 0x50a14a9e, 0x52521428, 0x142850a1, 0x02081f4a, 0x50a15284, 0x4a221428, 0xf42850a1, 0x03efa14b, + 0x50a16284, 0x4a521428, 0x042850a1, 0x0228a17a, 0xdfbf427c, 0x7e8bf7ef, 0xf7efdfbf, 0x03efbd0b, 0x00000000, 0x04000000, 0x00000000, 0x00000008, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200508, 0x00840400, 0x11458122, 0x00014210, + 0x00514294, 0x51420800, 0x20a22a94, 0x0050a508, 0x00200000, 0x00000000, 0x00050000, 0x08000000, 0xfefbefbe, 0xfbefbefb, 0xfbeb9114, 0x00fbefbe, + 0x20820820, 0x8a28a20a, 0x8a289114, 0x3e8a28a2, 0xfefbefbe, 0xfbefbe0b, 0x8a289114, 0x008a28a2, 0x228a28a2, 0x08208208, 0x8a289114, 0x088a28a2, + 0xfefbefbe, 0xfbefbefb, 0xfa2f9114, 0x00fbefbe, 0x00000000, 0x00000040, 0x00000000, 0x00000000, 0x00000000, 0x00000020, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00210100, 0x00000004, 0x00000000, 0x00000000, 0x14508200, 0x00001402, 0x00000000, 0x00000000, + 0x00000010, 0x00000020, 0x00000000, 0x00000000, 0xa28a28be, 0x00002228, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, + 0xa28a28aa, 0x000022a8, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, 0xbefbefbe, 0x00003e2f, 0x00000000, 0x00000000, + 0x00000004, 0x00002028, 0x00000000, 0x00000000, 0x80000000, 0x00003e0f, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; int charsHeight = 10; int charsDivisor = 1; // Every char is separated from the consecutive by a 1 pixel divisor, horizontally and vertically - int charsWidth[96] = { 3, 1, 4, 6, 5, 7, 6, 2, 3, 3, 5, 5, 2, 4, 1, 7, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 3, 4, 3, 6, - 7, 6, 6, 6, 6, 6, 6, 6, 6, 3, 5, 6, 5, 7, 6, 6, 6, 6, 6, 6, 7, 6, 7, 7, 6, 6, 6, 2, 7, 2, 3, 5, - 2, 5, 5, 5, 5, 5, 4, 5, 5, 1, 2, 5, 2, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 3, 1, 3, 4, 4 }; + int charsWidth[224] = { 3, 1, 4, 6, 5, 7, 6, 2, 3, 3, 5, 5, 2, 4, 1, 7, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 3, 4, 3, 6, + 7, 6, 6, 6, 6, 6, 6, 6, 6, 3, 5, 6, 5, 7, 6, 6, 6, 6, 6, 6, 7, 6, 7, 7, 6, 6, 6, 2, 7, 2, 3, 5, + 2, 5, 5, 5, 5, 5, 4, 5, 5, 1, 2, 5, 2, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 3, 1, 3, 4, 4, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 5, 5, 5, 7, 1, 5, 3, 7, 3, 5, 4, 1, 7, 4, 3, 5, 3, 3, 2, 5, 6, 1, 2, 2, 3, 5, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 3, 3, 3, 3, 7, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 4, 6, + 5, 5, 5, 5, 5, 5, 9, 5, 5, 5, 5, 5, 2, 2, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5 }; // Re-construct image from defaultFontData and generate OpenGL texture //---------------------------------------------------------------------- - image.pixels = (Color *)malloc(image.width * image.height * sizeof(Color)); + int imWidth = 128; + int imHeight = 128; + + Color *imagePixels = (Color *)malloc(imWidth*imHeight*sizeof(Color)); - for (int i = 0; i < image.width * image.height; i++) image.pixels[i] = BLANK; // Initialize array + for (int i = 0; i < imWidth*imHeight; i++) imagePixels[i] = BLANK; // Initialize array int counter = 0; // Font data elements counter // Fill imgData with defaultFontData (convert from bit to pixel!) - for (int i = 0; i < image.width * image.height; i += 32) + for (int i = 0; i < imWidth*imHeight; i += 32) { for (int j = 31; j >= 0; j--) { - if (BIT_CHECK(defaultFontData[counter], j)) image.pixels[i+j] = WHITE; + if (BIT_CHECK(defaultFontData[counter], j)) imagePixels[i+j] = WHITE; } counter++; - if (counter > 256) counter = 0; // Security check... + if (counter > 512) counter = 0; // Security check... } + //FILE *myimage = fopen("default_font.raw", "wb"); + //fwrite(image.pixels, 1, 128*128*4, myimage); + //fclose(myimage); + + Image image = LoadImageFromData(imagePixels, imWidth, imHeight, UNCOMPRESSED_GRAY_ALPHA); + + free(imagePixels); + defaultFont.texture = LoadTextureFromImage(image, false); // Convert loaded image to OpenGL texture UnloadImage(image); @@ -172,7 +213,7 @@ extern void LoadDefaultFont(void) extern void UnloadDefaultFont(void) { - rlDeleteTextures(defaultFont.texture.id); + UnloadTexture(defaultFont.texture); free(defaultFont.charSet); } @@ -189,18 +230,21 @@ SpriteFont LoadSpriteFont(const char *fileName) // Check file extension if (strcmp(GetExtension(fileName),"rbmf") == 0) spriteFont = LoadRBMF(fileName); + else if (strcmp(GetExtension(fileName),"ttf") == 0) spriteFont = LoadTTF(fileName, 20); else { Image image = LoadImage(fileName); - // At this point we have a pixel array with all the data... + // At this point we have a data array... + Color *imagePixels = GetPixelData(image); + #if defined(PLATFORM_RPI) || defined(PLATFORM_WEB) ConvertToPOT(&image, MAGENTA); #endif // Process bitmap Font pixel data to get measures (Character array) // spriteFont.charSet data is filled inside the function and memory is allocated! - int numChars = ParseImageData(image.pixels, image.width, image.height, &spriteFont.charSet); + int numChars = ParseImageData(imagePixels, image.width, image.height, &spriteFont.charSet); TraceLog(INFO, "[%s] SpriteFont data parsed correctly", fileName); TraceLog(INFO, "[%s] SpriteFont num chars detected: %i", fileName, numChars); @@ -208,7 +252,8 @@ SpriteFont LoadSpriteFont(const char *fileName) spriteFont.numChars = numChars; spriteFont.texture = LoadTextureFromImage(image, false); // Convert loaded image to OpenGL texture - + + free(imagePixels); UnloadImage(image); } @@ -218,7 +263,7 @@ SpriteFont LoadSpriteFont(const char *fileName) // Unload SpriteFont from GPU memory void UnloadSpriteFont(SpriteFont spriteFont) { - rlDeleteTextures(spriteFont.texture.id); + UnloadTexture(spriteFont.texture); free(spriteFont.charSet); } @@ -244,41 +289,37 @@ void DrawText(const char *text, int posX, int posY, int fontSize, Color color) void DrawTextEx(SpriteFont spriteFont, const char *text, Vector2 position, int fontSize, int spacing, Color tint) { int length = strlen(text); - int positionX = (int)position.x; + int offsetX = 0; float scaleFactor; + unsigned char letter; Character c; - if (fontSize <= spriteFont.charSet[0].h) scaleFactor = 1.0f; - else scaleFactor = (float)fontSize / spriteFont.charSet[0].h; + //if (fontSize <= spriteFont.charSet[0].h) scaleFactor = 1.0f; + //else scaleFactor = (float)fontSize / spriteFont.charSet[0].h; + + scaleFactor = (float)fontSize/spriteFont.charSet[0].h; - rlEnableTexture(spriteFont.texture.id); - - rlBegin(RL_QUADS); - for(int i = 0; i < length; i++) + for(int i = 0; i < length; i++) + { + if ((unsigned char)text[i] == 0xc2) { - c = spriteFont.charSet[(int)text[i] - FONT_FIRST_CHAR]; - - rlColor4ub(tint.r, tint.g, tint.b, tint.a); - rlNormal3f(0.0f, 0.0f, 1.0f); // Normal Pointing Towards Viewer - - rlTexCoord2f((float)c.x / spriteFont.texture.width, (float)c.y / spriteFont.texture.height); - rlVertex2f(positionX, position.y); - - rlTexCoord2f((float)c.x / spriteFont.texture.width, (float)(c.y + c.h) / spriteFont.texture.height); - rlVertex2f(positionX, position.y + (c.h) * scaleFactor); - - rlTexCoord2f((float)(c.x + c.w) / spriteFont.texture.width, (float)(c.y + c.h) / spriteFont.texture.height); - rlVertex2f(positionX + (c.w) * scaleFactor, position.y + (c.h) * scaleFactor); - - rlTexCoord2f((float)(c.x + c.w) / spriteFont.texture.width, (float)c.y / spriteFont.texture.height); - rlVertex2f(positionX + (c.w) * scaleFactor, position.y); - - positionX += ((spriteFont.charSet[(int)text[i] - FONT_FIRST_CHAR].w) * scaleFactor + spacing); + letter = (unsigned char)text[i + 1]; + c = spriteFont.charSet[letter - FONT_FIRST_CHAR]; + i++; } - rlEnd(); + else if ((unsigned char)text[i] == 0xc3) + { + letter = (unsigned char)text[i + 1]; + c = spriteFont.charSet[letter - FONT_FIRST_CHAR + 64]; + i++; + } + else c = spriteFont.charSet[(int)text[i] - FONT_FIRST_CHAR]; - rlDisableTexture(); + DrawTexturePro(spriteFont.texture, (Rectangle){ c.x, c.y, c.w, c.h }, (Rectangle){ position.x + offsetX, position.y, c.w*scaleFactor, c.h*scaleFactor} , (Vector2){ 0, 0 }, 0.0f, tint); + + offsetX += (c.w*scaleFactor + spacing); + } } // Formatting of text with variables to 'embed' @@ -465,7 +506,6 @@ static SpriteFont LoadRBMF(const char *fileName) } rbmfInfoHeader; SpriteFont spriteFont; - Image image; rbmfInfoHeader rbmfHeader; unsigned int *rbmfFileData = NULL; @@ -487,9 +527,6 @@ static SpriteFont LoadRBMF(const char *fileName) spriteFont.numChars = (int)rbmfHeader.numChars; - image.width = (int)rbmfHeader.imgWidth; - image.height = (int)rbmfHeader.imgHeight; - int numPixelBits = rbmfHeader.imgWidth * rbmfHeader.imgHeight / 32; rbmfFileData = (unsigned int *)malloc(numPixelBits * sizeof(unsigned int)); @@ -502,22 +539,26 @@ static SpriteFont LoadRBMF(const char *fileName) // Re-construct image from rbmfFileData //----------------------------------------- - image.pixels = (Color *)malloc(image.width * image.height * sizeof(Color)); + Color *imagePixels = (Color *)malloc(rbmfHeader.imgWidth*rbmfHeader.imgHeight*sizeof(Color)); - for (int i = 0; i < image.width * image.height; i++) image.pixels[i] = BLANK; // Initialize array + for (int i = 0; i < rbmfHeader.imgWidth*rbmfHeader.imgHeight; i++) imagePixels[i] = BLANK; // Initialize array int counter = 0; // Font data elements counter // Fill image data (convert from bit to pixel!) - for (int i = 0; i < image.width * image.height; i += 32) + for (int i = 0; i < rbmfHeader.imgWidth*rbmfHeader.imgHeight; i += 32) { for (int j = 31; j >= 0; j--) { - if (BIT_CHECK(rbmfFileData[counter], j)) image.pixels[i+j] = WHITE; + if (BIT_CHECK(rbmfFileData[counter], j)) imagePixels[i+j] = WHITE; } counter++; } + + Image image = LoadImageFromData(imagePixels, rbmfHeader.imgWidth, rbmfHeader.imgHeight, UNCOMPRESSED_GRAY_ALPHA); + + free(imagePixels); TraceLog(INFO, "[%s] Image reconstructed correctly, now converting it to texture", fileName); @@ -557,7 +598,7 @@ static SpriteFont LoadRBMF(const char *fileName) TraceLog(INFO, "[%s] rBMF file loaded correctly as SpriteFont", fileName); } - + fclose(rbmfFile); free(rbmfFileData); // Now we can free loaded data from RAM memory @@ -567,11 +608,105 @@ static SpriteFont LoadRBMF(const char *fileName) } // Generate a sprite font from TTF data (font size required) -static SpriteFont GenerateFromTTF(const char *fileName, int fontSize) +static SpriteFont LoadTTF(const char *fileName, int fontSize) { SpriteFont font; - // TODO: Load TTF and generate bitmap font and chars data + Image image; + image.width = 512; + image.height = 512; + //image.pixels = (Color *)malloc(image.width*image.height*sizeof(Color)); + + unsigned char *ttfBuffer = (unsigned char *)malloc(1 << 25); + + // TODO: Load TTF and generate bitmap font and chars data -> REVIEW! + + stbtt_packedchar chardata[128]; // Num characters: 128 (?) -> REVIEW! + + unsigned char *tempBitmap = (unsigned char *)malloc(image.width*image.height*sizeof(unsigned char)); // One channel bitmap returned! + + // REFERENCE +/* + typedef struct + { + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; + } stbtt_packedchar; +*/ + + stbtt_pack_context pc; + + FILE *ttfFile = fopen(fileName, "rb"); + + fread(ttfBuffer, 1, 1<<25, ttfFile); + + stbtt_PackBegin(&pc, tempBitmap, image.width, image.height, 0, 1, NULL); + + //stbtt_PackSetOversampling(&pc, 1, 1); + //stbtt_PackFontRange(&pc, ttfBuffer, 0, fontSize, 32, 95, chardata[0]+32); + stbtt_PackSetOversampling(&pc, 2, 2); // Better results + stbtt_PackFontRange(&pc, ttfBuffer, 0, fontSize, 32, 95, chardata + 32); + //stbtt_PackSetOversampling(&pc, 3, 1); + //stbtt_PackFontRange(&pc, ttfBuffer, 0, fontSize, 32, 95, chardata[2]+32); + + stbtt_PackEnd(&pc); + + free(ttfBuffer); + + // Now we have image data in tempBitmap and chardata filled... +/* + for (int i = 0; i < 512*512; i++) + { + image.pixels[i].r = tempBitmap[i]; + image.pixels[i].g = tempBitmap[i]; + image.pixels[i].b = tempBitmap[i]; + image.pixels[i].a = 255; + } +*/ + free(tempBitmap); + + // REFERENCE EXAMPLE +/* + //To draw, provide *text, posX, posY + //stbtt_aligned_quad letter; + //stbtt_GetPackedQuad(chardata[0], BITMAP_W, BITMAP_H, *text++, &posX, &posY, &letter, font ? 0 : integer_align); + + void print(float x, float y, int fontNum, char *text) + { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, font_tex); + glBegin(GL_QUADS); + while (*text) { + stbtt_aligned_quad q; + stbtt_GetPackedQuad(chardata[fontNum], BITMAP_W, BITMAP_H, *text++, &x, &y, &q, fontNum ? 0 : integer_align); + drawBoxTC(q.x0,q.y0,q.x1,q.y1, q.s0,q.t0,q.s1,q.t1); + } + glEnd(); + } + + print(100,160, 0, "This is a test"); +*/ + font.numChars = 95; + font.charSet = (Character *)malloc(font.numChars*sizeof(Character)); + font.texture = LoadTextureFromImage(image, false); + + //stbtt_aligned_quad letter; + //int x = 0, y = 0; + + for (int i = 0; i < font.numChars; i++) + { + font.charSet[i].value = i + 32; + + //stbtt_GetPackedQuad(chardata[0], 512, 512, i, &x, &y, &letter, 0); + + font.charSet[i].x = chardata[i + 32].x0; + font.charSet[i].y = chardata[i + 32].y0; + font.charSet[i].w = chardata[i + 32].x1 - chardata[i + 32].x0; + font.charSet[i].h = chardata[i + 32].y1 - chardata[i + 32].y0; + } + + UnloadImage(image); return font; } \ No newline at end of file diff --git a/src/textures.c b/src/textures.c index f701c3800..e6c6cccb5 100644 --- a/src/textures.c +++ b/src/textures.c @@ -47,15 +47,7 @@ //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- -typedef struct { - unsigned char *data; // Image raw data - int width; // Image base width - int height; // Image base height - //int bpp; // bytes per pixel - //int components; // num color components - int mipmaps; // Mipmap levels, 1 by default - int compFormat; // Compressed data format, 0 if no compression -} ImageEx; +// ... //---------------------------------------------------------------------------------- // Global Variables Definition @@ -65,13 +57,16 @@ typedef struct { //---------------------------------------------------------------------------------- // Other Modules Functions Declaration (required by text) //---------------------------------------------------------------------------------- -//... +// ... //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -static ImageEx LoadDDS(const char *fileName); // Load DDS file -static ImageEx LoadPKM(const char *fileName); // Load PKM file +static Image LoadDDS(const char *fileName); // Load DDS file +static Image LoadPKM(const char *fileName); // Load PKM file +static Image LoadKTX(const char *fileName); // Load KTX file +static Image LoadPVR(const char *fileName); // Load PVR file +static Image LoadASTC(const char *fileName); // Load ASTC file //---------------------------------------------------------------------------------- // Module Functions Definition @@ -82,10 +77,12 @@ Image LoadImage(const char *fileName) { Image image; - // Initial values - image.pixels = NULL; + // Initialize image default values + image.data = NULL; image.width = 0; image.height = 0; + image.mipmaps = 0; + image.format = 0; if ((strcmp(GetExtension(fileName),"png") == 0) || (strcmp(GetExtension(fileName),"bmp") == 0) || @@ -95,84 +92,39 @@ Image LoadImage(const char *fileName) (strcmp(GetExtension(fileName),"psd") == 0) || (strcmp(GetExtension(fileName),"pic") == 0)) { - int imgWidth; - int imgHeight; - int imgBpp; - + int imgWidth = 0; + int imgHeight = 0; + int imgBpp = 0; + // NOTE: Using stb_image to load images (Supports: BMP, TGA, PNG, JPG, ...) - // Force loading to 4 components (RGBA) - byte *imgData = stbi_load(fileName, &imgWidth, &imgHeight, &imgBpp, 4); + image.data = stbi_load(fileName, &imgWidth, &imgHeight, &imgBpp, 0); - // TODO: Check if file could be loaded! (imgData == NULL)? - - if (imgData != NULL) - { - // Convert array to pixel array for working convenience - image.pixels = (Color *)malloc(imgWidth * imgHeight * sizeof(Color)); - - int pix = 0; - - for (int i = 0; i < (imgWidth * imgHeight * 4); i += 4) - { - image.pixels[pix].r = imgData[i]; - image.pixels[pix].g = imgData[i+1]; - image.pixels[pix].b = imgData[i+2]; - image.pixels[pix].a = imgData[i+3]; - pix++; - } - - stbi_image_free(imgData); - - image.width = imgWidth; - image.height = imgHeight; - - TraceLog(INFO, "[%s] Image loaded successfully (%ix%i)", fileName, image.width, image.height); - } - else TraceLog(WARNING, "[%s] Image could not be loaded, file not recognized", fileName); + image.width = imgWidth; + image.height = imgHeight; + image.mipmaps = 1; + + if (imgBpp == 1) image.format = UNCOMPRESSED_GRAYSCALE; + else if (imgBpp == 2) image.format = UNCOMPRESSED_GRAY_ALPHA; + else if (imgBpp == 3) image.format = UNCOMPRESSED_R8G8B8; + else if (imgBpp == 4) image.format = UNCOMPRESSED_R8G8B8A8; } - else if (strcmp(GetExtension(fileName),"dds") == 0) - { - // NOTE: DDS uncompressed images can also be loaded (discarding mipmaps...) - - ImageEx imageDDS = LoadDDS(fileName); - - if (imageDDS.compFormat == 0) - { - image.pixels = (Color *)malloc(imageDDS.width * imageDDS.height * sizeof(Color)); - image.width = imageDDS.width; - image.height = imageDDS.height; - - int pix = 0; - - for (int i = 0; i < (image.width * image.height * 4); i += 4) - { - image.pixels[pix].r = imageDDS.data[i]; - image.pixels[pix].g = imageDDS.data[i+1]; - image.pixels[pix].b = imageDDS.data[i+2]; - image.pixels[pix].a = imageDDS.data[i+3]; - pix++; - } - - free(imageDDS.data); - - TraceLog(INFO, "[%s] DDS Image loaded successfully (uncompressed, no mipmaps)", fileName); - } - else TraceLog(WARNING, "[%s] DDS Compressed image data could not be loaded", fileName); + else if (strcmp(GetExtension(fileName),"dds") == 0) image = LoadDDS(fileName); + else if (strcmp(GetExtension(fileName),"pkm") == 0) image = LoadPKM(fileName); + else if (strcmp(GetExtension(fileName),"ktx") == 0) image = LoadKTX(fileName); + else if (strcmp(GetExtension(fileName),"pvr") == 0) image = LoadPVR(fileName); + else if (strcmp(GetExtension(fileName),"astc") == 0) image = LoadASTC(fileName); + + if (image.data != NULL) + { + TraceLog(INFO, "[%s] Image loaded successfully (%ix%i)", fileName, image.width, image.height); } - else if (strcmp(GetExtension(fileName),"pkm") == 0) - { - TraceLog(INFO, "[%s] PKM Compressed image data could not be loaded", fileName); - } - else TraceLog(WARNING, "[%s] Image extension not recognized, it can't be loaded", fileName); - - // ALTERNATIVE: We can load pixel data directly into Color struct pixels array, - // to do that, struct data alignment should be the right one (4 byte); it is. - //image.pixels = stbi_load(fileName, &imgWidth, &imgHeight, &imgBpp, 4); + else TraceLog(WARNING, "[%s] Image could not be loaded, file not recognized", fileName); return image; } // Load an image from rRES file (raylib Resource) +// TODO: Review function to support multiple color modes Image LoadImageFromRES(const char *rresName, int resId) { Image image; @@ -187,7 +139,7 @@ Image LoadImageFromRES(const char *rresName, int resId) FILE *rresFile = fopen(rresName, "rb"); - if (rresFile == NULL) + if (rresFile == NULL) { TraceLog(WARNING, "[%s] rRES raylib resource file could not be opened", rresName); } @@ -235,28 +187,19 @@ Image LoadImageFromRES(const char *rresName, int resId) image.width = (int)imgWidth; image.height = (int)imgHeight; - unsigned char *data = malloc(infoHeader.size); + unsigned char *compData = malloc(infoHeader.size); - fread(data, infoHeader.size, 1, rresFile); + fread(compData, infoHeader.size, 1, rresFile); - unsigned char *imgData = DecompressData(data, infoHeader.size, infoHeader.srcSize); + unsigned char *imgData = DecompressData(compData, infoHeader.size, infoHeader.srcSize); - image.pixels = (Color *)malloc(sizeof(Color)*imgWidth*imgHeight); + // TODO: Review color mode + //image.data = (unsigned char *)malloc(sizeof(unsigned char)*imgWidth*imgHeight*4); + image.data = imgData; - int pix = 0; + //free(imgData); - for (int i = 0; i < (imgWidth*imgHeight*4); i += 4) - { - image.pixels[pix].r = imgData[i]; - image.pixels[pix].g = imgData[i+1]; - image.pixels[pix].b = imgData[i+2]; - image.pixels[pix].a = imgData[i+3]; - pix++; - } - - free(imgData); - - free(data); + free(compData); TraceLog(INFO, "[%s] Image loaded successfully from resource, size: %ix%i", rresName, image.width, image.height); } @@ -296,64 +239,42 @@ Image LoadImageFromRES(const char *rresName, int resId) Texture2D LoadTexture(const char *fileName) { Texture2D texture; + + Image image = LoadImage(fileName); - // Init texture to default values - texture.id = 0; - texture.width = 0; - texture.height = 0; +#if defined(PLATFORM_RPI) || defined(PLATFORM_WEB) + ConvertToPOT(&image, BLANK); +#endif - if (strcmp(GetExtension(fileName),"dds") == 0) - { - ImageEx image = LoadDDS(fileName); - - if (image.compFormat == 0) - { - texture.id = rlglLoadTexture(image.data, image.width, image.height, false); - } - else - { - texture.id = rlglLoadCompressedTexture(image.data, image.width, image.height, image.mipmaps, image.compFormat); - } - - texture.width = image.width; - texture.height = image.height; - - if (texture.id == 0) TraceLog(WARNING, "[%s] DDS texture could not be loaded", fileName); - else TraceLog(INFO, "[%s] DDS texture loaded successfully", fileName); - - free(image.data); - } - else if (strcmp(GetExtension(fileName),"pkm") == 0) - { - ImageEx image = LoadPKM(fileName); - - texture.id = rlglLoadCompressedTexture(image.data, image.width, image.height, image.mipmaps, image.compFormat); - - texture.width = image.width; - texture.height = image.height; - - if (texture.id == 0) TraceLog(WARNING, "[%s] PKM texture could not be loaded", fileName); - else TraceLog(INFO, "[%s] PKM texture loaded successfully", fileName); - - free(image.data); + if (image.data != NULL) + { + texture = LoadTextureFromImage(image, false); + UnloadImage(image); } else { - Image image = LoadImage(fileName); + TraceLog(WARNING, "Texture could not be created"); - if (image.pixels != NULL) - { -#if defined(PLATFORM_RPI) || defined(PLATFORM_WEB) - ConvertToPOT(&image, BLANK); -#endif - texture = LoadTextureFromImage(image, false); - UnloadImage(image); - } + texture.id = 0; } return texture; } +Texture2D LoadTextureEx(void *data, int width, int height, int textureFormat, int mipmapCount, bool genMipmaps) +{ + Texture2D texture; + + texture.width = width; + texture.height = height; + texture.mipmaps = mipmapCount; + texture.format = textureFormat; + + texture.id = rlglLoadTexture(data, width, height, textureFormat, mipmapCount, genMipmaps); + + return texture; +} + // Load a texture from image data // NOTE: image is not unloaded, it must be done manually Texture2D LoadTextureFromImage(Image image, bool genMipmaps) @@ -364,45 +285,15 @@ Texture2D LoadTextureFromImage(Image image, bool genMipmaps) texture.id = 0; texture.width = 0; texture.height = 0; - - if ((image.pixels != NULL) && (image.width > 0) && (image.height > 0)) - { - unsigned char *imgData = malloc(image.width * image.height * 4); - - int j = 0; - - for (int i = 0; i < image.width * image.height * 4; i += 4) - { - imgData[i] = image.pixels[j].r; - imgData[i+1] = image.pixels[j].g; - imgData[i+2] = image.pixels[j].b; - imgData[i+3] = image.pixels[j].a; - - j++; - } - - // NOTE: rlglLoadTexture() can generate mipmaps (POT image required) - texture.id = rlglLoadTexture(imgData, image.width, image.height, genMipmaps); - - texture.width = image.width; - texture.height = image.height; - - free(imgData); - } - else TraceLog(WARNING, "Texture could not be loaded, image data is not valid"); - - return texture; -} - -// [DEPRECATED] Load a texture from image data -// NOTE: Use LoadTextureFromImage() instead -Texture2D CreateTexture(Image image, bool genMipmaps) -{ - Texture2D texture; + texture.mipmaps = 0; + texture.format = 0; - texture = LoadTextureFromImage(image, genMipmaps); - - TraceLog(INFO, "Created texture id: %i", texture.id); + texture.id = rlglLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps, false); + + texture.width = image.width; + texture.height = image.height; + texture.mipmaps = image.mipmaps; + texture.format = image.format; return texture; } @@ -422,7 +313,7 @@ Texture2D LoadTextureFromRES(const char *rresName, int resId) // Unload image from CPU memory (RAM) void UnloadImage(Image image) { - free(image.pixels); + free(image.data); } // Unload texture from GPU memory @@ -435,6 +326,8 @@ void UnloadTexture(Texture2D texture) // NOTE: Requirement on OpenGL ES 2.0 (RPI, HTML5) void ConvertToPOT(Image *image, Color fillColor) { + // TODO: Review for new image struct + /* // Just add the required amount of pixels at the right and bottom sides of image... int potWidth = GetNextPOT(image->width); int potHeight = GetNextPOT(image->height); @@ -451,7 +344,7 @@ void ConvertToPOT(Image *image, Color fillColor) { for (int i = 0; i < potWidth; i++) { - if ((j < image->height) && (i < image->width)) imgDataPixelPOT[j*potWidth + i] = image->pixels[j*image->width + i]; + if ((j < image->height) && (i < image->width)) imgDataPixelPOT[j*potWidth + i] = image->data[j*image->width + i]; else imgDataPixelPOT[j*potWidth + i] = fillColor; } } @@ -464,6 +357,232 @@ void ConvertToPOT(Image *image, Color fillColor) image->width = potWidth; image->height = potHeight; } + */ +} + +// Get pixel data from image in the form of Color struct array +Color *GetPixelData(Image image) +{ + Color *pixels = (Color *)malloc(image.width*image.height*sizeof(Color)); + + int k = 0; + + for (int i = 0; i < image.width*image.height; i++) + { + switch (image.format) + { + case UNCOMPRESSED_GRAYSCALE: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k]; + pixels[i].b = ((unsigned char *)image.data)[k]; + pixels[i].a = 255; + + k++; + } break; + case UNCOMPRESSED_GRAY_ALPHA: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k]; + pixels[i].b = ((unsigned char *)image.data)[k]; + pixels[i].a = ((unsigned char *)image.data)[k + 1]; + + k += 2; + } break; + case UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[k]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31)); + pixels[i].a = (unsigned char)((pixel & 0b0000000000000001)*255); + + k++; + } break; + case UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[k]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63)); + pixels[i].b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31)); + pixels[i].a = 255; + + k++; + } break; + case UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[k]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15)); + pixels[i].a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15)); + + k++; + } break; + case UNCOMPRESSED_R8G8B8A8: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k + 1]; + pixels[i].b = ((unsigned char *)image.data)[k + 2]; + pixels[i].a = ((unsigned char *)image.data)[k + 3]; + + k += 4; + } break; + case UNCOMPRESSED_R8G8B8: + { + pixels[i].r = (unsigned char)((unsigned char *)image.data)[k]; + pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1]; + pixels[i].b = (unsigned char)((unsigned char *)image.data)[k + 2]; + pixels[i].a = 255; + + k += 3; + } break; + default: TraceLog(WARNING, "Format not supported for pixel data retrieval"); break; + } + } + + return pixels; +} + +// Fill image data with pixels Color data (RGBA - 32bit) +// NOTE: Data is transformed to desired format +Image LoadImageFromData(Color *pixels, int width, int height, int format) +{ + Image image; + image.data = NULL; + image.width = width; + image.height = height; + image.mipmaps = 1; + image.format = format; + + int k = 0; + + switch (format) + { + case UNCOMPRESSED_GRAYSCALE: + { + image.data = (unsigned char *)malloc(image.width*image.height*sizeof(unsigned char)); + + for (int i = 0; i < image.width*image.height; i++) + { + ((unsigned char *)image.data)[i] = (unsigned char)((float)pixels[k].r*0.299f + (float)pixels[k].g*0.587f + (float)pixels[k].b*0.114f); + k++; + } + + } break; + case UNCOMPRESSED_GRAY_ALPHA: + { + image.data = (unsigned char *)malloc(image.width*image.height*2*sizeof(unsigned char)); + + for (int i = 0; i < image.width*image.height*2; i += 2) + { + ((unsigned char *)image.data)[i] = (unsigned char)((float)pixels[k].r*0.299f + (float)pixels[k].g*0.587f + (float)pixels[k].b*0.114f); + ((unsigned char *)image.data)[i + 1] = pixels[k].a; + k++; + } + + } break; + case UNCOMPRESSED_R5G6B5: + { + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + + unsigned char r; + unsigned char g; + unsigned char b; + + for (int i = 0; i < image.width*image.height; i++) + { + r = (unsigned char)(round((float)pixels[k].r*31/255)); + g = (unsigned char)(round((float)pixels[k].g*63/255)); + b = (unsigned char)(round((float)pixels[k].b*31/255)); + + ((unsigned short *)image.data)[i] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + k++; + } + + } break; + case UNCOMPRESSED_R8G8B8: + { + image.data = (unsigned char *)malloc(image.width*image.height*3*sizeof(unsigned char)); + + for (int i = 0; i < image.width*image.height*3; i += 3) + { + ((unsigned char *)image.data)[i] = pixels[k].r; + ((unsigned char *)image.data)[i + 1] = pixels[k].g; + ((unsigned char *)image.data)[i + 2] = pixels[k].b; + k++; + } + } break; + case UNCOMPRESSED_R5G5B5A1: + { + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a = 1; + + for (int i = 0; i < image.width*image.height; i++) + { + r = (unsigned char)(round((float)pixels[k].r*31/255)); + g = (unsigned char)(round((float)pixels[k].g*31/255)); + b = (unsigned char)(round((float)pixels[k].b*31/255)); + a = (pixels[k].a > 50) ? 1 : 0; + + ((unsigned short *)image.data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1| (unsigned short)a; + + k++; + } + + } break; + case UNCOMPRESSED_R4G4B4A4: + { + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + + for (int i = 0; i < image.width*image.height; i++) + { + r = (unsigned char)(round((float)pixels[k].r*15/255)); + g = (unsigned char)(round((float)pixels[k].g*15/255)); + b = (unsigned char)(round((float)pixels[k].b*15/255)); + a = (unsigned char)(round((float)pixels[k].a*15/255)); + + ((unsigned short *)image.data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8| (unsigned short)b << 4| (unsigned short)a; + + k++; + } + + } break; + case UNCOMPRESSED_R8G8B8A8: + { + image.data = (unsigned char *)malloc(image.width*image.height*4*sizeof(unsigned char)); + + for (int i = 0; i < image.width*image.height*4; i += 4) + { + ((unsigned char *)image.data)[i] = pixels[k].r; + ((unsigned char *)image.data)[i + 1] = pixels[k].g; + ((unsigned char *)image.data)[i + 2] = pixels[k].b; + ((unsigned char *)image.data)[i + 3] = pixels[k].a; + k++; + } + } break; + default: + { + TraceLog(WARNING, "Format not recognized, image could not be loaded"); + + return image; + } break; + } + + return image; } // Draw a Texture2D @@ -494,7 +613,7 @@ void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Co Rectangle destRec = { (int)position.x, (int)position.y, sourceRec.width, sourceRec.height }; Vector2 origin = { 0, 0 }; - DrawTexturePro(texture, sourceRec, destRec, origin, 0, tint); + DrawTexturePro(texture, sourceRec, destRec, origin, 0.0f, tint); } // Draw a part of a texture (defined by a rectangle) with 'pro' parameters @@ -538,25 +657,21 @@ void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, V //---------------------------------------------------------------------------------- // Loading DDS image data (compressed or uncompressed) -// NOTE: Compressed data loading not supported on OpenGL 1.1 -static ImageEx LoadDDS(const char *fileName) +static Image LoadDDS(const char *fileName) { + // Required extension: + // GL_EXT_texture_compression_s3tc + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 + // GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 + // GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 + // GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + #define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII #define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII #define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII - #ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 - #endif - - #ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 - #endif - - #ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 - #endif - // DDS Pixel Format typedef struct { unsigned int size; @@ -565,7 +680,7 @@ static ImageEx LoadDDS(const char *fileName) unsigned int rgbBitCount; unsigned int rBitMask; unsigned int gBitMask; - unsigned int bitMask; + unsigned int bBitMask; unsigned int aBitMask; } ddsPixelFormat; @@ -577,7 +692,7 @@ static ImageEx LoadDDS(const char *fileName) unsigned int width; unsigned int pitchOrLinearSize; unsigned int depth; - unsigned int mipMapCount; + unsigned int mipmapCount; unsigned int reserved1[11]; ddsPixelFormat ddspf; unsigned int caps; @@ -587,20 +702,19 @@ static ImageEx LoadDDS(const char *fileName) unsigned int reserved2; } ddsHeader; - ImageEx image; - ddsHeader header; - + Image image; + image.data = NULL; image.width = 0; image.height = 0; image.mipmaps = 0; - image.compFormat = 0; + image.format = 0; FILE *ddsFile = fopen(fileName, "rb"); if (ddsFile == NULL) { - TraceLog(WARNING, "[%s] DDS image file could not be opened", fileName); + TraceLog(WARNING, "[%s] DDS file could not be opened", fileName); } else { @@ -612,11 +726,12 @@ static ImageEx LoadDDS(const char *fileName) if (strncmp(filecode, "DDS ", 4) != 0) { TraceLog(WARNING, "[%s] DDS file does not seem to be a valid image", fileName); - fclose(ddsFile); } else { - // Get the surface descriptor + ddsHeader header; + + // Get the image header fread(&header, sizeof(ddsHeader), 1, ddsFile); TraceLog(DEBUG, "[%s] DDS file header size: %i", fileName, sizeof(ddsHeader)); @@ -626,75 +741,101 @@ static ImageEx LoadDDS(const char *fileName) image.width = header.width; image.height = header.height; - image.mipmaps = 1; - image.compFormat = 0; + image.mipmaps = 1; // Default value, could be changed (header.mipmapCount) - if (header.ddspf.flags == 0x40 && header.ddspf.rgbBitCount == 24) // DDS_RGB, no compressed + if (header.ddspf.rgbBitCount == 16) // 16bit mode, no compressed { - image.data = (unsigned char *)malloc(header.width * header.height * 4); - unsigned char *buffer = (unsigned char *)malloc(header.width * header.height * 3); - - fread(buffer, image.width*image.height*3, 1, ddsFile); - - unsigned char *src = buffer; - unsigned char *dest = image.data; - - for(int y = 0; y < image.height; y++) + if (header.ddspf.flags == 0x40) // no alpha channel { - for(int x = 0; x < image.width; x++) + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); + + image.format = UNCOMPRESSED_R5G6B5; + } + else if (header.ddspf.flags == 0x41) // with alpha channel + { + if (header.ddspf.aBitMask == 0x8000) // 1bit alpha { - *dest++ = *src++; - *dest++ = *src++; - *dest++ = *src++; - *dest++ = 255; + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); + + unsigned char alpha = 0; + + // NOTE: Data comes as A1R5G5B5, it must be reordered to R5G5B5A1 + for (int i = 0; i < image.width*image.height; i++) + { + alpha = ((unsigned short *)image.data)[i] >> 15; + ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 1; + ((unsigned short *)image.data)[i] += alpha; + } + + image.format = UNCOMPRESSED_R5G5B5A1; + } + else if (header.ddspf.aBitMask == 0xf000) // 4bit alpha + { + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); + + unsigned char alpha = 0; + + // NOTE: Data comes as A4R4G4B4, it must be reordered R4G4B4A4 + for (int i = 0; i < image.width*image.height; i++) + { + alpha = ((unsigned short *)image.data)[i] >> 12; + ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 4; + ((unsigned short *)image.data)[i] += alpha; + } + + image.format = UNCOMPRESSED_R4G4B4A4; } } - - free(buffer); + } + if (header.ddspf.flags == 0x40 && header.ddspf.rgbBitCount == 24) // DDS_RGB, no compressed + { + // NOTE: not sure if this case exists... + image.data = (unsigned char *)malloc(image.width*image.height*3*sizeof(unsigned char)); + fread(image.data, image.width*image.height*3, 1, ddsFile); + + image.format = UNCOMPRESSED_R8G8B8; } else if (header.ddspf.flags == 0x41 && header.ddspf.rgbBitCount == 32) // DDS_RGBA, no compressed { - image.data = (unsigned char *)malloc(header.width * header.height * 4); - + image.data = (unsigned char *)malloc(image.width*image.height*4*sizeof(unsigned char)); fread(image.data, image.width*image.height*4, 1, ddsFile); - image.mipmaps = 1; - image.compFormat = 0; + image.format = UNCOMPRESSED_R8G8B8A8; } - else if ((header.ddspf.flags == 0x04) && (header.ddspf.fourCC > 0)) + else if (((header.ddspf.flags == 0x04) || (header.ddspf.flags == 0x05)) && (header.ddspf.fourCC > 0)) { - TraceLog(WARNING, "[%s] DDS image uses compression, not supported on OpenGL 1.1", fileName); - TraceLog(WARNING, "[%s] DDS compressed files require OpenGL 3.2+ or ES 2.0", fileName); - int bufsize; // Calculate data size, including all mipmaps - if (header.mipMapCount > 1) bufsize = header.pitchOrLinearSize * 2; + if (header.mipmapCount > 1) bufsize = header.pitchOrLinearSize*2; else bufsize = header.pitchOrLinearSize; + + TraceLog(DEBUG, "Pitch or linear size: %i", header.pitchOrLinearSize); - image.data = (unsigned char*)malloc(bufsize * sizeof(unsigned char)); + image.data = (unsigned char*)malloc(bufsize*sizeof(unsigned char)); fread(image.data, 1, bufsize, ddsFile); - // Close file pointer - fclose(ddsFile); - - image.mipmaps = header.mipMapCount; - image.compFormat = 0; + image.mipmaps = header.mipmapCount; switch(header.ddspf.fourCC) { - case FOURCC_DXT1: image.compFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; - case FOURCC_DXT3: image.compFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; - case FOURCC_DXT5: image.compFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case FOURCC_DXT1: + { + if (header.ddspf.flags == 0x04) image.format = COMPRESSED_DXT1_RGB; + else image.format = COMPRESSED_DXT1_RGBA; + } break; + case FOURCC_DXT3: image.format = COMPRESSED_DXT3_RGBA; break; + case FOURCC_DXT5: image.format = COMPRESSED_DXT5_RGBA; break; default: break; } - - // NOTE: Image num color components not required... for now... - //if (fourCC == FOURCC_DXT1) image.components = 3; - //else image.components = 4; } } + + fclose(ddsFile); // Close file pointer } return image; @@ -703,85 +844,410 @@ static ImageEx LoadDDS(const char *fileName) // Loading PKM image data (ETC1/ETC2 compression) // NOTE: KTX is the standard Khronos Group compression format (ETC1/ETC2, mipmaps) // PKM is a much simpler file format used mainly to contain a single ETC1/ETC2 compressed image (no mipmaps) -static ImageEx LoadPKM(const char *fileName) +static Image LoadPKM(const char *fileName) { - // If OpenGL ES 2.0. the following format could be supported (ETC1): - //GL_ETC1_RGB8_OES - - #ifndef GL_ETC1_RGB8_OES - #define GL_ETC1_RGB8_OES 0x8D64 - #endif - - // If OpenGL ES 3.0, the following formats are supported (ETC2/EAC): - //GL_COMPRESSED_RGB8_ETC2 - //GL_COMPRESSED_RGBA8_ETC2 - //GL_COMPRESSED_RG11_EAC - //... + // Required extensions: + // GL_OES_compressed_ETC1_RGB8_texture (ETC1) (OpenGL ES 2.0) + // GL_ARB_ES3_compatibility (ETC2/EAC) (OpenGL ES 3.0) + + // Supported tokens (defined by extensions) + // GL_ETC1_RGB8_OES 0x8D64 + // GL_COMPRESSED_RGB8_ETC2 0x9274 + // GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 // PKM file (ETC1) Header (16 bytes) typedef struct { char id[4]; // "PKM " - char version[2]; // "10" - unsigned short format; // Format = number of mipmaps = 0 (ETC1_RGB_NO_MIPMAPS) - unsigned short extWidth; // Texture width (big-endian) - unsigned short extHeight; // Texture height (big-endian) + char version[2]; // "10" or "20" + unsigned short format; // Data format (big-endian) (Check list below) + unsigned short width; // Texture width (big-endian) (origWidth rounded to multiple of 4) + unsigned short height; // Texture height (big-endian) (origHeight rounded to multiple of 4) unsigned short origWidth; // Original width (big-endian) unsigned short origHeight; // Original height (big-endian) } pkmHeader; + + // Formats list + // version 10: format: 0=ETC1_RGB, [1=ETC1_RGBA, 2=ETC1_RGB_MIP, 3=ETC1_RGBA_MIP] (not used) + // version 20: format: 0=ETC1_RGB, 1=ETC2_RGB, 2=ETC2_RGBA_OLD, 3=ETC2_RGBA, 4=ETC2_RGBA1, 5=ETC2_R, 6=ETC2_RG, 7=ETC2_SIGNED_R, 8=ETC2_SIGNED_R // NOTE: The extended width and height are the widths rounded up to a multiple of 4. - // NOTE: ETC is always 4bit per pixel (64 bits for each 4x4 block of pixels) + // NOTE: ETC is always 4bit per pixel (64 bit for each 4x4 block of pixels) - // Bytes Swap (little-endian <-> big-endian) - //unsigned short data; - //unsigned short swap = ((data & 0x00FF) << 8) | ((data & 0xFF00) >> 8); - - ImageEx image; - - unsigned short width; - unsigned short height; - unsigned short useless; + Image image; + + image.data = NULL; + image.width = 0; + image.height = 0; + image.mipmaps = 0; + image.format = 0; FILE *pkmFile = fopen(fileName, "rb"); if (pkmFile == NULL) { - TraceLog(WARNING, "[%s] PKM image file could not be opened", fileName); + TraceLog(WARNING, "[%s] PKM file could not be opened", fileName); } else { - // Verify the type of file - char filecode[4]; + pkmHeader header; - fread(filecode, 1, 4, pkmFile); + // Get the image header + fread(&header, sizeof(pkmHeader), 1, pkmFile); - if (strncmp(filecode, "PKM ", 4) != 0) + if (strncmp(header.id, "PKM ", 4) != 0) { TraceLog(WARNING, "[%s] PKM file does not seem to be a valid image", fileName); - fclose(pkmFile); } else { - // Get the surface descriptor - fread(&useless, sizeof(unsigned short), 1, pkmFile); // Discard version - fread(&useless, sizeof(unsigned short), 1, pkmFile); // Discard format - - fread(&width, sizeof(unsigned short), 1, pkmFile); // Read extended width - fread(&height, sizeof(unsigned short), 1, pkmFile); // Read extended height - - int size = (width/4)*(height/4)*8; // Total data size in bytes + // NOTE: format, width and height come as big-endian, data must be swapped to little-endian + header.format = ((header.format & 0x00FF) << 8) | ((header.format & 0xFF00) >> 8); + header.width = ((header.width & 0x00FF) << 8) | ((header.width & 0xFF00) >> 8); + header.height = ((header.height & 0x00FF) << 8) | ((header.height & 0xFF00) >> 8); + + TraceLog(INFO, "PKM (ETC) image width: %i", header.width); + TraceLog(INFO, "PKM (ETC) image height: %i", header.height); + TraceLog(INFO, "PKM (ETC) image format: %i", header.format); + + image.width = header.width; + image.height = header.height; + image.mipmaps = 1; + + int size = image.width*image.height*4/8; // Total data size in bytes image.data = (unsigned char*)malloc(size * sizeof(unsigned char)); fread(image.data, 1, size, pkmFile); - fclose(pkmFile); // Close file pointer - - image.width = width; - image.height = height; - image.mipmaps = 1; - image.compFormat = GL_ETC1_RGB8_OES; + if (header.format == 0) image.format = COMPRESSED_ETC1_RGB; + else if (header.format == 1) image.format = COMPRESSED_ETC2_RGB; + else if (header.format == 3) image.format = COMPRESSED_ETC2_EAC_RGBA; } + + fclose(pkmFile); // Close file pointer + } + + return image; +} + +// Load KTX compressed image data (ETC1/ETC2 compression) +static Image LoadKTX(const char *fileName) +{ + // Required extensions: + // GL_OES_compressed_ETC1_RGB8_texture (ETC1) + // GL_ARB_ES3_compatibility (ETC2/EAC) + + // Supported tokens (defined by extensions) + // GL_ETC1_RGB8_OES 0x8D64 + // GL_COMPRESSED_RGB8_ETC2 0x9274 + // GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 + + // KTX file Header (64 bytes) + // https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + typedef struct { + char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" + unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04 + unsigned int glType; // For compressed textures, glType must equal 0 + unsigned int glTypeSize; // For compressed texture data, usually 1 + unsigned int glFormat; // For compressed textures is 0 + unsigned int glInternalFormat; // Compressed internal format + unsigned int glBaseInternalFormat; // Same as glFormat (RGB, RGBA, ALPHA...) + unsigned int width; // Texture image width in pixels + unsigned int height; // Texture image height in pixels + unsigned int depth; // For 2D textures is 0 + unsigned int elements; // Number of array elements, usually 0 + unsigned int faces; // Cubemap faces, for no-cubemap = 1 + unsigned int mipmapLevels; // Non-mipmapped textures = 1 + unsigned int keyValueDataSize; // Used to encode any arbitrary data... + } ktxHeader; + + // NOTE: Before start of every mipmap data block, we have: unsigned int dataSize + + Image image; + + image.width = 0; + image.height = 0; + image.mipmaps = 0; + image.format = 0; + + FILE *ktxFile = fopen(fileName, "rb"); + + if (ktxFile == NULL) + { + TraceLog(WARNING, "[%s] KTX image file could not be opened", fileName); + } + else + { + ktxHeader header; + + // Get the image header + fread(&header, sizeof(ktxHeader), 1, ktxFile); + + if ((header.id[1] != 'K') || (header.id[2] != 'T') || (header.id[3] != 'X') || + (header.id[4] != ' ') || (header.id[5] != '1') || (header.id[6] != '1')) + { + TraceLog(WARNING, "[%s] KTX file does not seem to be a valid file", fileName); + } + else + { + image.width = header.width; + image.height = header.height; + image.mipmaps = header.mipmapLevels; + + TraceLog(DEBUG, "KTX (ETC) image width: %i", header.width); + TraceLog(DEBUG, "KTX (ETC) image height: %i", header.height); + TraceLog(DEBUG, "KTX (ETC) image format: 0x%x", header.glInternalFormat); + + unsigned char unused; + + if (header.keyValueDataSize > 0) + { + for (int i = 0; i < header.keyValueDataSize; i++) fread(&unused, 1, 1, ktxFile); + } + + int dataSize; + fread(&dataSize, sizeof(unsigned int), 1, ktxFile); + + image.data = (unsigned char*)malloc(dataSize * sizeof(unsigned char)); + + fread(image.data, 1, dataSize, ktxFile); + + if (header.glInternalFormat == 0x8D64) image.format = COMPRESSED_ETC1_RGB; + else if (header.glInternalFormat == 0x9274) image.format = COMPRESSED_ETC2_RGB; + else if (header.glInternalFormat == 0x9278) image.format = COMPRESSED_ETC2_EAC_RGBA; + } + + fclose(ktxFile); // Close file pointer + } + + return image; +} + +// Loading PVR image data (uncompressed or PVRT compression) +// NOTE: PVR v2 not supported, use PVR v3 instead +static Image LoadPVR(const char *fileName) +{ + // Required extension: + // GL_IMG_texture_compression_pvrtc + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 + // GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 + + // PVR file v2 Header (52 bytes) + typedef struct { + unsigned int headerLength; + unsigned int height; + unsigned int width; + unsigned int numMipmaps; + unsigned int flags; + unsigned int dataLength; + unsigned int bpp; + unsigned int bitmaskRed; + unsigned int bitmaskGreen; + unsigned int bitmaskBlue; + unsigned int bitmaskAlpha; + unsigned int pvrTag; + unsigned int numSurfs; + } pvrHeaderV2; + + // PVR file v3 Header (52 bytes) + // NOTE: After it could be metadata (15 bytes?) + typedef struct { + char id[4]; + unsigned int flags; + unsigned char channels[4]; // pixelFormat high part + unsigned char channelDepth[4]; // pixelFormat low part + unsigned int colourSpace; + unsigned int channelType; + unsigned int height; + unsigned int width; + unsigned int depth; + unsigned int numSurfaces; + unsigned int numFaces; + unsigned int numMipmaps; + unsigned int metaDataSize; + } pvrHeaderV3; + + // Metadata (usually 15 bytes) + typedef struct { + unsigned int devFOURCC; + unsigned int key; + unsigned int dataSize; // Not used? + unsigned char *data; // Not used? + } pvrMetadata; + + Image image; + + image.data = NULL; + image.width = 0; + image.height = 0; + image.mipmaps = 0; + image.format = 0; + + FILE *pvrFile = fopen(fileName, "rb"); + + if (pvrFile == NULL) + { + TraceLog(WARNING, "[%s] PVR file could not be opened", fileName); + } + else + { + // Check PVR image version + unsigned char pvrVersion = 0; + fread(&pvrVersion, sizeof(unsigned char), 1, pvrFile); + fseek(pvrFile, 0, SEEK_SET); + + // Load different PVR data formats + if (pvrVersion == 0x50) + { + pvrHeaderV3 header; + + // Get PVR image header + fread(&header, sizeof(pvrHeaderV3), 1, pvrFile); + + if ((header.id[0] != 'P') || (header.id[1] != 'V') || (header.id[2] != 'R') || (header.id[3] != 3)) + { + TraceLog(WARNING, "[%s] PVR file does not seem to be a valid image", fileName); + } + else + { + image.width = header.width; + image.height = header.height; + image.mipmaps = header.numMipmaps; + + // Check data format + if (((header.channels[0] == 'l') && (header.channels[1] == 0)) && (header.channelDepth[0] == 8)) image.format = UNCOMPRESSED_GRAYSCALE; + else if (((header.channels[0] == 'l') && (header.channels[1] == 'a')) && ((header.channelDepth[0] == 8) && (header.channelDepth[1] == 8))) image.format = UNCOMPRESSED_GRAY_ALPHA; + else if ((header.channels[0] == 'r') && (header.channels[1] == 'g') && (header.channels[2] == 'b')) + { + if (header.channels[3] == 'a') + { + if ((header.channelDepth[0] == 5) && (header.channelDepth[1] == 5) && (header.channelDepth[2] == 5) && (header.channelDepth[3] == 1)) image.format = UNCOMPRESSED_R5G5B5A1; + else if ((header.channelDepth[0] == 4) && (header.channelDepth[1] == 4) && (header.channelDepth[2] == 4) && (header.channelDepth[3] == 4)) image.format = UNCOMPRESSED_R4G4B4A4; + else if ((header.channelDepth[0] == 8) && (header.channelDepth[1] == 8) && (header.channelDepth[2] == 8) && (header.channelDepth[3] == 8)) image.format = UNCOMPRESSED_R8G8B8A8; + } + else if (header.channels[3] == 0) + { + if ((header.channelDepth[0] == 5) && (header.channelDepth[1] == 6) && (header.channelDepth[2] == 5)) image.format = UNCOMPRESSED_R5G6B5; + else if ((header.channelDepth[0] == 8) && (header.channelDepth[1] == 8) && (header.channelDepth[2] == 8)) image.format = UNCOMPRESSED_R8G8B8; + } + } + else if (header.channels[0] == 2) image.format = COMPRESSED_PVRT_RGB; + else if (header.channels[0] == 3) image.format = COMPRESSED_PVRT_RGBA; + + // Skip meta data header + unsigned char unused = 0; + for (int i = 0; i < header.metaDataSize; i++) fread(&unused, sizeof(unsigned char), 1, pvrFile); + + // Calculate data size (depends on format) + int bpp = 0; + + switch (image.format) + { + case UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case UNCOMPRESSED_GRAY_ALPHA: + case UNCOMPRESSED_R5G5B5A1: + case UNCOMPRESSED_R5G6B5: + case UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case UNCOMPRESSED_R8G8B8: bpp = 24; break; + case COMPRESSED_PVRT_RGB: + case COMPRESSED_PVRT_RGBA: bpp = 4; break; + default: break; + } + + int dataSize = image.width*image.height*bpp/8; // Total data size in bytes + image.data = (unsigned char*)malloc(dataSize*sizeof(unsigned char)); + + // Read data from file + fread(image.data, dataSize, 1, pvrFile); + } + } + else if (pvrVersion == 52) TraceLog(INFO, "PVR v2 not supported, update your files to PVR v3"); + + fclose(pvrFile); // Close file pointer + } + + return image; +} + +// Load ASTC compressed image data (ASTC compression) +static Image LoadASTC(const char *fileName) +{ + // Required extensions: + // GL_KHR_texture_compression_astc_hdr + // GL_KHR_texture_compression_astc_ldr + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 + // GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 + + // ASTC file Header (16 bytes) + typedef struct { + unsigned char id[4]; // Signature: 0x13 0xAB 0xA1 0x5C + unsigned char blockX; // Block X dimensions + unsigned char blockY; // Block Y dimensions + unsigned char blockZ; // Block Z dimensions (1 for 2D images) + unsigned char width[3]; // Image width in pixels (24bit value) + unsigned char height[3]; // Image height in pixels (24bit value) + unsigned char lenght[3]; // Image Z-size (1 for 2D images) + } astcHeader; + + Image image; + + image.data = NULL; + image.width = 0; + image.height = 0; + image.mipmaps = 0; + image.format = 0; + + FILE *astcFile = fopen(fileName, "rb"); + + if (astcFile == NULL) + { + TraceLog(WARNING, "[%s] ASTC file could not be opened", fileName); + } + else + { + astcHeader header; + + // Get ASTC image header + fread(&header, sizeof(astcHeader), 1, astcFile); + + if ((header.id[3] != 0x5c) || (header.id[2] != 0xa1) || (header.id[1] != 0xab) || (header.id[0] != 0x13)) + { + TraceLog(WARNING, "[%s] ASTC file does not seem to be a valid image", fileName); + } + else + { + // NOTE: Assuming Little Endian (could it be wrong?) + image.width = 0x00000000 | ((int)header.width[2] << 16) | ((int)header.width[1] << 8) | ((int)header.width[0]); + image.height = 0x00000000 | ((int)header.height[2] << 16) | ((int)header.height[1] << 8) | ((int)header.height[0]); + image.mipmaps = 1; + + TraceLog(DEBUG, "ASTC image width: %i", image.width); + TraceLog(DEBUG, "ASTC image height: %i", image.height); + TraceLog(DEBUG, "ASTC image blocks: %ix%i", header.blockX, header.blockY); + + // NOTE: Each block is always stored in 128bit so we can calculate the bpp + int bpp = 128/(header.blockX*header.blockY); + + // NOTE: Currently we only support 2 blocks configurations: 4x4 and 8x8 + if ((bpp == 8) || (bpp == 2)) + { + int dataSize = image.width*image.height*bpp/8; // Data size in bytes + + image.data = (unsigned char *)malloc(dataSize*sizeof(unsigned char)); + fread(image.data, dataSize, 1, astcFile); + + if (bpp == 8) image.format = COMPRESSED_ASTC_4x4_RGBA; + else if (bpp == 2) image.format = COMPRESSED_ASTC_4x4_RGBA; + } + else TraceLog(WARNING, "[%s] ASTC block size configuration not supported", fileName); + } + + fclose(astcFile); } return image; diff --git a/src/utils.c b/src/utils.c index c3c20b472..8e42e5333 100644 --- a/src/utils.c +++ b/src/utils.c @@ -79,7 +79,7 @@ unsigned char *DecompressData(const unsigned char *data, unsigned long compSize, pUncomp = (mz_uint8 *)malloc((size_t)uncompSize); // Check correct memory allocation - if (!pUncomp) + if (pUncomp == NULL) { TraceLog(WARNING, "Out of memory while decompressing data"); } @@ -235,18 +235,18 @@ void TraceLog(int msgType, const char *text, ...) } // Initialize asset manager from android app -void InitAssetManager(AAssetManager *manager) +void InitAssetManager(AAssetManager *manager) { assetManager = manager; } // Replacement for fopen -FILE *android_fopen(const char *fileName, const char *mode) +FILE *android_fopen(const char *fileName, const char *mode) { if (mode[0] == 'w') return NULL; AAsset *asset = AAssetManager_open(assetManager, fileName, 0); - + if(!asset) return NULL; return funopen(asset, android_read, android_write, android_seek, android_close); @@ -292,24 +292,24 @@ int GetNextPOT(int num) // Module specific Functions Definition //---------------------------------------------------------------------------------- #if defined(PLATFORM_ANDROID) -static int android_read(void *cookie, char *buf, int size) +static int android_read(void *cookie, char *buf, int size) { return AAsset_read((AAsset *)cookie, buf, size); } -static int android_write(void *cookie, const char *buf, int size) +static int android_write(void *cookie, const char *buf, int size) { TraceLog(ERROR, "Can't provide write access to the APK"); return EACCES; } -static fpos_t android_seek(void *cookie, fpos_t offset, int whence) +static fpos_t android_seek(void *cookie, fpos_t offset, int whence) { return AAsset_seek((AAsset *)cookie, offset, whence); } -static int android_close(void *cookie) +static int android_close(void *cookie) { AAsset_close((AAsset *)cookie); return 0; diff --git a/templates/advance_game/makefile b/templates/advance_game/makefile index cb482582c..80e9357c4 100644 --- a/templates/advance_game/makefile +++ b/templates/advance_game/makefile @@ -2,7 +2,7 @@ # # raylib - Advance Game # -# makefile to compile advance game +# makefile to compile advance game for desktop platforms, Raspberry Pi and HTML5 (emscripten) # # Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) # @@ -23,49 +23,133 @@ # #************************************************************************************************** -# define raylib platform if not defined (by default, compile for RPI) -# Other possible platform: PLATFORM_DESKTOP -PLATFORM ?= PLATFORM_RPI +# define raylib platform to compile for +# possible platforms: PLATFORM_DESKTOP PLATFORM_RPI PLATFORM_WEB +# WARNING: To compile to HTML5, code must be redesigned to use emscripten.h and emscripten_set_main_loop() +PLATFORM ?= PLATFORM_DESKTOP + +# determine PLATFORM_OS in case PLATFORM_DESKTOP selected +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + # No uname.exe on MinGW!, but OS=Windows_NT on Windows! ifeq ($(UNAME),Msys) -> Windows + ifeq ($(OS),Windows_NT) + PLATFORM_OS=WINDOWS + LIBPATH=win32 + else + UNAMEOS:=$(shell uname) + ifeq ($(UNAMEOS),Linux) + PLATFORM_OS=LINUX + LIBPATH=linux + else + ifeq ($(UNAMEOS),Darwin) + PLATFORM_OS=OSX + LIBPATH=osx + endif + endif + endif +endif # define compiler: gcc for C program, define as g++ for C++ -CC = gcc +ifeq ($(PLATFORM),PLATFORM_WEB) + # define emscripten compiler + CC = emcc +else +ifeq ($(PLATFORM_OS),OSX) + # define llvm compiler for mac + CC = clang +else + # define default gcc compiler + CC = gcc +endif +endif # define compiler flags: # -O2 defines optimization level # -Wall turns on most, but not all, compiler warnings # -std=c99 use standard C from 1999 revision -CFLAGS = -O2 -Wall -std=c99 +ifeq ($(PLATFORM),PLATFORM_RPI) + CFLAGS = -O2 -Wall -std=gnu99 -fgnu89-inline +else + CFLAGS = -O2 -Wall -std=c99 +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + CFLAGS = -O1 -Wall -std=c99 -s USE_GLFW=3 -s ASSERTIONS=1 --preload-file resources + #-s ALLOW_MEMORY_GROWTH=1 # to allow memory resizing + #-s TOTAL_MEMORY=16777216 # to specify heap memory size (default = 16MB) +endif + #CFLAGSEXTRA = -Wextra -Wmissing-prototypes -Wstrict-prototypes # define any directories containing required header files ifeq ($(PLATFORM),PLATFORM_RPI) - INCLUDES = -I. -I./screens -I../../src -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads + INCLUDES = -I. -I../../src -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads else - INCLUDES = -I. -I./screens -I../../src + INCLUDES = -I. -I../../src +# external libraries headers +# GLFW3 + INCLUDES += -I../../external/glfw3/include +# GLEW + INCLUDES += -I../../external/glew/include +# OpenAL Soft + INCLUDES += -I../../external/openal_soft/include endif # define library paths containing required libs -LFLAGS = -L. -L../../src -L/opt/vc/lib +ifeq ($(PLATFORM),PLATFORM_RPI) + LFLAGS = -L. -L../../src -L/opt/vc/lib +else + LFLAGS = -L. -L../../src + # external libraries to link with + # GLFW3 + LFLAGS += -L../../external/glfw3/lib/$(LIBPATH) + ifneq ($(PLATFORM_OS),OSX) + # OpenAL Soft + LFLAGS += -L../../external/openal_soft/lib/$(LIBPATH) + # GLEW + LFLAGS += -L../../external/glew/lib/$(LIBPATH) + endif +endif # define any libraries to link into executable # if you want to link libraries (libname.so or libname.a), use the -lname +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),LINUX) + # libraries for Debian GNU/Linux desktop compiling + # requires the following packages: + # libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw3 -lGLEW -lGL -lopenal -lX11 -lXrandr -lXinerama -lXi -lXxf86vm -lXcursor -lm -pthread + else + ifeq ($(PLATFORM_OS),OSX) + # libraries for OS X 10.9 desktop compiling + # requires the following packages: + # libglfw3-dev libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw -framework OpenGL -framework OpenAl -framework Cocoa + else + # libraries for Windows desktop compiling + # NOTE: GLFW3 and OpenAL Soft libraries should be installed + LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 + endif + endif +endif ifeq ($(PLATFORM),PLATFORM_RPI) # libraries for Raspberry Pi compiling # NOTE: OpenAL Soft library should be installed (libopenal1 package) LIBS = -lraylib -lGLESv2 -lEGL -lpthread -lrt -lm -lbcm_host -lopenal -else - # libraries for Windows desktop compiling - # NOTE: GLFW3 and OpenAL Soft libraries should be installed - LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + LIBS = ../../src/libraylib.bc endif # define additional parameters and flags for windows -ifeq ($(PLATFORM),PLATFORM_DESKTOP) +ifeq ($(PLATFORM_OS),WINDOWS) # resources file contains windows exe icon # -Wl,--subsystem,windows hides the console window WINFLAGS = ../../src/resources -Wl,--subsystem,windows endif +ifeq ($(PLATFORM),PLATFORM_WEB) + EXT = .html +endif + # define all screen object files required SCREENS = \ screens/screen_logo.o \ @@ -80,7 +164,7 @@ default: advance_game # compile template - advance_game advance_game: advance_game.c $(SCREENS) - $(CC) -o $@ $< $(SCREENS) $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) $(WINFLAGS) + $(CC) -o $@$(EXT) $< $(SCREENS) $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) $(WINFLAGS) # compile screen LOGO screens/screen_logo.o: screens/screen_logo.c @@ -104,11 +188,24 @@ screens/screen_ending.o: screens/screen_ending.c # clean everything clean: +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),OSX) + find . -type f -perm +ugo+x -delete + rm -f *.o + else + ifeq ($(PLATFORM_OS),LINUX) + find -type f -executable | xargs file -i | grep -E 'x-object|x-archive|x-sharedlib|x-executable' | rev | cut -d ':' -f 2- | rev | xargs rm -f + else + del *.o *.exe + endif + endif +endif ifeq ($(PLATFORM),PLATFORM_RPI) - rm -f screens/*.o -# find . -executable -delete -else - del screens/*.o *.exe + find . -type f -executable -delete + rm -f *.o +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + del *.o *.html *.js endif @echo Cleaning done diff --git a/templates/advance_game/screens/screen_ending.o b/templates/advance_game/screens/screen_ending.o deleted file mode 100644 index 5d819b161..000000000 Binary files a/templates/advance_game/screens/screen_ending.o and /dev/null differ diff --git a/templates/advance_game/screens/screen_gameplay.o b/templates/advance_game/screens/screen_gameplay.o deleted file mode 100644 index b31cccb31..000000000 Binary files a/templates/advance_game/screens/screen_gameplay.o and /dev/null differ diff --git a/templates/advance_game/screens/screen_logo.o b/templates/advance_game/screens/screen_logo.o deleted file mode 100644 index 10a997460..000000000 Binary files a/templates/advance_game/screens/screen_logo.o and /dev/null differ diff --git a/templates/advance_game/screens/screen_options.o b/templates/advance_game/screens/screen_options.o deleted file mode 100644 index 17153fd93..000000000 Binary files a/templates/advance_game/screens/screen_options.o and /dev/null differ diff --git a/templates/advance_game/screens/screen_title.o b/templates/advance_game/screens/screen_title.o deleted file mode 100644 index cbe172970..000000000 Binary files a/templates/advance_game/screens/screen_title.o and /dev/null differ diff --git a/templates/basic_game/makefile b/templates/basic_game/makefile index afb157c14..5ce3aa3e9 100644 --- a/templates/basic_game/makefile +++ b/templates/basic_game/makefile @@ -2,7 +2,7 @@ # # raylib - Basic Game # -# makefile to compile basic game +# makefile to compile basic game for desktop platforms, Raspberry Pi and HTML5 (emscripten) # # Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) # @@ -23,18 +23,60 @@ # #************************************************************************************************** -# define raylib platform (by default, compile for RPI) -# Other possible platform: PLATFORM_DESKTOP -PLATFORM ?= PLATFORM_RPI +# define raylib platform to compile for +# possible platforms: PLATFORM_DESKTOP PLATFORM_RPI PLATFORM_WEB +# WARNING: To compile to HTML5, code must be redesigned to use emscripten.h and emscripten_set_main_loop() +PLATFORM ?= PLATFORM_DESKTOP + +# determine PLATFORM_OS in case PLATFORM_DESKTOP selected +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + # No uname.exe on MinGW!, but OS=Windows_NT on Windows! ifeq ($(UNAME),Msys) -> Windows + ifeq ($(OS),Windows_NT) + PLATFORM_OS=WINDOWS + LIBPATH=win32 + else + UNAMEOS:=$(shell uname) + ifeq ($(UNAMEOS),Linux) + PLATFORM_OS=LINUX + LIBPATH=linux + else + ifeq ($(UNAMEOS),Darwin) + PLATFORM_OS=OSX + LIBPATH=osx + endif + endif + endif +endif # define compiler: gcc for C program, define as g++ for C++ -CC = gcc +ifeq ($(PLATFORM),PLATFORM_WEB) + # define emscripten compiler + CC = emcc +else +ifeq ($(PLATFORM_OS),OSX) + # define llvm compiler for mac + CC = clang +else + # define default gcc compiler + CC = gcc +endif +endif # define compiler flags: # -O2 defines optimization level # -Wall turns on most, but not all, compiler warnings # -std=c99 use standard C from 1999 revision -CFLAGS = -O2 -Wall -std=c99 +ifeq ($(PLATFORM),PLATFORM_RPI) + CFLAGS = -O2 -Wall -std=gnu99 -fgnu89-inline +else + CFLAGS = -O2 -Wall -std=c99 +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + CFLAGS = -O1 -Wall -std=c99 -s USE_GLFW=3 -s ASSERTIONS=1 --preload-file resources + #-s ALLOW_MEMORY_GROWTH=1 # to allow memory resizing + #-s TOTAL_MEMORY=16777216 # to specify heap memory size (default = 16MB) +endif + #CFLAGSEXTRA = -Wextra -Wmissing-prototypes -Wstrict-prototypes # define any directories containing required header files @@ -42,45 +84,100 @@ ifeq ($(PLATFORM),PLATFORM_RPI) INCLUDES = -I. -I../../src -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads else INCLUDES = -I. -I../../src +# external libraries headers +# GLFW3 + INCLUDES += -I../../external/glfw3/include +# GLEW + INCLUDES += -I../../external/glew/include +# OpenAL Soft + INCLUDES += -I../../external/openal_soft/include endif # define library paths containing required libs -LFLAGS = -L. -L../../src -L/opt/vc/lib +ifeq ($(PLATFORM),PLATFORM_RPI) + LFLAGS = -L. -L../../src -L/opt/vc/lib +else + LFLAGS = -L. -L../../src + # external libraries to link with + # GLFW3 + LFLAGS += -L../../external/glfw3/lib/$(LIBPATH) + ifneq ($(PLATFORM_OS),OSX) + # OpenAL Soft + LFLAGS += -L../../external/openal_soft/lib/$(LIBPATH) + # GLEW + LFLAGS += -L../../external/glew/lib/$(LIBPATH) + endif +endif # define any libraries to link into executable # if you want to link libraries (libname.so or libname.a), use the -lname +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),LINUX) + # libraries for Debian GNU/Linux desktop compiling + # requires the following packages: + # libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw3 -lGLEW -lGL -lopenal -lX11 -lXrandr -lXinerama -lXi -lXxf86vm -lXcursor -lm -pthread + else + ifeq ($(PLATFORM_OS),OSX) + # libraries for OS X 10.9 desktop compiling + # requires the following packages: + # libglfw3-dev libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw -framework OpenGL -framework OpenAl -framework Cocoa + else + # libraries for Windows desktop compiling + # NOTE: GLFW3 and OpenAL Soft libraries should be installed + LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 + endif + endif +endif ifeq ($(PLATFORM),PLATFORM_RPI) # libraries for Raspberry Pi compiling # NOTE: OpenAL Soft library should be installed (libopenal1 package) LIBS = -lraylib -lGLESv2 -lEGL -lpthread -lrt -lm -lbcm_host -lopenal -else - # libraries for Windows desktop compiling - # NOTE: GLFW3 and OpenAL Soft libraries should be installed - LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + LIBS = ../../src/libraylib.bc endif # define additional parameters and flags for windows -ifeq ($(PLATFORM),PLATFORM_DESKTOP) +ifeq ($(PLATFORM_OS),WINDOWS) # resources file contains windows exe icon # -Wl,--subsystem,windows hides the console window WINFLAGS = ../../src/resources -Wl,--subsystem,windows endif +ifeq ($(PLATFORM),PLATFORM_WEB) + EXT = .html +endif + # typing 'make' will invoke the first target entry in the file, # in this case, the 'default' target entry is basic_game default: basic_game # compile template - basic_game basic_game: basic_game.c - $(CC) -o $@ $< $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) $(WINFLAGS) + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) $(WINFLAGS) # clean everything clean: +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),OSX) + find . -type f -perm +ugo+x -delete + rm -f *.o + else + ifeq ($(PLATFORM_OS),LINUX) + find -type f -executable | xargs file -i | grep -E 'x-object|x-archive|x-sharedlib|x-executable' | rev | cut -d ':' -f 2- | rev | xargs rm -f + else + del *.o *.exe + endif + endif +endif ifeq ($(PLATFORM),PLATFORM_RPI) + find . -type f -executable -delete rm -f *.o -# find . -executable -delete -else - del *.o *.exe +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + del *.o *.html *.js endif @echo Cleaning done diff --git a/templates/basic_test/makefile b/templates/basic_test/makefile index 8b2a1fd12..cf763628a 100644 --- a/templates/basic_test/makefile +++ b/templates/basic_test/makefile @@ -2,7 +2,7 @@ # # raylib - Basic Test # -# makefile to compile basic test +# makefile to compile basic test for desktop platforms, Raspberry Pi and HTML5 (emscripten) # # Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) # @@ -23,18 +23,60 @@ # #************************************************************************************************** -# define raylib platform if not defined (by default, compile for RPI) -# Other possible platform: PLATFORM_DESKTOP -PLATFORM ?= PLATFORM_RPI +# define raylib platform to compile for +# possible platforms: PLATFORM_DESKTOP PLATFORM_RPI PLATFORM_WEB +# WARNING: To compile to HTML5, code must be redesigned to use emscripten.h and emscripten_set_main_loop() +PLATFORM ?= PLATFORM_DESKTOP + +# determine PLATFORM_OS in case PLATFORM_DESKTOP selected +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + # No uname.exe on MinGW!, but OS=Windows_NT on Windows! ifeq ($(UNAME),Msys) -> Windows + ifeq ($(OS),Windows_NT) + PLATFORM_OS=WINDOWS + LIBPATH=win32 + else + UNAMEOS:=$(shell uname) + ifeq ($(UNAMEOS),Linux) + PLATFORM_OS=LINUX + LIBPATH=linux + else + ifeq ($(UNAMEOS),Darwin) + PLATFORM_OS=OSX + LIBPATH=osx + endif + endif + endif +endif # define compiler: gcc for C program, define as g++ for C++ -CC = gcc +ifeq ($(PLATFORM),PLATFORM_WEB) + # define emscripten compiler + CC = emcc +else +ifeq ($(PLATFORM_OS),OSX) + # define llvm compiler for mac + CC = clang +else + # define default gcc compiler + CC = gcc +endif +endif # define compiler flags: # -O2 defines optimization level # -Wall turns on most, but not all, compiler warnings # -std=c99 use standard C from 1999 revision -CFLAGS = -O2 -Wall -std=c99 +ifeq ($(PLATFORM),PLATFORM_RPI) + CFLAGS = -O2 -Wall -std=gnu99 -fgnu89-inline +else + CFLAGS = -O2 -Wall -std=c99 +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + CFLAGS = -O1 -Wall -std=c99 -s USE_GLFW=3 -s ASSERTIONS=1 --preload-file resources + #-s ALLOW_MEMORY_GROWTH=1 # to allow memory resizing + #-s TOTAL_MEMORY=16777216 # to specify heap memory size (default = 16MB) +endif + #CFLAGSEXTRA = -Wextra -Wmissing-prototypes -Wstrict-prototypes # define any directories containing required header files @@ -42,45 +84,100 @@ ifeq ($(PLATFORM),PLATFORM_RPI) INCLUDES = -I. -I../../src -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads else INCLUDES = -I. -I../../src +# external libraries headers +# GLFW3 + INCLUDES += -I../../external/glfw3/include +# GLEW + INCLUDES += -I../../external/glew/include +# OpenAL Soft + INCLUDES += -I../../external/openal_soft/include endif # define library paths containing required libs -LFLAGS = -L. -L../../src -L/opt/vc/lib +ifeq ($(PLATFORM),PLATFORM_RPI) + LFLAGS = -L. -L../../src -L/opt/vc/lib +else + LFLAGS = -L. -L../../src + # external libraries to link with + # GLFW3 + LFLAGS += -L../../external/glfw3/lib/$(LIBPATH) + ifneq ($(PLATFORM_OS),OSX) + # OpenAL Soft + LFLAGS += -L../../external/openal_soft/lib/$(LIBPATH) + # GLEW + LFLAGS += -L../../external/glew/lib/$(LIBPATH) + endif +endif # define any libraries to link into executable # if you want to link libraries (libname.so or libname.a), use the -lname +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),LINUX) + # libraries for Debian GNU/Linux desktop compiling + # requires the following packages: + # libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw3 -lGLEW -lGL -lopenal -lX11 -lXrandr -lXinerama -lXi -lXxf86vm -lXcursor -lm -pthread + else + ifeq ($(PLATFORM_OS),OSX) + # libraries for OS X 10.9 desktop compiling + # requires the following packages: + # libglfw3-dev libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw -framework OpenGL -framework OpenAl -framework Cocoa + else + # libraries for Windows desktop compiling + # NOTE: GLFW3 and OpenAL Soft libraries should be installed + LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 + endif + endif +endif ifeq ($(PLATFORM),PLATFORM_RPI) # libraries for Raspberry Pi compiling # NOTE: OpenAL Soft library should be installed (libopenal1 package) LIBS = -lraylib -lGLESv2 -lEGL -lpthread -lrt -lm -lbcm_host -lopenal -else - # libraries for Windows desktop compiling - # NOTE: GLFW3 and OpenAL Soft libraries should be installed - LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + LIBS = ../../src/libraylib.bc endif # define additional parameters and flags for windows -ifeq ($(PLATFORM),PLATFORM_DESKTOP) +ifeq ($(PLATFORM_OS),WINDOWS) # resources file contains windows exe icon # -Wl,--subsystem,windows hides the console window WINFLAGS = ../../src/resources -Wl,--subsystem,windows endif +ifeq ($(PLATFORM),PLATFORM_WEB) + EXT = .html +endif + # typing 'make' will invoke the first target entry in the file, # in this case, the 'default' target entry is basic_test default: basic_test # compile template - basic_test basic_test: basic_test.c - $(CC) -o $@ $< $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) $(WINFLAGS) + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) $(WINFLAGS) # clean everything clean: +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),OSX) + find . -type f -perm +ugo+x -delete + rm -f *.o + else + ifeq ($(PLATFORM_OS),LINUX) + find -type f -executable | xargs file -i | grep -E 'x-object|x-archive|x-sharedlib|x-executable' | rev | cut -d ':' -f 2- | rev | xargs rm -f + else + del *.o *.exe + endif + endif +endif ifeq ($(PLATFORM),PLATFORM_RPI) + find . -type f -executable -delete rm -f *.o -# find . -executable -delete -else - del *.o *.exe +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + del *.o *.html *.js endif @echo Cleaning done diff --git a/templates/simple_game/makefile b/templates/simple_game/makefile index e5c2b2c26..586233a7e 100644 --- a/templates/simple_game/makefile +++ b/templates/simple_game/makefile @@ -2,7 +2,7 @@ # # raylib - Simple Game # -# makefile to compile simple game +# makefile to compile simple game for desktop platforms, Raspberry Pi and HTML5 (emscripten) # # Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) # @@ -23,12 +23,44 @@ # #************************************************************************************************** -# define raylib platform (by default, compile for RPI) -# Other possible platform: PLATFORM_DESKTOP -PLATFORM ?= PLATFORM_RPI +# define raylib platform to compile for +# possible platforms: PLATFORM_DESKTOP PLATFORM_RPI PLATFORM_WEB +# WARNING: To compile to HTML5, code must be redesigned to use emscripten.h and emscripten_set_main_loop() +PLATFORM ?= PLATFORM_DESKTOP + +# determine PLATFORM_OS in case PLATFORM_DESKTOP selected +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + # No uname.exe on MinGW!, but OS=Windows_NT on Windows! ifeq ($(UNAME),Msys) -> Windows + ifeq ($(OS),Windows_NT) + PLATFORM_OS=WINDOWS + LIBPATH=win32 + else + UNAMEOS:=$(shell uname) + ifeq ($(UNAMEOS),Linux) + PLATFORM_OS=LINUX + LIBPATH=linux + else + ifeq ($(UNAMEOS),Darwin) + PLATFORM_OS=OSX + LIBPATH=osx + endif + endif + endif +endif # define compiler: gcc for C program, define as g++ for C++ -CC = gcc +ifeq ($(PLATFORM),PLATFORM_WEB) + # define emscripten compiler + CC = emcc +else +ifeq ($(PLATFORM_OS),OSX) + # define llvm compiler for mac + CC = clang +else + # define default gcc compiler + CC = gcc +endif +endif # define compiler flags: # -O2 defines optimization level @@ -39,6 +71,12 @@ ifeq ($(PLATFORM),PLATFORM_RPI) else CFLAGS = -O2 -Wall -std=c99 endif +ifeq ($(PLATFORM),PLATFORM_WEB) + CFLAGS = -O1 -Wall -std=c99 -s USE_GLFW=3 -s ASSERTIONS=1 --preload-file resources + #-s ALLOW_MEMORY_GROWTH=1 # to allow memory resizing + #-s TOTAL_MEMORY=16777216 # to specify heap memory size (default = 16MB) +endif + #CFLAGSEXTRA = -Wextra -Wmissing-prototypes -Wstrict-prototypes # define any directories containing required header files @@ -46,37 +84,79 @@ ifeq ($(PLATFORM),PLATFORM_RPI) INCLUDES = -I. -I../../src -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads else INCLUDES = -I. -I../../src +# external libraries headers +# GLFW3 + INCLUDES += -I../../external/glfw3/include +# GLEW + INCLUDES += -I../../external/glew/include +# OpenAL Soft + INCLUDES += -I../../external/openal_soft/include endif # define library paths containing required libs -LFLAGS = -L. -L../../src -L/opt/vc/lib +ifeq ($(PLATFORM),PLATFORM_RPI) + LFLAGS = -L. -L../../src -L/opt/vc/lib +else + LFLAGS = -L. -L../../src + # external libraries to link with + # GLFW3 + LFLAGS += -L../../external/glfw3/lib/$(LIBPATH) + ifneq ($(PLATFORM_OS),OSX) + # OpenAL Soft + LFLAGS += -L../../external/openal_soft/lib/$(LIBPATH) + # GLEW + LFLAGS += -L../../external/glew/lib/$(LIBPATH) + endif +endif # define any libraries to link into executable # if you want to link libraries (libname.so or libname.a), use the -lname +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),LINUX) + # libraries for Debian GNU/Linux desktop compiling + # requires the following packages: + # libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw3 -lGLEW -lGL -lopenal -lX11 -lXrandr -lXinerama -lXi -lXxf86vm -lXcursor -lm -pthread + else + ifeq ($(PLATFORM_OS),OSX) + # libraries for OS X 10.9 desktop compiling + # requires the following packages: + # libglfw3-dev libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw -framework OpenGL -framework OpenAl -framework Cocoa + else + # libraries for Windows desktop compiling + # NOTE: GLFW3 and OpenAL Soft libraries should be installed + LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 + endif + endif +endif ifeq ($(PLATFORM),PLATFORM_RPI) # libraries for Raspberry Pi compiling # NOTE: OpenAL Soft library should be installed (libopenal1 package) LIBS = -lraylib -lGLESv2 -lEGL -lpthread -lrt -lm -lbcm_host -lopenal -else - # libraries for Windows desktop compiling - # NOTE: GLFW3 and OpenAL Soft libraries should be installed - LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + LIBS = ../../src/libraylib.bc endif # define additional parameters and flags for windows -ifeq ($(PLATFORM),PLATFORM_DESKTOP) +ifeq ($(PLATFORM_OS),WINDOWS) # resources file contains windows exe icon # -Wl,--subsystem,windows hides the console window WINFLAGS = ../../src/resources -Wl,--subsystem,windows endif +ifeq ($(PLATFORM),PLATFORM_WEB) + EXT = .html +endif + # typing 'make' will invoke the first target entry in the file, # in this case, the 'default' target entry is simple_game default: simple_game # compile template - simple_game simple_game: simple_game.c screens.o - $(CC) -o $@ $< screens.o $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) $(WINFLAGS) + $(CC) -o $@$(EXT) $< screens.o $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) $(WINFLAGS) # compile screens screens.o: screens.c @@ -84,11 +164,24 @@ screens.o: screens.c # clean everything clean: +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),OSX) + find . -type f -perm +ugo+x -delete + rm -f *.o + else + ifeq ($(PLATFORM_OS),LINUX) + find -type f -executable | xargs file -i | grep -E 'x-object|x-archive|x-sharedlib|x-executable' | rev | cut -d ':' -f 2- | rev | xargs rm -f + else + del *.o *.exe + endif + endif +endif ifeq ($(PLATFORM),PLATFORM_RPI) + find . -type f -executable -delete rm -f *.o -# find . -executable -delete -else - del *.o *.exe +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + del *.o *.html *.js endif @echo Cleaning done diff --git a/templates/simple_game/screens.o b/templates/simple_game/screens.o deleted file mode 100644 index 7ed0860c0..000000000 Binary files a/templates/simple_game/screens.o and /dev/null differ diff --git a/templates/standard_game/makefile b/templates/standard_game/makefile index 8a5a7b55b..cdb42f537 100644 --- a/templates/standard_game/makefile +++ b/templates/standard_game/makefile @@ -2,7 +2,7 @@ # # raylib - Standard Game # -# makefile to compile standard game +# makefile to compile standard game for desktop platforms, Raspberry Pi and HTML5 (emscripten) # # Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) # @@ -23,49 +23,133 @@ # #************************************************************************************************** -# define raylib platform if not defined (by default, compile for RPI) -# Other possible platform: PLATFORM_DESKTOP -PLATFORM ?= PLATFORM_RPI +# define raylib platform to compile for +# possible platforms: PLATFORM_DESKTOP PLATFORM_RPI PLATFORM_WEB +# WARNING: To compile to HTML5, code must be redesigned to use emscripten.h and emscripten_set_main_loop() +PLATFORM ?= PLATFORM_DESKTOP + +# determine PLATFORM_OS in case PLATFORM_DESKTOP selected +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + # No uname.exe on MinGW!, but OS=Windows_NT on Windows! ifeq ($(UNAME),Msys) -> Windows + ifeq ($(OS),Windows_NT) + PLATFORM_OS=WINDOWS + LIBPATH=win32 + else + UNAMEOS:=$(shell uname) + ifeq ($(UNAMEOS),Linux) + PLATFORM_OS=LINUX + LIBPATH=linux + else + ifeq ($(UNAMEOS),Darwin) + PLATFORM_OS=OSX + LIBPATH=osx + endif + endif + endif +endif # define compiler: gcc for C program, define as g++ for C++ -CC = gcc +ifeq ($(PLATFORM),PLATFORM_WEB) + # define emscripten compiler + CC = emcc +else +ifeq ($(PLATFORM_OS),OSX) + # define llvm compiler for mac + CC = clang +else + # define default gcc compiler + CC = gcc +endif +endif # define compiler flags: # -O2 defines optimization level # -Wall turns on most, but not all, compiler warnings # -std=c99 use standard C from 1999 revision -CFLAGS = -O2 -Wall -std=c99 +ifeq ($(PLATFORM),PLATFORM_RPI) + CFLAGS = -O2 -Wall -std=gnu99 -fgnu89-inline +else + CFLAGS = -O2 -Wall -std=c99 +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + CFLAGS = -O1 -Wall -std=c99 -s USE_GLFW=3 -s ASSERTIONS=1 --preload-file resources + #-s ALLOW_MEMORY_GROWTH=1 # to allow memory resizing + #-s TOTAL_MEMORY=16777216 # to specify heap memory size (default = 16MB) +endif + #CFLAGSEXTRA = -Wextra -Wmissing-prototypes -Wstrict-prototypes # define any directories containing required header files ifeq ($(PLATFORM),PLATFORM_RPI) - INCLUDES = -I. -I./screens -I../../src -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads + INCLUDES = -I. -I../../src -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads else - INCLUDES = -I. -I./screens -I../../src + INCLUDES = -I. -I../../src +# external libraries headers +# GLFW3 + INCLUDES += -I../../external/glfw3/include +# GLEW + INCLUDES += -I../../external/glew/include +# OpenAL Soft + INCLUDES += -I../../external/openal_soft/include endif # define library paths containing required libs -LFLAGS = -L. -L../../src -L/opt/vc/lib +ifeq ($(PLATFORM),PLATFORM_RPI) + LFLAGS = -L. -L../../src -L/opt/vc/lib +else + LFLAGS = -L. -L../../src + # external libraries to link with + # GLFW3 + LFLAGS += -L../../external/glfw3/lib/$(LIBPATH) + ifneq ($(PLATFORM_OS),OSX) + # OpenAL Soft + LFLAGS += -L../../external/openal_soft/lib/$(LIBPATH) + # GLEW + LFLAGS += -L../../external/glew/lib/$(LIBPATH) + endif +endif # define any libraries to link into executable # if you want to link libraries (libname.so or libname.a), use the -lname +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),LINUX) + # libraries for Debian GNU/Linux desktop compiling + # requires the following packages: + # libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw3 -lGLEW -lGL -lopenal -lX11 -lXrandr -lXinerama -lXi -lXxf86vm -lXcursor -lm -pthread + else + ifeq ($(PLATFORM_OS),OSX) + # libraries for OS X 10.9 desktop compiling + # requires the following packages: + # libglfw3-dev libopenal-dev libglew-dev libegl1-mesa-dev + LIBS = -lraylib -lglfw -framework OpenGL -framework OpenAl -framework Cocoa + else + # libraries for Windows desktop compiling + # NOTE: GLFW3 and OpenAL Soft libraries should be installed + LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 + endif + endif +endif ifeq ($(PLATFORM),PLATFORM_RPI) # libraries for Raspberry Pi compiling # NOTE: OpenAL Soft library should be installed (libopenal1 package) LIBS = -lraylib -lGLESv2 -lEGL -lpthread -lrt -lm -lbcm_host -lopenal -else - # libraries for Windows desktop compiling - # NOTE: GLFW3 and OpenAL Soft libraries should be installed - LIBS = -lraylib -lglfw3 -lglew32 -lopengl32 -lopenal32 -lgdi32 +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + LIBS = ../../src/libraylib.bc endif # define additional parameters and flags for windows -ifeq ($(PLATFORM),PLATFORM_DESKTOP) +ifeq ($(PLATFORM_OS),WINDOWS) # resources file contains windows exe icon # -Wl,--subsystem,windows hides the console window WINFLAGS = ../../src/resources -Wl,--subsystem,windows endif +ifeq ($(PLATFORM),PLATFORM_WEB) + EXT = .html +endif + # define all screen object files required SCREENS = \ screens/screen_logo.o \ @@ -80,7 +164,7 @@ default: standard_game # compile template - standard_game standard_game: standard_game.c $(SCREENS) - $(CC) -o $@ $< $(SCREENS) $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) $(WINFLAGS) + $(CC) -o $@$(EXT) $< $(SCREENS) $(CFLAGS) $(INCLUDES) $(LFLAGS) $(LIBS) -D$(PLATFORM) $(WINFLAGS) # compile screen LOGO screens/screen_logo.o: screens/screen_logo.c @@ -104,11 +188,24 @@ screens/screen_ending.o: screens/screen_ending.c # clean everything clean: +ifeq ($(PLATFORM),PLATFORM_DESKTOP) + ifeq ($(PLATFORM_OS),OSX) + find . -type f -perm +ugo+x -delete + rm -f *.o + else + ifeq ($(PLATFORM_OS),LINUX) + find -type f -executable | xargs file -i | grep -E 'x-object|x-archive|x-sharedlib|x-executable' | rev | cut -d ':' -f 2- | rev | xargs rm -f + else + del *.o *.exe + endif + endif +endif ifeq ($(PLATFORM),PLATFORM_RPI) - rm -f screens/*.o -# find . -executable -delete -else - del screens/*.o *.exe + find . -type f -executable -delete + rm -f *.o +endif +ifeq ($(PLATFORM),PLATFORM_WEB) + del *.o *.html *.js endif @echo Cleaning done diff --git a/templates/standard_game/screens/screen_ending.o b/templates/standard_game/screens/screen_ending.o deleted file mode 100644 index 5d819b161..000000000 Binary files a/templates/standard_game/screens/screen_ending.o and /dev/null differ diff --git a/templates/standard_game/screens/screen_gameplay.o b/templates/standard_game/screens/screen_gameplay.o deleted file mode 100644 index b31cccb31..000000000 Binary files a/templates/standard_game/screens/screen_gameplay.o and /dev/null differ diff --git a/templates/standard_game/screens/screen_logo.o b/templates/standard_game/screens/screen_logo.o deleted file mode 100644 index 10a997460..000000000 Binary files a/templates/standard_game/screens/screen_logo.o and /dev/null differ diff --git a/templates/standard_game/screens/screen_options.o b/templates/standard_game/screens/screen_options.o deleted file mode 100644 index 17153fd93..000000000 Binary files a/templates/standard_game/screens/screen_options.o and /dev/null differ diff --git a/templates/standard_game/screens/screen_title.o b/templates/standard_game/screens/screen_title.o deleted file mode 100644 index cbe172970..000000000 Binary files a/templates/standard_game/screens/screen_title.o and /dev/null differ