packages feed

h-raylib 4.5.0.9 → 4.5.0.10

raw patch · 24 files changed

+965/−320 lines, 24 filesdep +h-raylibdep ~basenew-component:exe:basic-windownew-component:exe:camera-ray-collisionnew-component:exe:custom-font-textnew-component:exe:first-person-cameraPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: h-raylib

Dependency ranges changed: base

API changes (from Hackage documentation)

- Raylib: c'drawCubeTexture :: Ptr Texture -> Ptr Vector3 -> CFloat -> CFloat -> CFloat -> Ptr Color -> IO ()
- Raylib: c'drawCubeTextureRec :: Ptr Texture -> Ptr Rectangle -> Ptr Vector3 -> CFloat -> CFloat -> CFloat -> Ptr Color -> IO ()
- Raylib: drawCubeTexture :: Texture -> Vector3 -> Float -> Float -> Float -> Color -> IO ()
- Raylib: drawCubeTextureRec :: Texture -> Rectangle -> Vector3 -> Float -> Float -> Float -> Color -> IO ()
- Raylib: p'drawCubeTexture :: FunPtr (Texture -> Vector3 -> CFloat -> CFloat -> CFloat -> Color -> IO ())
- Raylib: p'drawCubeTextureRec :: FunPtr (Texture -> Rectangle -> Vector3 -> CFloat -> CFloat -> CFloat -> Color -> IO ())
+ Raylib: c'colorBrightness :: Ptr Color -> CFloat -> IO (Ptr Color)
+ Raylib: c'colorContrast :: Ptr Color -> CFloat -> IO (Ptr Color)
+ Raylib: c'colorTint :: Ptr Color -> Ptr Color -> IO (Ptr Color)
+ Raylib: colorBrightness :: Color -> Float -> Color
+ Raylib: colorContrast :: Color -> Float -> Color
+ Raylib: colorTint :: Color -> Color -> Color
+ Raylib: p'colorBrightness :: FunPtr (Color -> CFloat -> IO Color)
+ Raylib: p'colorContrast :: FunPtr (Color -> CFloat -> IO Color)
+ Raylib: p'colorTint :: FunPtr (Color -> Color -> IO Color)

Files

CHANGELOG.md view
@@ -37,3 +37,9 @@ _23 December 2022_
 
 - Changed `setConfigFlags` and `setGesturesEnabled` to use an array of flags
+
+## Version 4.5.0.10
+_5 January 2023_
+
+- Restructured to make the examples easier to run
+- Updated raylib to the master branch
README.md view
@@ -79,7 +79,7 @@ Anybody willing to contribute to the project is welcome to do so. This
 library is very new, so please report any bugs in the GitHub issue tracker.
 
-You can run the examples by using `cabal run` in the example project directory.
+You can run the examples by using `cabal run {example name}` in the project directory.
 
 ## License
 
+ examples/basic-window/src/Main.hs view
@@ -0,0 +1,34 @@+{-# OPTIONS -Wall #-}
+module Main where
+
+import Control.Monad (unless)
+import Raylib
+  ( beginDrawing,
+    clearBackground,
+    closeWindow,
+    drawText,
+    endDrawing,
+    initWindow,
+    setTargetFPS,
+    windowShouldClose,
+  )
+import Raylib.Colors (lightGray, rayWhite)
+
+main :: IO ()
+main = do
+  initWindow 600 450 "raylib example - basic window"
+  setTargetFPS 60
+  gameLoop
+  closeWindow
+
+gameLoop :: IO ()
+gameLoop = do
+  beginDrawing
+
+  clearBackground rayWhite
+  drawText "Basic raylib window" 30 40 18 lightGray
+
+  endDrawing
+
+  shouldClose <- windowShouldClose
+  unless shouldClose gameLoop
+ examples/camera-ray-collision/src/Main.hs view
@@ -0,0 +1,56 @@+{-# OPTIONS -Wall #-}
+module Main where
+
+import Control.Monad (unless, when)
+import Raylib
+  ( beginDrawing,
+    beginMode3D,
+    clearBackground,
+    closeWindow,
+    drawFPS,
+    endDrawing,
+    endMode3D,
+    initWindow,
+    setCameraMode,
+    setTargetFPS,
+    updateCamera,
+    windowShouldClose, getRayCollisionQuad, drawBoundingBox, drawPoint3D
+  )
+import Raylib.Colors (black, white, red)
+import Raylib.Types (Camera3D (Camera3D, camera3D'position, camera3D'target), Vector3 (Vector3), Ray (Ray), BoundingBox (BoundingBox), RayCollision (rayCollision'point, rayCollision'hit), CameraProjection (CameraPerspective), CameraMode (CameraModeFirstPerson))
+import Foreign (toBool)
+
+main :: IO ()
+main = do
+  initWindow 600 450 "raylib example - camera ray collision"
+  let camera = Camera3D (Vector3 0 0 0) (Vector3 2 0 1) (Vector3 0 1 0) 70 CameraPerspective
+  setCameraMode camera CameraModeFirstPerson
+  setTargetFPS 60
+  gameLoop camera
+  closeWindow
+
+gameLoop :: Camera3D -> IO ()
+gameLoop camera = do
+  beginDrawing
+
+  clearBackground black
+  drawFPS 10 20
+
+  let ray = Ray (camera3D'position camera) (camera3D'target camera -.- camera3D'position camera)
+  let collision = getRayCollisionQuad ray (Vector3 0 0 0) (Vector3 0 2 0) (Vector3 0 2 4) (Vector3 0 0 4)
+  let col = if toBool (rayCollision'hit collision) then red else white
+
+  beginMode3D camera
+
+  drawBoundingBox (BoundingBox (Vector3 0 0 0) (Vector3 0 2 4)) col
+  when (rayCollision'hit collision > 0) $ drawPoint3D (rayCollision'point collision) red
+
+  endMode3D
+
+  endDrawing
+  newCam <- updateCamera camera
+  shouldClose <- windowShouldClose
+  unless shouldClose $ gameLoop newCam
+
+(-.-) :: Vector3 -> Vector3 -> Vector3
+(Vector3 x1 x2 x3) -.- (Vector3 y1 y2 y3) = Vector3 (x1 - y1) (x2 - y2) (x3 - y3)
+ examples/custom-font-text/src/Main.hs view
@@ -0,0 +1,45 @@+{-# OPTIONS -Wall #-}
+module Main where
+
+import Control.Monad ( unless )
+import Raylib
+    ( beginDrawing,
+      clearBackground,
+      drawTextEx,
+      endDrawing,
+      initWindow,
+      loadFont,
+      setTargetFPS,
+      windowShouldClose, changeDirectory, getApplicationDirectory, unloadFont, isKeyPressed, drawText )
+import Raylib.Types (Vector2 (Vector2), Font, KeyboardKey (KeyUp, KeyDown))
+import Raylib.Colors (rayWhite, black)
+import Foreign (fromBool)
+
+mainFontPath :: String
+mainFontPath = "../../../../../../../../../examples/custom-font-text/assets/Lato-Regular.ttf"
+
+main :: IO ()
+main = do
+  initWindow 800 450 "raylib example - custom font text"
+  setTargetFPS 60
+  _ <- getApplicationDirectory >>= changeDirectory
+
+  mainFont <- loadFont mainFontPath
+  gameLoop mainFont 20
+  unloadFont mainFont
+
+gameLoop :: Font -> Int -> IO ()
+gameLoop mainFont size = do
+  beginDrawing
+  clearBackground rayWhite
+
+  drawTextEx mainFont "Testing drawTextEx" (Vector2 20.0 12.0) (fromIntegral size) 1.0 black
+  drawText "Press the up and down arrows to change the font size" 20 (size + 15) 24 black
+
+  increaseSize <- isKeyPressed KeyUp
+  decreaseSize <- isKeyPressed KeyDown
+
+  endDrawing
+
+  shouldClose <- windowShouldClose
+  unless shouldClose $ gameLoop mainFont (size + fromBool increaseSize - fromBool decreaseSize)
+ examples/first-person-camera/src/Main.hs view
@@ -0,0 +1,52 @@+{-# OPTIONS -Wall #-}
+module Main where
+
+import Control.Monad (unless)
+import Raylib
+  ( beginDrawing,
+    beginMode3D,
+    clearBackground,
+    closeWindow,
+    drawCircle3D,
+    drawFPS,
+    drawLine3D,
+    endDrawing,
+    endMode3D,
+    initWindow,
+    setCameraMode,
+    setTargetFPS,
+    updateCamera,
+    windowShouldClose, drawCubeWiresV
+  )
+import Raylib.Colors (black, white)
+import Raylib.Types (Camera3D (Camera3D), Vector3 (Vector3), CameraProjection (CameraPerspective), CameraMode (CameraModeFirstPerson))
+
+main :: IO ()
+main = do
+  initWindow 600 450 "raylib example - first person camera"
+  let camera = Camera3D (Vector3 0 0 0) (Vector3 2 0 1) (Vector3 0 1 0) 70 CameraPerspective
+  setCameraMode camera CameraModeFirstPerson
+  setTargetFPS 60
+  gameLoop camera
+  closeWindow
+
+gameLoop :: Camera3D -> IO ()
+gameLoop camera = do
+  beginDrawing
+
+  clearBackground black
+  drawFPS 10 20
+
+  beginMode3D camera
+
+  drawCircle3D (Vector3 2 0 1) 2 (Vector3 0 0 0) 0 white
+  drawLine3D (Vector3 3 (-1) 1) (Vector3 1 1 1) white
+  drawLine3D (Vector3 4 2 2) (Vector3 1 (-1) 1) white
+  drawCubeWiresV (Vector3 (-2) 0 0) (Vector3 1 1 1) white
+
+  endMode3D
+
+  endDrawing
+  newCam <- updateCamera camera
+  shouldClose <- windowShouldClose
+  unless shouldClose $ gameLoop newCam
h-raylib.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4
 name:               h-raylib
-version:            4.5.0.9
+version:            4.5.0.10
 synopsis:           Raylib bindings for Haskell
 category:           graphics
 description:
@@ -64,6 +64,63 @@   default:     False
   manual:      True
 
+flag examples
+  description: Build the example projects
+  default:     False
+  manual:      True
+
+executable basic-window
+  hs-source-dirs:   examples/basic-window/src
+  main-is:          Main.hs
+  default-language: Haskell2010
+
+  if !flag(examples)
+    buildable: False
+
+  else
+    build-depends:
+      , base
+      , h-raylib
+
+executable camera-ray-collision
+  hs-source-dirs:   examples/camera-ray-collision/src
+  main-is:          Main.hs
+  default-language: Haskell2010
+
+  if !flag(examples)
+    buildable: False
+
+  else
+    build-depends:
+      , base
+      , h-raylib
+
+executable custom-font-text
+  hs-source-dirs:   examples/custom-font-text/src
+  main-is:          Main.hs
+  default-language: Haskell2010
+
+  if !flag(examples)
+    buildable: False
+
+  else
+    build-depends:
+      , base
+      , h-raylib
+
+executable first-person-camera
+  hs-source-dirs:   examples/first-person-camera/src
+  main-is:          Main.hs
+  default-language: Haskell2010
+
+  if !flag(examples)
+    buildable: False
+
+  else
+    build-depends:
+      , base
+      , h-raylib
+
 library
   exposed-modules:
     Raylib
@@ -121,9 +178,9 @@     cc-options:      -DPLATFORM_DESKTOP
 
   else
-    -- Unsupported OS, do nothing. If you can get it working on an
-    -- OS that isn't on here, please add it here.
-  
+
+  -- Unsupported OS, do nothing. If you can get it working on an
+  -- OS that isn't on here, please add it here.
   if (flag(platform-mac) || (flag(detect-platform) && os(osx)))
     -- Use rgflw.m instead of .c on Mac to force objective-c
     c-sources:
lib/bindings.c view
@@ -901,6 +901,27 @@     return ptr;
 }
 
+Color *ColorTint_(Color* a, Color* b)
+{
+    Color *ptr = (Color *)malloc(sizeof(Color));
+    *ptr = ColorTint(*a, *b);
+    return ptr;
+}
+
+Color *ColorBrightness_(Color* a, float b)
+{
+    Color *ptr = (Color *)malloc(sizeof(Color));
+    *ptr = ColorBrightness(*a, b);
+    return ptr;
+}
+
+Color *ColorContrast_(Color* a, float b)
+{
+    Color *ptr = (Color *)malloc(sizeof(Color));
+    *ptr = ColorContrast(*a, b);
+    return ptr;
+}
+
 Color *ColorAlpha_(Color *a, float b)
 {
     Color *ptr = (Color *)malloc(sizeof(Color));
@@ -1080,16 +1101,6 @@ void DrawCubeWiresV_(Vector3 *a, Vector3 *b, Color *c)
 {
     DrawCubeWiresV(*a, *b, *c);
-}
-
-void DrawCubeTexture_(Texture *a, Vector3 *b, float c, float d, float e, Color *f)
-{
-    DrawCubeTexture(*a, *b, c, d, e, *f);
-}
-
-void DrawCubeTextureRec_(Texture *a, Rectangle *b, Vector3 *c, float d, float e, float f, Color *g)
-{
-    DrawCubeTextureRec(*a, *b, *c, d, e, f, *g);
 }
 
 void DrawSphere_(Vector3 *a, float b, Color *c)
raylib/src/config.h view
@@ -6,7 +6,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2018-2022 Ahmad Fatoum & Ramon Santamaria (@raysan5)
+*   Copyright (c) 2018-2023 Ahmad Fatoum & Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
@@ -148,6 +148,8 @@ //#define SUPPORT_FILEFORMAT_PSD      1
 #define SUPPORT_FILEFORMAT_DDS      1
 #define SUPPORT_FILEFORMAT_HDR      1
+//#define SUPPORT_FILEFORMAT_PIC      1
+//#define SUPPORT_FILEFORMAT_PNM      1
 //#define SUPPORT_FILEFORMAT_KTX      1
 //#define SUPPORT_FILEFORMAT_ASTC     1
 //#define SUPPORT_FILEFORMAT_PKM      1
raylib/src/raudio.c view
@@ -50,7 +50,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2013-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2013-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
@@ -974,7 +974,7 @@     byteCount += sprintf(txtData + byteCount, "// more info and bugs-report:  github.com/raysan5/raylib                        //\n");
     byteCount += sprintf(txtData + byteCount, "// feedback and support:       ray[at]raylib.com                                //\n");
     byteCount += sprintf(txtData + byteCount, "//                                                                              //\n");
-    byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2022 Ramon Santamaria (@raysan5)                          //\n");
+    byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2023 Ramon Santamaria (@raysan5)                          //\n");
     byteCount += sprintf(txtData + byteCount, "//                                                                              //\n");
     byteCount += sprintf(txtData + byteCount, "//////////////////////////////////////////////////////////////////////////////////\n\n");
 
raylib/src/raylib.h view
@@ -57,7 +57,7 @@ *   raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified,
 *   BSD-like license that allows static linking with closed source software:
 *
-*   Copyright (c) 2013-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2013-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
@@ -716,7 +716,7 @@ 
 // Material map index
 typedef enum {
-    MATERIAL_MAP_ALBEDO    = 0,     // Albedo material (same as: MATERIAL_MAP_DIFFUSE)
+    MATERIAL_MAP_ALBEDO = 0,        // Albedo material (same as: MATERIAL_MAP_DIFFUSE)
     MATERIAL_MAP_METALNESS,         // Metalness material (same as: MATERIAL_MAP_SPECULAR)
     MATERIAL_MAP_NORMAL,            // Normal material
     MATERIAL_MAP_ROUGHNESS,         // Roughness material
@@ -857,12 +857,12 @@     BLEND_ADD_COLORS,               // Blend textures adding colors (alternative)
     BLEND_SUBTRACT_COLORS,          // Blend textures subtracting colors (alternative)
     BLEND_ALPHA_PREMULTIPLY,        // Blend premultiplied textures considering alpha
-    BLEND_CUSTOM,                   // Blend textures using custom src/dst factors (use rlSetBlendMode())
-    BLEND_CUSTOM_SEPARATE           // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendModeSeparate())
+    BLEND_CUSTOM,                   // Blend textures using custom src/dst factors (use rlSetBlendFactors())
+    BLEND_CUSTOM_SEPARATE           // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate())
 } BlendMode;
 
 // Gesture
-// NOTE: It could be used as flags to enable only some gestures
+// NOTE: Provided as bit-wise flags to enable only desired gestures
 typedef enum {
     GESTURE_NONE        = 0,        // No gesture
     GESTURE_TAP         = 1,        // Tap gesture
@@ -1331,6 +1331,9 @@ RLAPI Color ColorFromNormalized(Vector4 normalized);                        // Get Color from normalized values [0..1]
 RLAPI Vector3 ColorToHSV(Color color);                                      // Get HSV values for a Color, hue [0..360], saturation/value [0..1]
 RLAPI Color ColorFromHSV(float hue, float saturation, float value);         // Get a Color from HSV values, hue [0..360], saturation/value [0..1]
+RLAPI Color ColorTint(Color color, Color tint);                             // Get color multiplied with another color
+RLAPI Color ColorBrightness(Color color, float factor);                     // Get color with brightness correction, brightness factor goes from -1.0f to 1.0f
+RLAPI Color ColorContrast(Color color, float contrast);                     // Get color with contrast correction, contrast values between -1.0f and 1.0f
 RLAPI Color ColorAlpha(Color color, float alpha);                           // Get color with alpha applied, alpha goes from 0.0f to 1.0f
 RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint);              // Get src alpha-blended into dst color with tint
 RLAPI Color GetColor(unsigned int hexValue);                                // Get Color structure from hexadecimal value
@@ -1412,8 +1415,6 @@ RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color);                                       // Draw cube (Vector version)
 RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color);        // Draw cube wires
 RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color);                                  // Draw cube wires (Vector version)
-RLAPI void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float height, float length, Color color); // Draw cube textured
-RLAPI void DrawCubeTextureRec(Texture2D texture, Rectangle source, Vector3 position, float width, float height, float length, Color color); // Draw cube with a region of a texture
 RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color);                                     // Draw sphere
 RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color);            // Draw sphere with extended parameters
 RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color);         // Draw sphere wires
raylib/src/raymath.h view
@@ -25,7 +25,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2015-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2015-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
@@ -307,9 +307,23 @@ }
 
 // Calculate angle from two vectors
+// NOTE: Parameters need to be normalized
+// Current implementation should be aligned with glm::angle
 RMAPI float Vector2Angle(Vector2 v1, Vector2 v2)
 {
-    float result = atan2f(v2.y, v2.x) - atan2f(v1.y, v1.x);
+    float result = 0.0f;
+    
+    float dot = v1.x*v2.x + v1.y*v2.y;      // Dot product
+
+    float dotClamp = (dot < -1.0f)? -1.0f : dot;  // Clamp
+    if (dotClamp > 1.0f) dotClamp = 1.0f;
+
+    result = acosf(dotClamp);
+    
+    // Alternative implementation, more costly
+    //float v1Length = sqrtf((v1.x*v1.x) + (v1.y*v1.y));
+    //float v2Length = sqrtf((v2.x*v2.x) + (v2.y*v2.y));
+    //float result = -acosf((v1.x*v2.x + v1.y*v2.y)/(v1Length*v2Length));
 
     return result;
 }
raylib/src/rcamera.h view
@@ -22,7 +22,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2015-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2015-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
raylib/src/rcore.c view
@@ -89,7 +89,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2013-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2013-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
@@ -151,7 +151,7 @@     #include "external/sdefl.h"     // Deflate (RFC 1951) compressor
 #endif
 
-#if (defined(__linux__) || defined(PLATFORM_WEB)) && _POSIX_C_SOURCE < 199309L
+#if (defined(__linux__) || defined(PLATFORM_WEB)) && (_POSIX_C_SOURCE < 199309L)
     #undef _POSIX_C_SOURCE
     #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext.
 #endif
@@ -765,7 +765,7 @@     CORE.Input.Keyboard.exitKey = KEY_ESCAPE;
     CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f };
     CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW;
-    CORE.Input.Gamepad.lastButtonPressed = -1;
+    CORE.Input.Gamepad.lastButtonPressed = 0;       // GAMEPAD_BUTTON_UNKNOWN
 #if defined(SUPPORT_EVENTS_WAITING)
     CORE.Window.eventWaiting = true;
 #endif
@@ -1945,7 +1945,21 @@     return glfwGetClipboardString(CORE.Window.handle);
 #endif
 #if defined(PLATFORM_WEB)
-    return emscripten_run_script_string("navigator.clipboard.readText()");
+    // Accessing clipboard data from browser is tricky due to security reasons
+    // The method to use is navigator.clipboard.readText() but this is an asynchronous method
+    // that will return at some moment after the function is called with the required data
+    emscripten_run_script_string("navigator.clipboard.readText() \
+        .then(text => { document.getElementById('clipboard').innerText = text; console.log('Pasted content: ', text); }) \
+        .catch(err => { console.error('Failed to read clipboard contents: ', err); });"
+    );
+    
+    // The main issue is getting that data, one approach could be using ASYNCIFY and wait
+    // for the data but it requires adding Asyncify emscripten library on compilation
+    
+    // Another approach could be just copy the data in a HTML text field and try to retrieve it
+    // later on if available... and clean it for future accesses
+
+    return NULL;
 #endif
     return NULL;
 }
@@ -2446,11 +2460,7 @@ Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode)
 {
     Shader shader = { 0 };
-    shader.locs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int));
 
-    // NOTE: All locations must be reseted to -1 (no location)
-    for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1;
-
     shader.id = rlLoadShaderCode(vsCode, fsCode);
 
     // After shader loading, we TRY to set default location names
@@ -2465,7 +2475,12 @@         //          vertex texcoord2 location   = 5
 
         // NOTE: If any location is not found, loc point becomes -1
+        
+        shader.locs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int));
 
+        // All locations reseted to -1 (no location)
+        for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1;
+
         // Get handles to GLSL input attibute locations
         shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION);
         shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD);
@@ -2497,6 +2512,8 @@     if (shader.id != rlGetShaderIdDefault())
     {
         rlUnloadShaderProgram(shader.id);
+        
+        // NOTE: If shader loading failed, it should be 0
         RL_FREE(shader.locs);
     }
 }
@@ -2522,25 +2539,34 @@ // Set shader uniform value vector
 void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count)
 {
-    rlEnableShader(shader.id);
-    rlSetUniform(locIndex, value, uniformType, count);
-    //rlDisableShader();      // Avoid reseting current shader program, in case other uniforms are set
+    if (locIndex > -1)
+    {
+        rlEnableShader(shader.id);
+        rlSetUniform(locIndex, value, uniformType, count);
+        //rlDisableShader();      // Avoid reseting current shader program, in case other uniforms are set
+    }
 }
 
 // Set shader uniform value (matrix 4x4)
 void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat)
 {
-    rlEnableShader(shader.id);
-    rlSetUniformMatrix(locIndex, mat);
-    //rlDisableShader();
+    if (locIndex > -1)
+    {
+        rlEnableShader(shader.id);
+        rlSetUniformMatrix(locIndex, mat);
+        //rlDisableShader();
+    }
 }
 
 // Set shader uniform value for texture
 void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture)
 {
-    rlEnableShader(shader.id);
-    rlSetUniformSampler(locIndex, texture.id);
-    //rlDisableShader();
+    if (locIndex > -1)
+    {
+        rlEnableShader(shader.id);
+        rlSetUniformSampler(locIndex, texture.id);
+        //rlDisableShader();
+    }
 }
 
 // Get a ray trace from mouse position
@@ -2755,17 +2781,19 @@ // NOTE: On PLATFORM_DESKTOP, timer is initialized on glfwInit()
 double GetTime(void)
 {
+    double time = 0.0;
 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
-    return glfwGetTime();   // Elapsed time since glfwInit()
+    time = glfwGetTime();   // Elapsed time since glfwInit()
 #endif
 
 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
     struct timespec ts = { 0 };
     clock_gettime(CLOCK_MONOTONIC, &ts);
-    unsigned long long int time = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec;
+    unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec;
 
-    return (double)(time - CORE.Time.base)*1e-9;  // Elapsed time since InitTimer()
+    time = (double)(nanoSeconds - CORE.Time.base)*1e-9;  // Elapsed time since InitTimer()
 #endif
+    return time;
 }
 
 // Setup window configuration flags (view FLAGS)
@@ -2808,6 +2836,9 @@ }
 
 // Get a random value between min and max (both included)
+// WARNING: Ranges higher than RAND_MAX will return invalid results
+// More specifically, if (max - min) > INT_MAX there will be an overflow, 
+// and otherwise if (max - min) > RAND_MAX the random value will incorrectly never exceed a certain threshold
 int GetRandomValue(int min, int max)
 {
     if (min > max)
@@ -2816,6 +2847,11 @@         max = min;
         min = tmp;
     }
+    
+    if ((unsigned int)(max - min) > (unsigned int)RAND_MAX)
+    {
+        TRACELOG(LOG_WARNING, "Invalid GetRandomValue() arguments, range should not be higher than %i", RAND_MAX);
+    }
 
     return (rand()%(abs(max - min) + 1) + min);
 }
@@ -3181,14 +3217,12 @@ }
 
 // Unload directory filepaths
+// WARNING: files.count is not reseted to 0 after unloading
 void UnloadDirectoryFiles(FilePathList files)
 {
-    if (files.capacity > 0)
-    {
-        for (unsigned int i = 0; i < files.capacity; i++) RL_FREE(files.paths[i]);
+    for (unsigned int i = 0; i < files.capacity; i++) RL_FREE(files.paths[i]);
 
-        RL_FREE(files.paths);
-    }
+    RL_FREE(files.paths);
 }
 
 // Change working directory, returns true on success
@@ -3286,12 +3320,12 @@ 
 #if defined(SUPPORT_COMPRESSION_API)
     // Decompress data from a valid DEFLATE stream
-    data = RL_CALLOC(MAX_DECOMPRESSION_SIZE*1024*1024, 1);
+    data = (unsigned char *)RL_CALLOC(MAX_DECOMPRESSION_SIZE*1024*1024, 1);
     int length = sinflate(data, MAX_DECOMPRESSION_SIZE*1024*1024, compData, compDataSize);
 
     // WARNING: RL_REALLOC can make (and leave) data copies in memory, be careful with sensitive compressed data!
     // TODO: Use a different approach, create another buffer, copy data manually to it and wipe original buffer memory
-    unsigned char *temp = RL_REALLOC(data, length);
+    unsigned char *temp = (unsigned char *)RL_REALLOC(data, length);
 
     if (temp != NULL) data = temp;
     else TRACELOG(LOG_WARNING, "SYSTEM: Failed to re-allocate required decompression memory");
@@ -3317,7 +3351,7 @@ 
     *outputSize = 4*((dataSize + 2)/3);
 
-    char *encodedData = RL_MALLOC(*outputSize);
+    char *encodedData = (char *)RL_MALLOC(*outputSize);
 
     if (encodedData == NULL) return NULL;
 
@@ -3509,7 +3543,7 @@             CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1];
 
         // Reset last character in the queue
-        CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 0;
+        CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount - 1] = 0;
         CORE.Input.Keyboard.keyPressedQueueCount--;
     }
 
@@ -3531,7 +3565,7 @@             CORE.Input.Keyboard.charPressedQueue[i] = CORE.Input.Keyboard.charPressedQueue[i + 1];
 
         // Reset last character in the queue
-        CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = 0;
+        CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount - 1] = 0;
         CORE.Input.Keyboard.charPressedQueueCount--;
     }
 
@@ -4860,7 +4894,7 @@ 
 #if !(defined(PLATFORM_RPI) || defined(PLATFORM_DRM))
     // Reset last gamepad button/axis registered state
-    CORE.Input.Gamepad.lastButtonPressed = -1;
+    CORE.Input.Gamepad.lastButtonPressed = 0;       // GAMEPAD_BUTTON_UNKNOWN
     CORE.Input.Gamepad.axisCount = 0;
 #endif
 
@@ -5365,7 +5399,7 @@     // Ref: https://www.glfw.org/docs/latest/input_guide.html#input_char
 
     // Check if there is space available in the queue
-    if (CORE.Input.Keyboard.charPressedQueueCount < MAX_KEY_PRESSED_QUEUE)
+    if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE)
     {
         // Add character to the queue
         CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = key;
@@ -5703,14 +5737,15 @@             CORE.Input.Gamepad.ready[0] = true;
 
             GamepadButton button = AndroidTranslateGamepadButton(keycode);
-            if (button == GAMEPAD_BUTTON_UNKNOWN)
-                return 1;
+            
+            if (button == GAMEPAD_BUTTON_UNKNOWN) return 1;
 
             if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN)
             {
                 CORE.Input.Gamepad.currentButtonState[0][button] = 1;
             }
             else CORE.Input.Gamepad.currentButtonState[0][button] = 0;  // Key up
+            
             return 1; // Handled gamepad button
         }
 
@@ -6674,7 +6709,7 @@                         CORE.Input.Gamepad.currentButtonState[i][gamepadEvent.number] = (int)gamepadEvent.value;
 
                         if ((int)gamepadEvent.value == 1) CORE.Input.Gamepad.lastButtonPressed = gamepadEvent.number;
-                        else CORE.Input.Gamepad.lastButtonPressed = -1;
+                        else CORE.Input.Gamepad.lastButtonPressed = 0;       // GAMEPAD_BUTTON_UNKNOWN
                     }
                 }
                 else if (gamepadEvent.type == JS_EVENT_AXIS)
raylib/src/rgestures.h view
@@ -24,7 +24,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2014-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2014-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
raylib/src/rglfw.c view
@@ -7,7 +7,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2017-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2017-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
raylib/src/rlgl.h view
@@ -85,7 +85,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2014-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2014-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
@@ -264,7 +264,7 @@ #define RL_UNSIGNED_BYTE                        0x1401      // GL_UNSIGNED_BYTE
 #define RL_FLOAT                                0x1406      // GL_FLOAT
 
-// Buffer usage hint
+// GL buffer usage hint
 #define RL_STREAM_DRAW                          0x88E0      // GL_STREAM_DRAW
 #define RL_STREAM_READ                          0x88E1      // GL_STREAM_READ
 #define RL_STREAM_COPY                          0x88E2      // GL_STREAM_COPY
@@ -280,6 +280,37 @@ #define RL_VERTEX_SHADER                        0x8B31      // GL_VERTEX_SHADER
 #define RL_COMPUTE_SHADER                       0x91B9      // GL_COMPUTE_SHADER
 
+// GL blending factors
+#define RL_ZERO                                 0           // GL_ZERO
+#define RL_ONE                                  1           // GL_ONE  
+#define RL_SRC_COLOR                            0x0300      // GL_SRC_COLOR            
+#define RL_ONE_MINUS_SRC_COLOR                  0x0301      // GL_ONE_MINUS_SRC_COLOR 
+#define RL_SRC_ALPHA                            0x0302      // GL_SRC_ALPHA           
+#define RL_ONE_MINUS_SRC_ALPHA                  0x0303      // GL_ONE_MINUS_SRC_ALPHA 
+#define RL_DST_ALPHA                            0x0304      // GL_DST_ALPHA           
+#define RL_ONE_MINUS_DST_ALPHA                  0x0305      // GL_ONE_MINUS_DST_ALPHA 
+#define RL_DST_COLOR                            0x0306      // GL_DST_COLOR           
+#define RL_ONE_MINUS_DST_COLOR                  0x0307      // GL_ONE_MINUS_DST_COLOR 
+#define RL_SRC_ALPHA_SATURATE                   0x0308      // GL_SRC_ALPHA_SATURATE  
+#define RL_CONSTANT_COLOR                       0x8001      // GL_CONSTANT_COLOR          
+#define RL_ONE_MINUS_CONSTANT_COLOR             0x8002      // GL_ONE_MINUS_CONSTANT_COLOR
+#define RL_CONSTANT_ALPHA                       0x8003      // GL_CONSTANT_ALPHA          
+#define RL_ONE_MINUS_CONSTANT_ALPHA             0x8004      // GL_ONE_MINUS_CONSTANT_ALPHA
+
+// GL blending functions/equations
+#define RL_FUNC_ADD                             0x8006      // GL_FUNC_ADD             
+#define RL_FUNC_SUBTRACT                        0x800A      // GL_FUNC_SUBTRACT        
+#define RL_FUNC_REVERSE_SUBTRACT                0x800B      // GL_FUNC_REVERSE_SUBTRACT
+#define RL_BLEND_EQUATION                       0x8009      // GL_BLEND_EQUATION       
+#define RL_BLEND_EQUATION_RGB                   0x8009      // GL_BLEND_EQUATION_RGB   // (Same as BLEND_EQUATION)
+#define RL_BLEND_EQUATION_ALPHA                 0x883D      // GL_BLEND_EQUATION_ALPHA 
+#define RL_BLEND_DST_RGB                        0x80C8      // GL_BLEND_DST_RGB        
+#define RL_BLEND_SRC_RGB                        0x80C9      // GL_BLEND_SRC_RGB        
+#define RL_BLEND_DST_ALPHA                      0x80CA      // GL_BLEND_DST_ALPHA      
+#define RL_BLEND_SRC_ALPHA                      0x80CB      // GL_BLEND_SRC_ALPHA                   
+#define RL_BLEND_COLOR                          0x8005      // GL_BLEND_COLOR               
+
+
 //----------------------------------------------------------------------------------
 // Types and Structures Definition
 //----------------------------------------------------------------------------------
@@ -498,6 +529,12 @@     RL_ATTACHMENT_RENDERBUFFER = 200,   // Framebuffer texture attachment type: renderbuffer
 } rlFramebufferAttachTextureType;
 
+// Face culling mode
+typedef enum {
+    RL_CULL_FACE_FRONT = 0,
+    RL_CULL_FACE_BACK
+} rlCullMode;
+
 //------------------------------------------------------------------------------------
 // Functions Declaration - Matrix operations
 //------------------------------------------------------------------------------------
@@ -513,7 +550,7 @@ RLAPI void rlTranslatef(float x, float y, float z);   // Multiply the current matrix by a translation matrix
 RLAPI void rlRotatef(float angle, float x, float y, float z);  // Multiply the current matrix by a rotation matrix
 RLAPI void rlScalef(float x, float y, float z);       // Multiply the current matrix by a scaling matrix
-RLAPI void rlMultMatrixf(float *matf);                // Multiply the current matrix by another matrix
+RLAPI void rlMultMatrixf(const float *matf);                // Multiply the current matrix by another matrix
 RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar);
 RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar);
 RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area
@@ -578,6 +615,7 @@ RLAPI void rlDisableDepthMask(void);                    // Disable depth write
 RLAPI void rlEnableBackfaceCulling(void);               // Enable backface culling
 RLAPI void rlDisableBackfaceCulling(void);              // Disable backface culling
+RLAPI void rlSetCullFace(int mode);                     // Set face culling mode
 RLAPI void rlEnableScissorTest(void);                   // Enable scissor test
 RLAPI void rlDisableScissorTest(void);                  // Disable scissor test
 RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test
@@ -689,7 +727,7 @@ RLAPI unsigned int rlGetShaderBufferSize(unsigned int id);                      // Get SSBO buffer size
 
 // Buffer management
-RLAPI void rlBindImageTexture(unsigned int id, unsigned int index, unsigned int format, int readonly);  // Bind image texture
+RLAPI void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly);  // Bind image texture
 
 // Matrix state management
 RLAPI Matrix rlGetMatrixModelview(void);                                  // Get internal modelview matrix
@@ -949,7 +987,8 @@         bool vao;                           // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object)
         bool instancing;                    // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays)
         bool texNPOT;                       // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot)
-        bool texDepth;                      // Depth textures supported (GL_ARB_depth_texture, GL_WEBGL_depth_texture, GL_OES_depth_texture)
+        bool texDepth;                      // Depth textures supported (GL_ARB_depth_texture, GL_OES_depth_texture)
+        bool texDepthWebGL;                 // Depth textures supported WebGL specific (GL_WEBGL_depth_texture)
         bool texFloat32;                    // float textures support (32 bit per channel) (GL_OES_texture_float)
         bool texCompDXT;                    // DDS texture compression support (GL_EXT_texture_compression_s3tc, GL_WEBGL_compressed_texture_s3tc, GL_WEBKIT_WEBGL_compressed_texture_s3tc)
         bool texCompETC1;                   // ETC1 texture compression support (GL_OES_compressed_ETC1_RGB8_texture, GL_WEBGL_compressed_texture_etc1)
@@ -1041,7 +1080,7 @@ void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); }
 void rlRotatef(float angle, float x, float y, float z) { glRotatef(angle, x, y, z); }
 void rlScalef(float x, float y, float z) { glScalef(x, y, z); }
-void rlMultMatrixf(float *matf) { glMultMatrixf(matf); }
+void rlMultMatrixf(const float *matf) { glMultMatrixf(matf); }
 #endif
 #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
 // Choose the current matrix to be transformed
@@ -1166,7 +1205,7 @@ }
 
 // Multiply the current matrix by another matrix
-void rlMultMatrixf(float *matf)
+void rlMultMatrixf(const float *matf)
 {
     // Matrix creation from array
     Matrix mat = { matf[0], matf[4], matf[8], matf[12],
@@ -1671,6 +1710,17 @@ // Disable backface culling
 void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); }
 
+// Set face culling mode
+void rlSetCullFace(int mode)
+{
+    switch (mode)
+    {
+        case RL_CULL_FACE_BACK: glCullFace(GL_BACK); break;
+        case RL_CULL_FACE_FRONT: glCullFace(GL_FRONT); break;
+        default: break;
+    }
+}
+
 // Enable scissor test
 void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); }
 
@@ -1798,7 +1848,7 @@ void rlSetBlendMode(int mode)
 {
 #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
-    if (RLGL.State.currentBlendMode != mode || ((mode == RL_BLEND_CUSTOM || mode == RL_BLEND_CUSTOM_SEPARATE) && RLGL.State.glCustomBlendModeModified))
+    if ((RLGL.State.currentBlendMode != mode) || ((mode == RL_BLEND_CUSTOM || mode == RL_BLEND_CUSTOM_SEPARATE) && RLGL.State.glCustomBlendModeModified))
     {
         rlDrawRenderBatch(RLGL.currentBatch);
 
@@ -2169,11 +2219,12 @@         if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true;
 
         // Check depth texture support
-        if ((strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) ||
-            (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0)) RLGL.ExtSupported.texDepth = true;
+        if (strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) RLGL.ExtSupported.texDepth = true;
+        if (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0) RLGL.ExtSupported.texDepthWebGL = true;    // WebGL requires unsized internal format
+        if (RLGL.ExtSupported.texDepthWebGL) RLGL.ExtSupported.texDepth = true;
 
-        if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24;
-        if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32;
+        if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24;   // Not available on WebGL
+        if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32;   // Not available on WebGL
 
         // Check texture compression support: DXT
         if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) ||
@@ -2926,7 +2977,7 @@ }
 
 // Load depth texture/renderbuffer (to be attached to fbo)
-// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture/WEBGL_depth_texture extensions
+// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture and WebGL requires WEBGL_depth_texture extensions
 unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer)
 {
     unsigned int id = 0;
@@ -2934,15 +2985,20 @@ #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
     // In case depth textures not supported, we force renderbuffer usage
     if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true;
-
+    
     // NOTE: We let the implementation to choose the best bit-depth
     // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F
     unsigned int glInternalFormat = GL_DEPTH_COMPONENT;
 
 #if defined(GRAPHICS_API_OPENGL_ES2)
-    if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES;
-    else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES;
-    else glInternalFormat = GL_DEPTH_COMPONENT16;
+    // WARNING: WebGL platform requires unsized internal format definition (GL_DEPTH_COMPONENT)
+    // while other platforms using OpenGL ES 2.0 require/support sized internal formats depending on the GPU capabilities
+    if (!RLGL.ExtSupported.texDepthWebGL || useRenderBuffer)
+    {
+        if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES;
+        else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES;
+        else glInternalFormat = GL_DEPTH_COMPONENT16;
+    }
 #endif
 
     if (!useRenderBuffer && RLGL.ExtSupported.texDepth)
@@ -3625,12 +3681,14 @@         // NOTE: We detach shader before deletion to make sure memory is freed
         if (vertexShaderId != RLGL.State.defaultVShaderId)
         {
-            glDetachShader(id, vertexShaderId);
+            // WARNING: Shader program linkage could fail and returned id is 0
+            if (id > 0) glDetachShader(id, vertexShaderId);
             glDeleteShader(vertexShaderId);
         }
         if (fragmentShaderId != RLGL.State.defaultFShaderId)
         {
-            glDetachShader(id, fragmentShaderId);
+            // WARNING: Shader program linkage could fail and returned id is 0
+            if (id > 0) glDetachShader(id, fragmentShaderId);
             glDeleteShader(fragmentShaderId);
         }
 
@@ -4037,7 +4095,7 @@ }
 
 // Bind image texture
-void rlBindImageTexture(unsigned int id, unsigned int index, unsigned int format, int readonly)
+void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly)
 {
 #if defined(GRAPHICS_API_OPENGL_43)
     unsigned int glInternalFormat = 0, glFormat = 0, glType = 0;
raylib/src/rmodels.c view
@@ -22,7 +22,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2013-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2013-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
@@ -142,18 +142,18 @@ #endif
 #if defined(SUPPORT_FILEFORMAT_IQM)
 static Model LoadIQM(const char *fileName);     // Load IQM mesh data
-static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, unsigned int *animCount);    // Load IQM animation data
+static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, unsigned int *animCount);   // Load IQM animation data
 #endif
 #if defined(SUPPORT_FILEFORMAT_GLTF)
 static Model LoadGLTF(const char *fileName);    // Load GLTF mesh data
-//static ModelAnimation *LoadModelAnimationGLTF(const char *fileName, unsigned int *animCount);    // Load GLTF animation data
+static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned int *animCount);  // Load GLTF animation data
 #endif
 #if defined(SUPPORT_FILEFORMAT_VOX)
 static Model LoadVOX(const char *filename);     // Load VOX mesh data
 #endif
 #if defined(SUPPORT_FILEFORMAT_M3D)
 static Model LoadM3D(const char *filename);     // Load M3D mesh data
-static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int *animCount);    // Load M3D animation data
+static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int *animCount);   // Load M3D animation data
 #endif
 
 //----------------------------------------------------------------------------------
@@ -397,151 +397,6 @@     DrawCubeWires(position, size.x, size.y, size.z, color);
 }
 
-// Draw cube
-// NOTE: Cube position is the center position
-void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float height, float length, Color color)
-{
-    float x = position.x;
-    float y = position.y;
-    float z = position.z;
-
-    rlSetTexture(texture.id);
-
-    //rlPushMatrix();
-        // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate)
-        //rlTranslatef(2.0f, 0.0f, 0.0f);
-        //rlRotatef(45, 0, 1, 0);
-        //rlScalef(2.0f, 2.0f, 2.0f);
-
-        rlBegin(RL_QUADS);
-            rlColor4ub(color.r, color.g, color.b, color.a);
-            // Front Face
-            rlNormal3f(0.0f, 0.0f, 1.0f);       // Normal Pointing Towards Viewer
-            rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z + length/2);  // Bottom Left Of The Texture and Quad
-            rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/2);  // Bottom Right Of The Texture and Quad
-            rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z + length/2);  // Top Right Of The Texture and Quad
-            rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z + length/2);  // Top Left Of The Texture and Quad
-            // Back Face
-            rlNormal3f(0.0f, 0.0f, - 1.0f);     // Normal Pointing Away From Viewer
-            rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z - length/2);  // Bottom Right Of The Texture and Quad
-            rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z - length/2);  // Top Right Of The Texture and Quad
-            rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/2);  // Top Left Of The Texture and Quad
-            rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z - length/2);  // Bottom Left Of The Texture and Quad
-            // Top Face
-            rlNormal3f(0.0f, 1.0f, 0.0f);       // Normal Pointing Up
-            rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z - length/2);  // Top Left Of The Texture and Quad
-            rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - width/2, y + height/2, z + length/2);  // Bottom Left Of The Texture and Quad
-            rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + width/2, y + height/2, z + length/2);  // Bottom Right Of The Texture and Quad
-            rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/2);  // Top Right Of The Texture and Quad
-            // Bottom Face
-            rlNormal3f(0.0f, - 1.0f, 0.0f);     // Normal Pointing Down
-            rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - width/2, y - height/2, z - length/2);  // Top Right Of The Texture and Quad
-            rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y - height/2, z - length/2);  // Top Left Of The Texture and Quad
-            rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/2);  // Bottom Left Of The Texture and Quad
-            rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z + length/2);  // Bottom Right Of The Texture and Quad
-            // Right face
-            rlNormal3f(1.0f, 0.0f, 0.0f);       // Normal Pointing Right
-            rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z - length/2);  // Bottom Right Of The Texture and Quad
-            rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/2);  // Top Right Of The Texture and Quad
-            rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z + length/2);  // Top Left Of The Texture and Quad
-            rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/2);  // Bottom Left Of The Texture and Quad
-            // Left Face
-            rlNormal3f( - 1.0f, 0.0f, 0.0f);    // Normal Pointing Left
-            rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z - length/2);  // Bottom Left Of The Texture and Quad
-            rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z + length/2);  // Bottom Right Of The Texture and Quad
-            rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z + length/2);  // Top Right Of The Texture and Quad
-            rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z - length/2);  // Top Left Of The Texture and Quad
-        rlEnd();
-    //rlPopMatrix();
-
-    rlSetTexture(0);
-}
-
-// Draw cube with texture piece applied to all faces
-void DrawCubeTextureRec(Texture2D texture, Rectangle source, Vector3 position, float width, float height, float length, Color color)
-{
-    float x = position.x;
-    float y = position.y;
-    float z = position.z;
-    float texWidth = (float)texture.width;
-    float texHeight = (float)texture.height;
-
-    rlSetTexture(texture.id);
-
-    rlBegin(RL_QUADS);
-        rlColor4ub(color.r, color.g, color.b, color.a);
-
-        // Front face
-        rlNormal3f(0.0f, 0.0f, 1.0f);
-        rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x - width/2, y - height/2, z + length/2);
-        rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x + width/2, y - height/2, z + length/2);
-        rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
-        rlVertex3f(x + width/2, y + height/2, z + length/2);
-        rlTexCoord2f(source.x/texWidth, source.y/texHeight);
-        rlVertex3f(x - width/2, y + height/2, z + length/2);
-
-        // Back face
-        rlNormal3f(0.0f, 0.0f, - 1.0f);
-        rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x - width/2, y - height/2, z - length/2);
-        rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
-        rlVertex3f(x - width/2, y + height/2, z - length/2);
-        rlTexCoord2f(source.x/texWidth, source.y/texHeight);
-        rlVertex3f(x + width/2, y + height/2, z - length/2);
-        rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x + width/2, y - height/2, z - length/2);
-
-        // Top face
-        rlNormal3f(0.0f, 1.0f, 0.0f);
-        rlTexCoord2f(source.x/texWidth, source.y/texHeight);
-        rlVertex3f(x - width/2, y + height/2, z - length/2);
-        rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x - width/2, y + height/2, z + length/2);
-        rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x + width/2, y + height/2, z + length/2);
-        rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
-        rlVertex3f(x + width/2, y + height/2, z - length/2);
-
-        // Bottom face
-        rlNormal3f(0.0f, - 1.0f, 0.0f);
-        rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
-        rlVertex3f(x - width/2, y - height/2, z - length/2);
-        rlTexCoord2f(source.x/texWidth, source.y/texHeight);
-        rlVertex3f(x + width/2, y - height/2, z - length/2);
-        rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x + width/2, y - height/2, z + length/2);
-        rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x - width/2, y - height/2, z + length/2);
-
-        // Right face
-        rlNormal3f(1.0f, 0.0f, 0.0f);
-        rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x + width/2, y - height/2, z - length/2);
-        rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
-        rlVertex3f(x + width/2, y + height/2, z - length/2);
-        rlTexCoord2f(source.x/texWidth, source.y/texHeight);
-        rlVertex3f(x + width/2, y + height/2, z + length/2);
-        rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x + width/2, y - height/2, z + length/2);
-
-        // Left face
-        rlNormal3f( - 1.0f, 0.0f, 0.0f);
-        rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x - width/2, y - height/2, z - length/2);
-        rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
-        rlVertex3f(x - width/2, y - height/2, z + length/2);
-        rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
-        rlVertex3f(x - width/2, y + height/2, z + length/2);
-        rlTexCoord2f(source.x/texWidth, source.y/texHeight);
-        rlVertex3f(x - width/2, y + height/2, z - length/2);
-
-    rlEnd();
-
-    rlSetTexture(0);
-}
-
 // Draw sphere
 void DrawSphere(Vector3 centerPos, float radius, Color color)
 {
@@ -1940,7 +1795,7 @@         byteCount += sprintf(txtData + byteCount, "# // more info and bugs-report:  github.com/raysan5/raylib                        //\n");
         byteCount += sprintf(txtData + byteCount, "# // feedback and support:       ray[at]raylib.com                                //\n");
         byteCount += sprintf(txtData + byteCount, "# //                                                                              //\n");
-        byteCount += sprintf(txtData + byteCount, "# // Copyright (c) 2018-2022 Ramon Santamaria (@raysan5)                          //\n");
+        byteCount += sprintf(txtData + byteCount, "# // Copyright (c) 2018-2023 Ramon Santamaria (@raysan5)                          //\n");
         byteCount += sprintf(txtData + byteCount, "# //                                                                              //\n");
         byteCount += sprintf(txtData + byteCount, "# //////////////////////////////////////////////////////////////////////////////////\n\n");
         byteCount += sprintf(txtData + byteCount, "# Vertex Count:     %i\n", mesh.vertexCount);
@@ -2100,7 +1955,7 @@     if (IsFileExtension(fileName, ".m3d")) animations = LoadModelAnimationsM3D(fileName, animCount);
 #endif
 #if defined(SUPPORT_FILEFORMAT_GLTF)
-    //if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadModelAnimationGLTF(fileName, animCount);
+    if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadModelAnimationsGLTF(fileName, animCount);
 #endif
 
     return animations;
@@ -2174,8 +2029,8 @@                     // Vertices processing
                     // NOTE: We use meshes.vertices (default vertex position) to calculate meshes.animVertices (animated vertex position)
                     animVertex = (Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] };
-                    animVertex = Vector3Multiply(animVertex, outScale);
                     animVertex = Vector3Subtract(animVertex, inTranslation);
+                    animVertex = Vector3Multiply(animVertex, outScale);
                     animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation)));
                     animVertex = Vector3Add(animVertex, outTranslation);
                     //animVertex = Vector3Transform(animVertex, model.transform);
@@ -3977,6 +3832,29 @@ //----------------------------------------------------------------------------------
 // Module specific Functions Definition
 //----------------------------------------------------------------------------------
+#if defined(SUPPORT_FILEFORMAT_IQM) || defined(SUPPORT_FILEFORMAT_GLTF)
+// Build pose from parent joints
+// NOTE: Required for animations loading (required by IQM and GLTF)
+static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform *transforms)
+{
+    for (int i = 0; i < boneCount; i++)
+    {
+        if (bones[i].parent >= 0)
+        {
+            if (bones[i].parent > i)
+            {
+                TRACELOG(LOG_WARNING, "Assumes bones are toplogically sorted, but bone %d has parent %d. Skipping.", i, bones[i].parent);
+                continue;
+            }
+            transforms[i].rotation = QuaternionMultiply(transforms[bones[i].parent].rotation, transforms[i].rotation);
+            transforms[i].translation = Vector3RotateByQuaternion(transforms[i].translation, transforms[bones[i].parent].rotation);
+            transforms[i].translation = Vector3Add(transforms[i].translation, transforms[bones[i].parent].translation);
+            transforms[i].scale = Vector3Multiply(transforms[i].scale, transforms[bones[i].parent].scale);
+        }
+    }
+}
+#endif
+
 #if defined(SUPPORT_FILEFORMAT_OBJ)
 // Load OBJ mesh data
 //
@@ -4515,17 +4393,7 @@         model.bindPose[i].scale.z = ijoint[i].scale[2];
     }
 
-    // Build bind pose from parent joints
-    for (int i = 0; i < model.boneCount; i++)
-    {
-        if (model.bones[i].parent >= 0)
-        {
-            model.bindPose[i].rotation = QuaternionMultiply(model.bindPose[model.bones[i].parent].rotation, model.bindPose[i].rotation);
-            model.bindPose[i].translation = Vector3RotateByQuaternion(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].rotation);
-            model.bindPose[i].translation = Vector3Add(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].translation);
-            model.bindPose[i].scale = Vector3Multiply(model.bindPose[i].scale, model.bindPose[model.bones[i].parent].scale);
-        }
-    }
+    BuildPoseFromParentJoints(model.bones, model.boneCount, model.bindPose);
 
     RL_FREE(fileData);
 
@@ -4826,6 +4694,35 @@     return image;
 }
 
+// Load bone info from GLTF skin data
+static BoneInfo *LoadBoneInfoGLTF(cgltf_skin skin, int *boneCount)
+{
+    *boneCount = skin.joints_count;
+    BoneInfo *bones = RL_MALLOC(skin.joints_count*sizeof(BoneInfo));
+
+    for (unsigned int i = 0; i < skin.joints_count; i++)
+    {
+        cgltf_node node = *skin.joints[i];
+        strncpy(bones[i].name, node.name, sizeof(bones[i].name));
+
+        // Find parent bone index
+        unsigned int parentIndex = -1;
+        
+        for (unsigned int j = 0; j < skin.joints_count; j++)
+        {
+            if (skin.joints[j] == node.parent)
+            {
+                parentIndex = j;
+                break;
+            }
+        }
+
+        bones[i].parent = parentIndex;
+    }
+
+    return bones;
+}
+
 // Load glTF file into model struct, .gltf and .glb supported
 static Model LoadGLTF(const char *fileName)
 {
@@ -4840,6 +4737,7 @@           - Supports PBR metallic/roughness flow, loads material textures, values and colors
                      PBR specular/glossiness flow and extended texture flows not supported
           - Supports multiple meshes per model (every primitives is loaded as a separate mesh)
+          - Supports basic animations
 
         RESTRICTIONS:
           - Only triangle meshes supported
@@ -5184,11 +5082,45 @@             }
         }
 
-/*
-        // TODO: Load glTF meshes animation data
+        // Load glTF meshes animation data
         // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins
         // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes
+        //
+        // LIMITATIONS:
+        //  - Only supports 1 armature per file, and skips loading it if there are multiple armatures
+        //  - Only supports linear interpolation (default method in Blender when checked "Always Sample Animations" when exporting a GLTF file)
+        //  - Only supports translation/rotation/scale animation channel.path, weights not considered (i.e. morph targets)
         //----------------------------------------------------------------------------------------------------
+        if (data->skins_count == 1)
+        {
+            cgltf_skin skin = data->skins[0];
+            model.bones = LoadBoneInfoGLTF(skin, &model.boneCount);
+            model.bindPose = RL_MALLOC(model.boneCount*sizeof(Transform));
+
+            for (unsigned int i = 0; i < model.boneCount; i++)
+            {
+                cgltf_node node = *skin.joints[i];
+                model.bindPose[i].translation.x = node.translation[0];
+                model.bindPose[i].translation.y = node.translation[1];
+                model.bindPose[i].translation.z = node.translation[2];
+
+                model.bindPose[i].rotation.x = node.rotation[0];
+                model.bindPose[i].rotation.y = node.rotation[1];
+                model.bindPose[i].rotation.z = node.rotation[2];
+                model.bindPose[i].rotation.w = node.rotation[3];
+
+                model.bindPose[i].scale.x = node.scale[0];
+                model.bindPose[i].scale.y = node.scale[1];
+                model.bindPose[i].scale.z = node.scale[2];
+            }
+
+            BuildPoseFromParentJoints(model.bones, model.boneCount, model.bindPose);
+        }
+        else if (data->skins_count > 1)
+        {
+            TRACELOG(LOG_ERROR, "MODEL: [%s] can only load one skin (armature) per model, but gltf skins_count == %i", fileName, data->skins_count);
+        }
+
         for (unsigned int i = 0, meshIndex = 0; i < data->meshes_count; i++)
         {
             for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++)
@@ -5210,7 +5142,6 @@                             model.meshes[meshIndex].boneIds = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char));
 
                             // Load 4 components of unsigned char data type into mesh.boneIds
-                            // TODO: It seems LOAD_ATTRIBUTE() macro does not work as expected in some cases,
                             // for cgltf_attribute_type_joints we have:
                             //   - data.meshes[0] (256 vertices)
                             //   - 256 values, provided as cgltf_type_vec4 of bytes (4 byte per joint, stride 4)
@@ -5237,10 +5168,17 @@                     }
                 }
 
+                // Animated vertex data
+                model.meshes[meshIndex].animVertices = RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float));
+                memcpy(model.meshes[meshIndex].animVertices, model.meshes[meshIndex].vertices, model.meshes[meshIndex].vertexCount*3*sizeof(float));
+                model.meshes[meshIndex].animNormals = RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float));
+                memcpy(model.meshes[meshIndex].animNormals, model.meshes[meshIndex].normals, model.meshes[meshIndex].vertexCount*3*sizeof(float));
+
                 meshIndex++;       // Move to next mesh
             }
+
         }
-*/
+
         // Free all cgltf loaded data
         cgltf_free(data);
     }
@@ -5251,6 +5189,222 @@ 
     return model;
 }
+
+// Get interpolated pose for bone sampler at a specific time. Returns true on success.
+static bool GetPoseAtTimeGLTF(cgltf_accessor *input, cgltf_accessor *output, float time, void *data)
+{
+    // Input and output should have the same count
+    float tstart = 0.0f;
+    float tend = 0.0f;
+    int keyframe = 0;       // Defaults to first pose
+    
+    for (int i = 0; i < input->count - 1; i++)
+    {
+        cgltf_bool r1 = cgltf_accessor_read_float(input, i, &tstart, 1);
+        if (!r1) return false;
+        
+        cgltf_bool r2 = cgltf_accessor_read_float(input, i + 1, &tend, 1);
+        if (!r2) return false;
+
+        if ((tstart <= time) && (time < tend))
+        {
+            keyframe = i;
+            break;
+        }
+    }
+
+    float t = (time - tstart)/(tend - tstart);
+    t = (t < 0.0f)? 0.0f : t;
+    t = (t > 1.0f)? 1.0f : t;
+
+    if (output->component_type != cgltf_component_type_r_32f) return false;
+
+    if (output->type == cgltf_type_vec3)
+    {
+        float tmp[3] = { 0.0f };
+        cgltf_accessor_read_float(output, keyframe, tmp, 3);
+        Vector3 v1 = {tmp[0], tmp[1], tmp[2]};
+        cgltf_accessor_read_float(output, keyframe+1, tmp, 3);
+        Vector3 v2 = {tmp[0], tmp[1], tmp[2]};
+        Vector3 *r = data;
+        *r = Vector3Lerp(v1, v2, t);
+    }
+    else if (output->type == cgltf_type_vec4)
+    {
+        float tmp[4] = { 0.0f };
+        cgltf_accessor_read_float(output, keyframe, tmp, 4);
+        Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]};
+        cgltf_accessor_read_float(output, keyframe+1, tmp, 4);
+        Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]};
+        Vector4 *r = data;
+        
+        // Only v4 is for rotations, so we know it's a quat
+        *r = QuaternionSlerp(v1, v2, t);
+    }
+    
+    return true;
+}
+
+#define GLTF_ANIMDELAY 17    // Animation frames delay, (~1000 ms/60 FPS = 16.666666* ms)
+
+static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned int *animCount)
+{
+    // glTF file loading
+    unsigned int dataSize = 0;
+    unsigned char *fileData = LoadFileData(fileName, &dataSize);
+
+    ModelAnimation *animations = NULL;
+    
+    // glTF data loading
+    cgltf_options options = { 0 };
+    cgltf_data *data = NULL;
+    cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data);
+    
+    if (result != cgltf_result_success)
+    {
+        TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName);
+        *animCount = 0;
+        return NULL;
+    }
+
+    result = cgltf_load_buffers(&options, data, fileName);
+    if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load animation buffers", fileName);
+
+    if (result == cgltf_result_success)
+    {
+        if (data->skins_count == 1)
+        {
+            cgltf_skin skin = data->skins[0];
+            *animCount = data->animations_count;
+            animations = RL_MALLOC(data->animations_count*sizeof(ModelAnimation));
+            
+            for (unsigned int i = 0; i < data->animations_count; i++)
+            {
+                animations[i].bones = LoadBoneInfoGLTF(skin, &animations[i].boneCount);
+
+                cgltf_animation animData = data->animations[i];
+
+                struct Channels {
+                    cgltf_animation_channel *translate;
+                    cgltf_animation_channel *rotate;
+                    cgltf_animation_channel *scale;
+                };
+
+                struct Channels *boneChannels = RL_CALLOC(animations[i].boneCount, sizeof(struct Channels));
+                float animDuration = 0.0f;
+                
+                for (unsigned int j = 0; j < animData.channels_count; j++)
+                {
+                    cgltf_animation_channel channel = animData.channels[j];
+                    int boneIndex = -1;
+                    
+                    for (unsigned int k = 0; k < skin.joints_count; k++)
+                    {
+                        if (animData.channels[j].target_node == skin.joints[k])
+                        {
+                            boneIndex = k;
+                            break;
+                        }
+                    }
+
+                    if (boneIndex == -1)
+                    {
+                        // Animation channel for a node not in the armature
+                        continue;
+                    }
+
+                    if (animData.channels[j].sampler->interpolation == cgltf_interpolation_type_linear)
+                    {
+                        if (channel.target_path == cgltf_animation_path_type_translation)
+                        {
+                            boneChannels[boneIndex].translate = &animData.channels[j];
+                        }
+                        else if (channel.target_path == cgltf_animation_path_type_rotation)
+                        {
+                            boneChannels[boneIndex].rotate = &animData.channels[j];
+                        }
+                        else if (channel.target_path == cgltf_animation_path_type_scale)
+                        {
+                            boneChannels[boneIndex].scale = &animData.channels[j];
+                        }
+                        else
+                        {
+                            TRACELOG(LOG_WARNING, "MODEL: [%s] Unsupported target_path on channel %d's sampler for animation %d. Skipping.", fileName, j, i);
+                        }
+                    } 
+                    else TRACELOG(LOG_WARNING, "MODEL: [%s] Only linear interpolation curves are supported for GLTF animation.", fileName);
+
+                    float t = 0.0f;
+                    cgltf_bool r = cgltf_accessor_read_float(channel.sampler->input, channel.sampler->input->count - 1, &t, 1);
+                    
+                    if (!r)
+                    {
+                        TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load input time", fileName);
+                        continue;
+                    }
+
+                    animDuration = (t > animDuration)? t : animDuration;
+                }
+
+                animations[i].frameCount = (int)(animDuration*1000.0f/GLTF_ANIMDELAY);
+                animations[i].framePoses = RL_MALLOC(animations[i].frameCount*sizeof(Transform *));
+
+                for (unsigned int j = 0; j < animations[i].frameCount; j++)
+                {
+                    animations[i].framePoses[j] = RL_MALLOC(animations[i].boneCount*sizeof(Transform));
+                    float time = ((float) j*GLTF_ANIMDELAY)/1000.0f;
+                    
+                    for (unsigned int k = 0; k < animations[i].boneCount; k++)
+                    {
+                        Vector3 translation = {0, 0, 0};
+                        Quaternion rotation = {0, 0, 0, 1};
+                        Vector3 scale = {1, 1, 1};
+                        
+                        if (boneChannels[k].translate)
+                        {
+                            if (!GetPoseAtTimeGLTF(boneChannels[k].translate->sampler->input, boneChannels[k].translate->sampler->output, time, &translation))
+                            {
+                                TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load translate pose data for bone %s", fileName, animations[i].bones[k].name);
+                            }
+                        }
+
+                        if (boneChannels[k].rotate)
+                        {
+                            if (!GetPoseAtTimeGLTF(boneChannels[k].rotate->sampler->input, boneChannels[k].rotate->sampler->output, time, &rotation))
+                            {
+                                TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load rotate pose data for bone %s", fileName, animations[i].bones[k].name);
+                            }
+                        }
+
+                        if (boneChannels[k].scale)
+                        {
+                            if (!GetPoseAtTimeGLTF(boneChannels[k].scale->sampler->input, boneChannels[k].scale->sampler->output, time, &scale))
+                            {
+                                TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load scale pose data for bone %s", fileName, animations[i].bones[k].name);
+                            }
+                        }
+
+                        animations[i].framePoses[j][k] = (Transform){
+                            .translation = translation,
+                            .rotation = rotation,
+                            .scale = scale
+                        };
+                    }
+
+                    BuildPoseFromParentJoints(animations[i].bones, animations[i].boneCount, animations[i].framePoses[j]);
+                }
+
+                TRACELOG(LOG_INFO, "MODEL: [%s] Loaded animation: %s (%d frames, %fs)", fileName, animData.name, animations[i].frameCount, animDuration);
+                RL_FREE(boneChannels);
+            }
+        } 
+        else TRACELOG(LOG_ERROR, "MODEL: [%s] expected exactly one skin to load animation data from, but found %i", fileName, data->skins_count);
+
+        cgltf_free(data);
+    }
+
+    return animations;
+}
 #endif
 
 #if defined(SUPPORT_FILEFORMAT_VOX)
@@ -5437,12 +5591,14 @@                 model.meshes[k].vertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
                 model.meshes[k].texcoords = (float *)RL_CALLOC(model.meshes[k].vertexCount*2, sizeof(float));
                 model.meshes[k].normals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
+                
                 // without material, we rely on vertex colors
                 if (mi == M3D_UNDEF && model.meshes[k].colors == NULL)
                 {
                     model.meshes[k].colors = RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char));
                     for (j = 0; j < model.meshes[k].vertexCount*4; j += 4) memcpy(&model.meshes[k].colors[j], &WHITE, 4);
                 }
+                
                 if (m3d->numbone && m3d->numskin)
                 {
                     model.meshes[k].boneIds = (unsigned char *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char));
@@ -5450,6 +5606,7 @@                     model.meshes[k].animVertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
                     model.meshes[k].animNormals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
                 }
+                
                 model.meshMaterial[k] = mi + 1;
                 l = 0;
             }
@@ -5478,25 +5635,25 @@ 
             if (m3d->face[i].texcoord[0] != M3D_UNDEF)
             {
-                model.meshes[k].texcoords[l * 6 + 0] = m3d->tmap[m3d->face[i].texcoord[0]].u;
-                model.meshes[k].texcoords[l * 6 + 1] = 1.0 - m3d->tmap[m3d->face[i].texcoord[0]].v;
-                model.meshes[k].texcoords[l * 6 + 2] = m3d->tmap[m3d->face[i].texcoord[1]].u;
-                model.meshes[k].texcoords[l * 6 + 3] = 1.0 - m3d->tmap[m3d->face[i].texcoord[1]].v;
-                model.meshes[k].texcoords[l * 6 + 4] = m3d->tmap[m3d->face[i].texcoord[2]].u;
-                model.meshes[k].texcoords[l * 6 + 5] = 1.0 - m3d->tmap[m3d->face[i].texcoord[2]].v;
+                model.meshes[k].texcoords[l*6 + 0] = m3d->tmap[m3d->face[i].texcoord[0]].u;
+                model.meshes[k].texcoords[l*6 + 1] = 1.0 - m3d->tmap[m3d->face[i].texcoord[0]].v;
+                model.meshes[k].texcoords[l*6 + 2] = m3d->tmap[m3d->face[i].texcoord[1]].u;
+                model.meshes[k].texcoords[l*6 + 3] = 1.0 - m3d->tmap[m3d->face[i].texcoord[1]].v;
+                model.meshes[k].texcoords[l*6 + 4] = m3d->tmap[m3d->face[i].texcoord[2]].u;
+                model.meshes[k].texcoords[l*6 + 5] = 1.0 - m3d->tmap[m3d->face[i].texcoord[2]].v;
             }
 
             if (m3d->face[i].normal[0] != M3D_UNDEF)
             {
-                model.meshes[k].normals[l * 9 + 0] = m3d->vertex[m3d->face[i].normal[0]].x;
-                model.meshes[k].normals[l * 9 + 1] = m3d->vertex[m3d->face[i].normal[0]].y;
-                model.meshes[k].normals[l * 9 + 2] = m3d->vertex[m3d->face[i].normal[0]].z;
-                model.meshes[k].normals[l * 9 + 3] = m3d->vertex[m3d->face[i].normal[1]].x;
-                model.meshes[k].normals[l * 9 + 4] = m3d->vertex[m3d->face[i].normal[1]].y;
-                model.meshes[k].normals[l * 9 + 5] = m3d->vertex[m3d->face[i].normal[1]].z;
-                model.meshes[k].normals[l * 9 + 6] = m3d->vertex[m3d->face[i].normal[2]].x;
-                model.meshes[k].normals[l * 9 + 7] = m3d->vertex[m3d->face[i].normal[2]].y;
-                model.meshes[k].normals[l * 9 + 8] = m3d->vertex[m3d->face[i].normal[2]].z;
+                model.meshes[k].normals[l*9 + 0] = m3d->vertex[m3d->face[i].normal[0]].x;
+                model.meshes[k].normals[l*9 + 1] = m3d->vertex[m3d->face[i].normal[0]].y;
+                model.meshes[k].normals[l*9 + 2] = m3d->vertex[m3d->face[i].normal[0]].z;
+                model.meshes[k].normals[l*9 + 3] = m3d->vertex[m3d->face[i].normal[1]].x;
+                model.meshes[k].normals[l*9 + 4] = m3d->vertex[m3d->face[i].normal[1]].y;
+                model.meshes[k].normals[l*9 + 5] = m3d->vertex[m3d->face[i].normal[1]].z;
+                model.meshes[k].normals[l*9 + 6] = m3d->vertex[m3d->face[i].normal[2]].x;
+                model.meshes[k].normals[l*9 + 7] = m3d->vertex[m3d->face[i].normal[2]].y;
+                model.meshes[k].normals[l*9 + 8] = m3d->vertex[m3d->face[i].normal[2]].z;
             }
 
             // Add skin (vertex / bone weight pairs)
@@ -5519,8 +5676,8 @@                     {
                         // raylib does not handle boneless meshes with skeletal animations, so
                         // we put all vertices without a bone into a special "no bone" bone
-                        model.meshes[k].boneIds[l * 12 + n * 4] = m3d->numbone;
-                        model.meshes[k].boneWeights[l * 12 + n * 4] = 1.0f;
+                        model.meshes[k].boneIds[l*12 + n*4] = m3d->numbone;
+                        model.meshes[k].boneWeights[l*12 + n*4] = 1.0f;
                     }
                 }
             }
@@ -5615,6 +5772,7 @@                 model.bindPose[i].rotation.y = m3d->vertex[m3d->bone[i].ori].y;
                 model.bindPose[i].rotation.z = m3d->vertex[m3d->bone[i].ori].z;
                 model.bindPose[i].rotation.w = m3d->vertex[m3d->bone[i].ori].w;
+                
                 // TODO: if the orientation quaternion not normalized, then that's encoding scaling
                 model.bindPose[i].rotation = QuaternionNormalize(model.bindPose[i].rotation);
                 model.bindPose[i].scale.x = model.bindPose[i].scale.y = model.bindPose[i].scale.z = 1.0f;
@@ -5660,15 +5818,16 @@     return model;
 }
 
+#define M3D_ANIMDELAY 17    // Animation frames delay, (~1000 ms/60 FPS = 16.666666* ms)
+
 // Load M3D animation data
-#define M3D_ANIMDELAY 17    // that's roughly ~1000 msec / 60 FPS (16.666666* msec)
 static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int *animCount)
 {
     m3d_t *m3d = NULL;
     unsigned int bytesRead = 0;
     unsigned char *fileData = LoadFileData(fileName, &bytesRead);
     ModelAnimation *animations = NULL;
-    int i, j;
+    int i = 0, j = 0;
 
     *animCount = 0;
 
@@ -5685,7 +5844,7 @@         else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i animations, %i bones, %i skins", fileName,
             m3d->numaction, m3d->numbone, m3d->numskin);
 
-        // no animation or bone+skin?
+        // No animation or bone+skin?
         if (!m3d->numaction || !m3d->numbone || !m3d->numskin)
         {
             m3d_free(m3d);
@@ -5721,7 +5880,8 @@             {
                 animations[a].framePoses[i] = RL_MALLOC((m3d->numbone + 1)*sizeof(Transform));
 
-                m3db_t *pose = m3d_pose(m3d, a, i * M3D_ANIMDELAY);
+                m3db_t *pose = m3d_pose(m3d, a, i*M3D_ANIMDELAY);
+                
                 if (pose != NULL)
                 {
                     for (j = 0; j < m3d->numbone; j++)
raylib/src/rshapes.c view
@@ -26,7 +26,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2013-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2013-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
raylib/src/rtext.c view
@@ -30,7 +30,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2013-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2013-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
@@ -199,11 +199,11 @@     // Re-construct image from defaultFontData and generate OpenGL texture
     //----------------------------------------------------------------------
     Image imFont = {
-        .data = calloc(128*128, 2),  // 2 bytes per pixel (gray + alpha)
+        .data = RL_CALLOC(128*128, 2),  // 2 bytes per pixel (gray + alpha)
         .width = 128,
         .height = 128,
-        .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA,
-        .mipmaps = 1
+        .mipmaps = 1,
+        .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA
     };
 
     // Fill image.data with defaultFontData (convert from bit to pixel!)
@@ -454,8 +454,8 @@         .data = pixels,
         .width = image.width,
         .height = image.height,
-        .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
-        .mipmaps = 1
+        .mipmaps = 1,
+        .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
     };
 
     // Set font with all data parsed from image
@@ -620,11 +620,11 @@                 if (ch == 32)
                 {
                     Image imSpace = {
-                        .data = calloc(chars[i].advanceX*fontSize, 2),
+                        .data = RL_CALLOC(chars[i].advanceX*fontSize, 2),
                         .width = chars[i].advanceX,
                         .height = fontSize,
-                        .format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE,
-                        .mipmaps = 1
+                        .mipmaps = 1,
+                        .format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE
                     };
 
                     chars[i].image = imSpace;
@@ -867,7 +867,7 @@     byteCount += sprintf(txtData + byteCount, "// more info and bugs-report:  github.com/raysan5/raylib                              //\n");
     byteCount += sprintf(txtData + byteCount, "// feedback and support:       ray[at]raylib.com                                      //\n");
     byteCount += sprintf(txtData + byteCount, "//                                                                                    //\n");
-    byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2022 Ramon Santamaria (@raysan5)                                //\n");
+    byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2023 Ramon Santamaria (@raysan5)                                //\n");
     byteCount += sprintf(txtData + byteCount, "//                                                                                    //\n");
     byteCount += sprintf(txtData + byteCount, "// ---------------------------------------------------------------------------------- //\n");
     byteCount += sprintf(txtData + byteCount, "//                                                                                    //\n");
@@ -896,7 +896,7 @@ 
     // Compress font image data
     int compDataSize = 0;
-    unsigned char *compData = CompressData(image.data, imageDataSize, &compDataSize);
+    unsigned char *compData = CompressData((const unsigned char *)image.data, imageDataSize, &compDataSize);
 
     // Save font image data (compressed)
     byteCount += sprintf(txtData + byteCount, "#define COMPRESSED_DATA_SIZE_FONT_%s %i\n\n", TextToUpper(fileNamePascal), compDataSize);
@@ -1665,7 +1665,7 @@     int codepointCount = 0;
 
     // Allocate a big enough buffer to store as many codepoints as text bytes
-    int *codepoints = RL_CALLOC(textLength, sizeof(int));
+    int *codepoints = (int *)RL_CALLOC(textLength, sizeof(int));
 
     for (int i = 0; i < textLength; codepointCount++)
     {
@@ -1674,7 +1674,7 @@     }
 
     // Re-allocate buffer to the actual number of codepoints loaded
-    void *temp = RL_REALLOC(codepoints, codepointCount*sizeof(int));
+    int *temp = (int *)RL_REALLOC(codepoints, codepointCount*sizeof(int));
     if (temp != NULL) codepoints = temp;
 
     *count = codepointCount;
@@ -1992,7 +1992,7 @@     if (lastSlash != NULL)
     {
         // NOTE: We need some extra space to avoid memory corruption on next allocations!
-        imPath = RL_CALLOC(TextLength(fileName) - TextLength(lastSlash) + TextLength(imFileName) + 4, 1);
+        imPath = (char *)RL_CALLOC(TextLength(fileName) - TextLength(lastSlash) + TextLength(imFileName) + 4, 1);
         memcpy(imPath, fileName, TextLength(fileName) - TextLength(lastSlash) + 1);
         memcpy(imPath + TextLength(fileName) - TextLength(lastSlash) + 1, imFileName, TextLength(imFileName));
     }
@@ -2006,11 +2006,11 @@     {
         // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel
         Image imFontAlpha = {
-            .data = calloc(imFont.width*imFont.height, 2),
+            .data = RL_CALLOC(imFont.width*imFont.height, 2),
             .width = imFont.width,
             .height = imFont.height,
-            .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA,
-            .mipmaps = 1
+            .mipmaps = 1,
+            .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA
         };
 
         for (int p = 0, i = 0; p < (imFont.width*imFont.height*2); p += 2, i++)
raylib/src/rtextures.c view
@@ -14,8 +14,9 @@ *   #define SUPPORT_FILEFORMAT_GIF
 *   #define SUPPORT_FILEFORMAT_QOI
 *   #define SUPPORT_FILEFORMAT_PSD
-*   #define SUPPORT_FILEFORMAT_PIC
 *   #define SUPPORT_FILEFORMAT_HDR
+*   #define SUPPORT_FILEFORMAT_PIC
+*   #define SUPPORT_FILEFORMAT_PNM
 *   #define SUPPORT_FILEFORMAT_DDS
 *   #define SUPPORT_FILEFORMAT_PKM
 *   #define SUPPORT_FILEFORMAT_KTX
@@ -42,7 +43,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2013-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2013-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
@@ -103,6 +104,9 @@ #if !defined(SUPPORT_FILEFORMAT_HDR)
     #define STBI_NO_HDR
 #endif
+#if !defined(SUPPORT_FILEFORMAT_PNM)
+    #define STBI_NO_PNM
+#endif
 
 #if defined(SUPPORT_FILEFORMAT_DDS)
     #define RL_GPUTEX_SUPPORT_DDS
@@ -121,9 +125,6 @@ #endif
 
 // Image fileformats not supported by default
-#define STBI_NO_PIC
-#define STBI_NO_PNM             // Image format .ppm and .pgm
-
 #if defined(__TINYC__)
     #define STBI_NO_SIMD
 #endif
@@ -134,8 +135,9 @@      defined(SUPPORT_FILEFORMAT_JPG) || \
      defined(SUPPORT_FILEFORMAT_PSD) || \
      defined(SUPPORT_FILEFORMAT_GIF) || \
+     defined(SUPPORT_FILEFORMAT_HDR) || \
      defined(SUPPORT_FILEFORMAT_PIC) || \
-     defined(SUPPORT_FILEFORMAT_HDR))
+     defined(SUPPORT_FILEFORMAT_PNM))
 
     #define STBI_MALLOC RL_MALLOC
     #define STBI_FREE RL_FREE
@@ -234,6 +236,7 @@     defined(SUPPORT_FILEFORMAT_GIF) || \
     defined(SUPPORT_FILEFORMAT_PIC) || \
     defined(SUPPORT_FILEFORMAT_HDR) || \
+    defined(SUPPORT_FILEFORMAT_PNM) || \
     defined(SUPPORT_FILEFORMAT_PSD)
 
     #define STBI_REQUIRED
@@ -344,6 +347,9 @@ #if defined(SUPPORT_FILEFORMAT_PIC)
         || (strcmp(fileType, ".pic") == 0)
 #endif
+#if defined(SUPPORT_FILEFORMAT_PNM)
+        || ((strcmp(fileType, ".ppm") == 0) || (strcmp(fileType, ".pgm") == 0))
+#endif
 #if defined(SUPPORT_FILEFORMAT_PSD)
         || (strcmp(fileType, ".psd") == 0)
 #endif
@@ -603,7 +609,7 @@     byteCount += sprintf(txtData + byteCount, "// more info and bugs-report:  github.com/raysan5/raylib                              //\n");
     byteCount += sprintf(txtData + byteCount, "// feedback and support:       ray[at]raylib.com                                      //\n");
     byteCount += sprintf(txtData + byteCount, "//                                                                                    //\n");
-    byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2022 Ramon Santamaria (@raysan5)                                //\n");
+    byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2023 Ramon Santamaria (@raysan5)                                //\n");
     byteCount += sprintf(txtData + byteCount, "//                                                                                    //\n");
     byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n");
 
@@ -3815,7 +3821,7 @@     if (alpha < 0.0f) alpha = 0.0f;
     else if (alpha > 1.0f) alpha = 1.0f;
 
-    return (Color){color.r, color.g, color.b, (unsigned char)(255.0f*alpha)};
+    return (Color){ color.r, color.g, color.b, (unsigned char)(255.0f*alpha) };
 }
 
 // Get hexadecimal value for a Color
@@ -3936,6 +3942,105 @@     color.b = (unsigned char)((value - value*saturation*k)*255.0f);
 
     return color;
+}
+
+// Get color multiplied with another color
+Color ColorTint(Color color, Color tint)
+{
+    Color result = color;
+
+    float cR = (float)tint.r/255;
+    float cG = (float)tint.g/255;
+    float cB = (float)tint.b/255;
+    float cA = (float)tint.a/255;
+
+    unsigned char r = (unsigned char)(((float)color.r/255*cR)*255.0f);
+    unsigned char g = (unsigned char)(((float)color.g/255*cG)*255.0f);
+    unsigned char b = (unsigned char)(((float)color.b/255*cB)*255.0f);
+    unsigned char a = (unsigned char)(((float)color.a/255*cA)*255.0f);
+
+    result.r = r;
+    result.g = g;
+    result.b = b;
+    result.a = a;
+
+    return result;
+}
+
+// Get color with brightness correction, brightness factor goes from -1.0f to 1.0f
+Color ColorBrightness(Color color, float factor)
+{
+    Color result = color;
+    
+    if (factor > 1.0f) factor = 1.0f;
+    else if (factor < -1.0f) factor = -1.0f;
+    
+    float red = (float)color.r;
+    float green = (float)color.g;
+    float blue = (float)color.b;
+
+    if (factor < 0.0f)
+    {
+        factor = 1.0f + factor;
+        red *= factor;
+        green *= factor;
+        blue *= factor;
+    }
+    else
+    {
+        red = (255 - red)*factor + red;
+        green = (255 - green)*factor + green;
+        blue = (255 - blue)*factor + blue;
+    }
+    
+    result.r = (unsigned char)red;
+    result.g = (unsigned char)green;
+    result.b = (unsigned char)blue;
+
+    return result;
+}
+
+// Get color with contrast correction
+// NOTE: Contrast values between -1.0f and 1.0f
+Color ColorContrast(Color color, float contrast)
+{
+    Color result = color;
+    
+    if (contrast < -1.0f) contrast = -1.0f;
+    else if (contrast > 1.0f) contrast = 1.0f;
+
+    contrast = (1.0f + contrast);
+    contrast *= contrast;
+
+    float pR = (float)color.r/255.0f;
+    pR -= 0.5f;
+    pR *= contrast;
+    pR += 0.5f;
+    pR *= 255;
+    if (pR < 0) pR = 0;
+    else if (pR > 255) pR = 255;
+
+    float pG = (float)color.g/255.0f;
+    pG -= 0.5f;
+    pG *= contrast;
+    pG += 0.5f;
+    pG *= 255;
+    if (pG < 0) pG = 0;
+    else if (pG > 255) pG = 255;
+
+    float pB = (float)color.b/255.0f;
+    pB -= 0.5f;
+    pB *= contrast;
+    pB += 0.5f;
+    pB *= 255;
+    if (pB < 0) pB = 0;
+    else if (pB > 255) pB = 255;
+
+    result.r = (unsigned char)pR;
+    result.g = (unsigned char)pG;
+    result.b = (unsigned char)pB;
+
+    return result;
 }
 
 // Get color with alpha applied, alpha goes from 0.0f to 1.0f
raylib/src/utils.c view
@@ -11,7 +11,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2014-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2014-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
@@ -290,7 +290,7 @@     byteCount += sprintf(txtData + byteCount, "// more info and bugs-report:  github.com/raysan5/raylib                              //\n");
     byteCount += sprintf(txtData + byteCount, "// feedback and support:       ray[at]raylib.com                                      //\n");
     byteCount += sprintf(txtData + byteCount, "//                                                                                    //\n");
-    byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2022 Ramon Santamaria (@raysan5)                                     //\n");
+    byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2022-2023 Ramon Santamaria (@raysan5)                                //\n");
     byteCount += sprintf(txtData + byteCount, "//                                                                                    //\n");
     byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n");
 
raylib/src/utils.h view
@@ -5,7 +5,7 @@ *
 *   LICENSE: zlib/libpng
 *
-*   Copyright (c) 2014-2022 Ramon Santamaria (@raysan5)
+*   Copyright (c) 2014-2023 Ramon Santamaria (@raysan5)
 *
 *   This software is provided "as-is", without any express or implied warranty. In no event
 *   will the authors be held liable for any damages arising from the use of this software.
src/Raylib.hs view
@@ -3582,6 +3582,33 @@   p'colorFromHSV ::
     FunPtr (CFloat -> CFloat -> CFloat -> IO Raylib.Types.Color)
 
+foreign import ccall safe "bindings.h ColorTint_" c'colorTint :: Ptr Color -> Ptr Color -> IO (Ptr Raylib.Types.Color)
+
+colorTint :: Color -> Color -> Raylib.Types.Color
+colorTint color tint = unsafePerformIO $ with color (with tint . c'colorTint) >>= pop
+
+foreign import ccall safe "raylib.h &ColorTint"
+  p'colorTint ::
+    FunPtr (Color -> Color -> IO Raylib.Types.Color)
+
+foreign import ccall safe "bindings.h ColorBrightness_" c'colorBrightness :: Ptr Color -> CFloat -> IO (Ptr Raylib.Types.Color)
+
+colorBrightness :: Color -> Float -> Raylib.Types.Color
+colorBrightness color brightness = unsafePerformIO $ with color (\c -> c'colorBrightness c (realToFrac brightness)) >>= pop
+
+foreign import ccall safe "raylib.h &ColorBrightness"
+  p'colorBrightness ::
+    FunPtr (Color -> CFloat -> IO Raylib.Types.Color)
+
+foreign import ccall safe "bindings.h ColorContrast_" c'colorContrast :: Ptr Color -> CFloat -> IO (Ptr Raylib.Types.Color)
+
+colorContrast :: Color -> Float -> Raylib.Types.Color
+colorContrast color contrast = unsafePerformIO $ with color (\c -> c'colorContrast c (realToFrac contrast)) >>= pop
+
+foreign import ccall safe "raylib.h &ColorContrast"
+  p'colorContrast ::
+    FunPtr (Color -> CFloat -> IO Raylib.Types.Color)
+
 foreign import ccall safe "bindings.h ColorAlpha_" c'colorAlpha :: Ptr Raylib.Types.Color -> CFloat -> IO (Ptr Raylib.Types.Color)
 
 colorAlpha :: Raylib.Types.Color -> Float -> Raylib.Types.Color
@@ -4187,24 +4214,6 @@ foreign import ccall safe "raylib.h &DrawCubeWiresV"
   p'drawCubeWiresV ::
     FunPtr (Raylib.Types.Vector3 -> Raylib.Types.Vector3 -> Raylib.Types.Color -> IO ())
-
-foreign import ccall safe "bindings.h DrawCubeTexture_" c'drawCubeTexture :: Ptr Raylib.Types.Texture -> Ptr Raylib.Types.Vector3 -> CFloat -> CFloat -> CFloat -> Ptr Raylib.Types.Color -> IO ()
-
-drawCubeTexture :: Raylib.Types.Texture -> Raylib.Types.Vector3 -> Float -> Float -> Float -> Raylib.Types.Color -> IO ()
-drawCubeTexture texture position width height length color = with texture (\t -> with position (\p -> with color (c'drawCubeTexture t p (realToFrac width) (realToFrac height) (realToFrac length))))
-
-foreign import ccall safe "raylib.h &DrawCubeTexture"
-  p'drawCubeTexture ::
-    FunPtr (Raylib.Types.Texture -> Raylib.Types.Vector3 -> CFloat -> CFloat -> CFloat -> Raylib.Types.Color -> IO ())
-
-foreign import ccall safe "bindings.h DrawCubeTextureRec_" c'drawCubeTextureRec :: Ptr Raylib.Types.Texture -> Ptr Raylib.Types.Rectangle -> Ptr Raylib.Types.Vector3 -> CFloat -> CFloat -> CFloat -> Ptr Raylib.Types.Color -> IO ()
-
-drawCubeTextureRec :: Raylib.Types.Texture -> Raylib.Types.Rectangle -> Raylib.Types.Vector3 -> Float -> Float -> Float -> Raylib.Types.Color -> IO ()
-drawCubeTextureRec texture source position width height length color = with texture (\t -> with source (\s -> with position (\p -> with color (c'drawCubeTextureRec t s p (realToFrac width) (realToFrac height) (realToFrac length)))))
-
-foreign import ccall safe "raylib.h &DrawCubeTextureRec"
-  p'drawCubeTextureRec ::
-    FunPtr (Raylib.Types.Texture -> Raylib.Types.Rectangle -> Raylib.Types.Vector3 -> CFloat -> CFloat -> CFloat -> Raylib.Types.Color -> IO ())
 
 foreign import ccall safe "bindings.h DrawSphere_" c'drawSphere :: Ptr Raylib.Types.Vector3 -> CFloat -> Ptr Raylib.Types.Color -> IO ()