From 6e4fdd3b3ae4e4656e151f098c40cfe551a36e8c Mon Sep 17 00:00:00 2001 From: jussi Date: Fri, 18 Feb 2022 18:27:10 +0200 Subject: [PATCH] Added initial files. --- .vscode/launch.json | 27 + API.md | 2955 +++++++++++++ CMakeLists.txt | 49 + build/.gitignore | 5 + devnotes | 27 + doc_parser.lua | 171 + examples/dungeon_crawler/main.lua | 147 + examples/gui/main.lua | 70 + examples/image_draw/main.lua | 30 + examples/pixelated/main.lua | 55 + examples/ray/main.lua | 47 + examples/resources/images/LICENCE | 5 + examples/resources/images/apple.png | Bin 0 -> 239 bytes examples/resources/images/cat.png | Bin 0 -> 388467 bytes examples/resources/images/grass.png | Bin 0 -> 1065 bytes examples/resources/images/snake.png | Bin 0 -> 1933 bytes examples/resources/images/tiles.png | Bin 0 -> 32917 bytes examples/resources/shaders/glsl100/wave.fs | 36 + examples/resources/shaders/glsl330/wave.fs | 37 + examples/shaders/main.lua | 60 + examples/snake/main.lua | 219 + include/audio.h | 16 + include/core.h | 101 + include/lapi.h | 24 + include/lauxlib.h | 264 ++ include/lua.h | 486 ++ include/lua_core.h | 27 + include/luaconf.h | 790 ++++ include/lualib.h | 61 + include/main.h | 16 + include/models.h | 68 + include/raudio.h | 198 + include/raygui.h | 4342 ++++++++++++++++++ include/raylib.h | 1538 +++++++ include/raymath.h | 1850 ++++++++ include/rgui.h | 27 + include/rlgl.h | 4673 ++++++++++++++++++++ include/rmath.h | 50 + include/shapes.h | 21 + include/state.h | 71 + include/text.h | 8 + include/textures.h | 44 + src/audio.c | 272 ++ src/core.c | 1908 ++++++++ src/lua_core.c | 1033 +++++ src/main.c | 37 + src/models.c | 2113 +++++++++ src/rgui.c | 544 +++ src/rmath.c | 978 ++++ src/shapes.c | 457 ++ src/state.c | 194 + src/text.c | 102 + src/textures.c | 1057 +++++ 53 files changed, 27310 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 API.md create mode 100644 CMakeLists.txt create mode 100644 build/.gitignore create mode 100644 devnotes create mode 100644 doc_parser.lua create mode 100644 examples/dungeon_crawler/main.lua create mode 100644 examples/gui/main.lua create mode 100644 examples/image_draw/main.lua create mode 100644 examples/pixelated/main.lua create mode 100644 examples/ray/main.lua create mode 100644 examples/resources/images/LICENCE create mode 100644 examples/resources/images/apple.png create mode 100644 examples/resources/images/cat.png create mode 100644 examples/resources/images/grass.png create mode 100644 examples/resources/images/snake.png create mode 100644 examples/resources/images/tiles.png create mode 100644 examples/resources/shaders/glsl100/wave.fs create mode 100644 examples/resources/shaders/glsl330/wave.fs create mode 100644 examples/shaders/main.lua create mode 100644 examples/snake/main.lua create mode 100644 include/audio.h create mode 100644 include/core.h create mode 100644 include/lapi.h create mode 100644 include/lauxlib.h create mode 100644 include/lua.h create mode 100644 include/lua_core.h create mode 100644 include/luaconf.h create mode 100644 include/lualib.h create mode 100644 include/main.h create mode 100644 include/models.h create mode 100644 include/raudio.h create mode 100644 include/raygui.h create mode 100644 include/raylib.h create mode 100644 include/raymath.h create mode 100644 include/rgui.h create mode 100644 include/rlgl.h create mode 100644 include/rmath.h create mode 100644 include/shapes.h create mode 100644 include/state.h create mode 100644 include/text.h create mode 100644 include/textures.h create mode 100644 src/audio.c create mode 100644 src/core.c create mode 100644 src/lua_core.c create mode 100644 src/main.c create mode 100644 src/models.c create mode 100644 src/rgui.c create mode 100644 src/rmath.c create mode 100644 src/shapes.c create mode 100644 src/state.c create mode 100644 src/text.c create mode 100644 src/textures.c diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..662de51 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/ReiLua", + "args": ["/examples/font/"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} diff --git a/API.md b/API.md new file mode 100644 index 0000000..caa9303 --- /dev/null +++ b/API.md @@ -0,0 +1,2955 @@ +# ReiLua API + +## Usage + +Application needs 'main.lua' file as entry point. ReiLua executable will first look it from same directory +or it's path can be given by argument. There are three global functions that the engine will call, 'init', 'process' and 'draw'. + +--- +> function init() + +This function will be called first when 'main.lua' is found + +--- + +> function process( delta ) + +This function will be called every frame during execution. It will get time duration from last frame on argument 'delta' + +--- + +> function draw() + +This function will be called every frame after process and it should have all rendering related functions. +Note: Engine will call Raylib functions 'BeginDrawing()' before this function call and 'EndDrawing()' after it + +--- + +## Globals - Keys + +KEY_ENTER + +KEY_SPACE + +KEY_ESCAPE + +KEY_ENTER + +KEY_TAB + +KEY_BACKSPACE + +KEY_INSERT + +KEY_DELETE + +KEY_RIGHT + +KEY_LEFT + +KEY_DOWN + +KEY_UP + +## Globals - WindowFlags + +FLAG_VSYNC_HINT + +FLAG_FULLSCREEN_MODE + +FLAG_WINDOW_RESIZABLE + +FLAG_WINDOW_UNDECORATED + +FLAG_WINDOW_HIDDEN + +FLAG_WINDOW_MINIMIZED + +FLAG_WINDOW_MAXIMIZED + +FLAG_WINDOW_UNFOCUSED + +FLAG_WINDOW_TOPMOST + +FLAG_WINDOW_ALWAYS_RUN + +FLAG_WINDOW_TRANSPARENT + +FLAG_WINDOW_HIGHDPI + +FLAG_MSAA_4X_HINT + +FLAG_INTERLACED_HINT + +## Globals - BlendModes + +BLEND_ALPHA + +BLEND_ADDITIVE + +BLEND_MULTIPLIED + +BLEND_ADD_COLORS + +BLEND_SUBTRACT_COLORS + +BLEND_CUSTOM + +## Globals - TextureModes + +TEXTURE_SOURCE_TEXTURE + +TEXTURE_SOURCE_RENDER_TEXTURE + +## Globals - CameraProjections + +CAMERA_PERSPECTIVE + +CAMERA_ORTHOGRAPHIC + +## Globals - CameraMode + +CAMERA_CUSTOM + +CAMERA_FREE + +CAMERA_ORBITAL + +CAMERA_FIRST_PERSON + +CAMERA_THIRD_PERSON + +## Globals - MapTypes + +MATERIAL_MAP_ALBEDO + +MATERIAL_MAP_METALNESS + +MATERIAL_MAP_NORMAL + +MATERIAL_MAP_ROUGHNESS + +MATERIAL_MAP_OCCLUSION + +MATERIAL_MAP_EMISSION + +MATERIAL_MAP_HEIGHT + +MATERIAL_MAP_CUBEMAP + +MATERIAL_MAP_IRRADIANCE + +MATERIAL_MAP_PREFILTER + +MATERIAL_MAP_BRDF + +## Globals - TextureFilters + +TEXTURE_FILTER_POINT + +TEXTURE_FILTER_BILINEAR + +TEXTURE_FILTER_TRILINEAR + +TEXTURE_FILTER_ANISOTROPIC_4X + +TEXTURE_FILTER_ANISOTROPIC_8X + +TEXTURE_FILTER_ANISOTROPIC_16X + +## Globals - TextureWrapModes + +TEXTURE_WRAP_REPEAT + +TEXTURE_WRAP_CLAMP + +TEXTURE_WRAP_MIRROR_REPEAT + +TEXTURE_WRAP_MIRROR_CLAMP + +## Globals - TraceLogLevel + +LOG_ALL + +LOG_TRACE + +LOG_DEBUG + +LOG_INFO + +LOG_WARNING + +LOG_ERROR + +LOG_FATAL + +LOG_NONE + +## Globals - N-patchLayout + +NPATCH_NINE_PATCH + +NPATCH_THREE_PATCH_VERTICAL + +NPATCH_THREE_PATCH_HORIZONTAL + +## Globals - Shader + +SHADER_LOC_VERTEX_POSITION + +SHADER_LOC_VERTEX_TEXCOORD01 + +SHADER_LOC_VERTEX_TEXCOORD02 + +SHADER_LOC_VERTEX_NORMAL + +SHADER_LOC_VERTEX_TANGENT + +SHADER_LOC_VERTEX_COLOR + +SHADER_LOC_MATRIX_MVP + +SHADER_LOC_MATRIX_VIEW + +SHADER_LOC_MATRIX_PROJECTION + +SHADER_LOC_MATRIX_MODEL + +SHADER_LOC_MATRIX_NORMAL + +SHADER_LOC_VECTOR_VIEW + +SHADER_LOC_COLOR_DIFFUSE + +SHADER_LOC_COLOR_SPECULAR + +SHADER_LOC_COLOR_AMBIENT + +SHADER_LOC_MAP_ALBEDO + +SHADER_LOC_MAP_METALNESS + +SHADER_LOC_MAP_NORMAL + +SHADER_LOC_MAP_ROUGHNESS + +SHADER_LOC_MAP_OCCLUSION + +SHADER_LOC_MAP_EMISSION + +SHADER_LOC_MAP_HEIGHT + +SHADER_LOC_MAP_CUBEMAP + +SHADER_LOC_MAP_IRRADIANCE + +SHADER_LOC_MAP_PREFILTER + +SHADER_LOC_MAP_BRDF + +## Globals - Shader + +SHADER_UNIFORM_FLOAT + +SHADER_UNIFORM_VEC2 + +SHADER_UNIFORM_VEC3 + +SHADER_UNIFORM_VEC4 + +SHADER_UNIFORM_INT + +SHADER_UNIFORM_IVEC2 + +SHADER_UNIFORM_IVEC3 + +SHADER_UNIFORM_IVEC4 + +SHADER_UNIFORM_SAMPLER2D + +## Globals - Shader + +SHADER_ATTRIB_FLOAT + +SHADER_ATTRIB_VEC2 + +SHADER_ATTRIB_VEC3 + +SHADER_ATTRIB_VEC4 + +## Globals - Colors + +WHITE + +BLACK + +BLANK + +MAGENTA + +RAYWHITE + +RED + +GREEN + +BLUE + +## Globals - Math + +PI + +## Types + +Raylib structs in Lua + +--- + +> Vector2 = { 1.0, 1.0 } + +Vector2 type + +--- + +> Vector3 = { 1.0, 1.0, 1.0 } + +Vector3 type + +--- + +> Vector4 = { 1.0, 1.0, 1.0, 1.0 } + +Vector4 type + +--- + +> Quaternion = { 1.0, 1.0, 1.0, 1.0 } + +Quaternion type + +--- + +> Matrix = { { 1.0, 0.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0, 0.0 }, { 0.0, 0.0, 1.0, 0.0 }, { 0.0, 0.0, 0.0, 1.0 } } + +OpenGL style 4x4. Identity matrix example + +--- + +> Color = { 255, 255, 255, 255 } + +{ r, g, b ,a }. Color type, RGBA (32bit) + +--- + +> Rectangle = { 0.0, 0.0, 1.0, 1.0 } + +{ x, y, w ,h }. Rectangle type + +--- + +> Image = ImageId + +int id. Image type (multiple pixel formats supported). NOTE: Data stored in CPU memory (RAM) + +--- + +> Texture = TextureId + +int id. Texture type (multiple internal formats supported). NOTE: Data stored in GPU memory (VRAM) + +--- + +> RenderTexture = RenderTextureId + +int id. RenderTexture type, for texture rendering + +--- + +> Font = FontId + +int id. Font type, includes texture and chars data + +--- + +> Camera = CameraId + +int id. Defines 3d camera position/orientation + +--- + +> Mesh = MeshId + +int id. Vertex data defining a mesh + +--- + +> Material = MaterialId + +int id. Material type + +``` +table = { + shader = Shader, + maps = { + { + MATERIAL_MAP_ALBEDO, + { + texture = Texture, + color = WHITE, + value = 1.0, + }, + }, + ... + }, + params = { 1.0, 2.0, 3.0, 4.0 }, +} +``` + +--- + +> Model = ModelId + +int id. Basic 3d Model type + +--- + +> Ray = { { 0.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 } } + +{ position, direction }. Ray type (useful for raycast) + +--- + +> RayCollision = { hit = true, distance = 1.0, point = { 0.0, 0.0 }, normal = { 0.0, 0.0, 1.0 } } + +Raycast hit information. NOTE: Data in named keys + +--- + +> BoundingBox = { { 0.0, 0.0, 0.0 }, { 1.0, 1.0, 1.0 } } + +{ min, max }. Bounding box type for 3d mesh + +--- + +> Sound = SoundId + +int id. Basic Sound source and buffer + +--- + +> NPatchInfo = { { 0, 0, 24, 24 }, 0, 0, 0, 0, NPATCH_NINE_PATCH } + +{ Rectangle source, int left, int top, int right, int bottom, int layout }. +{ Texture source rectangle, Left border offset, Top border offset, Right border offset, Bottom border offset, Layout of the n-patch: 3x3, 1x3 or 3x1 } + +--- + +> ModelAnimations = ModelAnimationsId + +int id. ModelAnimations + +--- + +## Core - Window + +--- + +> success = RL_SetWindowMonitor( int monitor ) + +Set monitor for the current window (fullscreen mode) + +- Failure return false +- Success return true + +--- + +> success = RL_SetWindowPosition( Vector2 pos ) + +Set window position on screen + +- Failure return false +- Success return true + +--- + +> success = RL_SetWindowSize( Vector2 size ) + +Set window dimensions + +- Failure return false +- Success return true + +--- + +> position = RL_GetMonitorPosition( int monitor ) + +Get specified monitor position + +- Failure return nil +- Success return Vector2 + +--- + +> size = RL_GetMonitorSize( int monitor ) + +Get specified monitor size + +- Failure return nil +- Success return Vector2 + +--- + +> position = RL_GetWindowPosition() + +Get window position on monitor + +- Success return Vector2 + +--- + +> size = RL_GetWindowPosition() + +Get window size + +- Success return Vector2 + +--- + +> success = RL_SetWindowState( int flag ) + +Set window configuration state using flags ( FLAG_FULLSCREEN_MODE, FLAG_WINDOW_RESIZABLE... ) + +- Failure return false +- Success return true + +--- + +> state = RL_IsWindowState( int flag ) ) + +Check if one specific window flag is enabled ( FLAG_FULLSCREEN_MODE, FLAG_WINDOW_RESIZABLE... ) + +- Failure return nil +- Success return bool + +--- + +> resized = RL_ClearWindowState( int flag ) + +Clear window configuration state flags ( FLAG_FULLSCREEN_MODE, FLAG_WINDOW_RESIZABLE... ) + +- Success return bool + +--- + +> resized = RL_IsWindowResized() + +Check if window has been resized from last frame + +- Success return bool + +--- + +> success = RL_SetWindowIcon( Image image ) + +Set icon for window ( Only PLATFORM_DESKTOP ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetWindowTitle( string title ) + +Set title for window ( Only PLATFORM_DESKTOP ) + +- Failure return false +- Success return true + +--- + +## Core - Timing + +--- + +> success = RL_SetTargetFPS( int fps ) + +Set target FPS ( maximum ) + +- Failure return false +- Success return true + +--- + +> RL_GetFrameTime() + +Get time in seconds for last frame drawn ( Delta time ) + +--- + +> RL_GetTime() + +Get elapsed time in seconds since InitWindow() + +--- + +## Core - Misc + +--- + +> success = RL_TraceLog( int logLevel, string text ) + +Show trace log messages ( LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR... ) + +- Failure return false +- Success return true + +--- + +> success = RL_OpenURL( string url ) + +Open URL with default system browser ( If available ) + +- Failure return false +- Success return true + +--- + +## Core - Cursor + +--- + +> RL_ShowCursor() + +Shows cursor + +--- + +> RL_HideCursor() + +Hides cursor + +--- + +> hidden = RL_IsCursorHidden() + +Check if cursor is not visible + +- Success return bool + +--- + +> RL_EnableCursor() + +Enables cursor (unlock cursor) + +--- + +> RL_DisableCursor() + +Disables cursor (lock cursor) + +--- + +> onSreen = RL_IsCursorOnScreen() + +Check if cursor is on the screen + +- Success return bool + +--- + +## Core - Drawing + +--- + +> success = RL_ClearBackground( Color color ) + +Set background color ( framebuffer clear color ) + +- Failure return false +- Success return true + +--- + +> success = RL_BeginBlendMode( int mode ) + +Begin blending mode ( BLEND_ALPHA, BLEND_ADDITIVE, BLEND_MULTIPLIED... ) + +- Failure return false +- Success return true + +--- + +> RL_EndBlendMode() + +End blending mode ( reset to default: BLEND_ALPHA ) + +--- + +> success = RL_BeginScissorMode( Rectangle rectange ) + +Begin scissor mode ( define screen area for following drawing ) + +- Failure return false +- Success return true + +--- + +> RL_EndScissorMode() + +End scissor mode + +--- + +## Core - Shader + +--- + +> shader = RL_LoadShader( string vsFileName, string fsFileName ) + +Load shader from files and bind default locations + +- Failure return -1 +- Success return int + +--- + +> shader = RL_LoadShaderFromMemory( string vsCode, string fsCode ) + +Load shader from code strings and bind default locations + +- Failure return -1 +- Success return int + +--- + +> success = RL_BeginShaderMode( Shader shader ) + +Begin custom shader drawing + +- Failure return false +- Success return true + +--- + +> EndShaderMode() + +End custom shader drawing ( use default shader ) + +--- + +> location = RL_GetShaderLocation( Shader shader, string uniformName ) + +Get shader uniform location + +- Failure return -1 +- Success return int + +--- + +> location = RL_GetShaderLocationAttrib( Shader shader, string attribName ) + +Get shader attribute location + +- Failure return -1 +- Success return int + +--- + +> success = RL_SetShaderValueMatrix( Shader shader, int locIndex, Matrix mat ) + +Set shader uniform value ( matrix 4x4 ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetShaderValueTexture( Shader shader, int locIndex, Texture2D texture ) + +Set shader uniform value for texture ( sampler2d ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetShaderValue( Shader shader, int locIndex, number{} values, int uniformType ) + +Set shader uniform value +NOTE: Even one value should be in table + +- Failure return false +- Success return true + +--- + +> success = RL_SetShaderValueV( Shader shader, int locIndex, number{} values, int uniformType, int count ) + +Set shader uniform value vector +NOTE: Even one value should be in table + +- Failure return false +- Success return true + +--- + +> success = RL_UnloadShader( Shader shader ) + +Unload shader from GPU memory ( VRAM ) + +- Failure return false +- Success return true + +--- + +## Core - Input + +--- + +> pressed = RL_IsKeyPressed( int key ) + +Detect if a key has been pressed once + +- Failure return nil +- Success return bool + +--- + +> pressed = RL_IsKeyDown( int key ) + +Detect if a key is being pressed + +- Failure return nil +- Success return bool + +--- + +> released = RL_IsKeyReleased( int key ) + +Detect if a key has been released once + +- Failure return nil +- Success return bool + +--- + +> keycode = RL_GetKeyPressed() + +Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty + +- Success return int + +--- + +> unicode = RL_GetCharPressed() + +Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty + +- Success return int + +--- + +> RL_SetExitKey( int key ) + +Set a custom key to exit program ( default is ESC ) + +--- + +> available = RL_IsGamepadAvailable( int gamepad ) + +Detect if a gamepad is available + +- Failure return nil +- Success return bool + +--- + +> pressed = RL_IsGamepadButtonPressed( int gamepad, int button ) + +Detect if a gamepad button has been pressed once + +- Failure return nil +- Success return bool + +--- + +> pressed = RL_IsGamepadButtonDown( int gamepad, int button ) + +Detect if a gamepad button is being pressed + +- Failure return nil +- Success return bool + +--- + +> released = RL_IsGamepadButtonReleased( int gamepad, int button ) + +Detect if a gamepad button has been released once + +- Failure return nil +- Success return bool + +--- + +> count = RL_GetGamepadAxisCount( int gamepad ) + +Return gamepad axis count for a gamepad + +- Failure return false +- Success return int + +--- + +> value = RL_GetGamepadAxisMovement( int gamepad, int axis ) + +Return axis movement value for a gamepad axis + +- Failure return false +- Success return float + +--- + +> name = RL_GetGamepadName( int gamepad ) + +Return gamepad internal name id + +- Failure return false +- Success return string + +--- + +> pressed = RL_IsMouseButtonPressed( int button ) + +Detect if a mouse button has been pressed once + +- Failure return nil +- Success return bool + +--- + +> pressed = RL_IsMouseButtonDown( int button ) + +Detect if a mouse button is being pressed + +- Failure return nil +- Success return bool + +--- + +> released = RL_IsMouseButtonReleased( int button ) + +Detect if a mouse button has been released once + +- Failure return nil +- Success return bool + +--- + +> position = RL_GetMousePosition() + +Returns mouse position + +- Success return Vector2 + +--- + +> position = RL_GetMouseDelta() + +Get mouse delta between frames + +- Success return Vector2 + +--- + +> movement = RL_GetMouseWheelMove() + +Returns mouse wheel movement Y + +- Success return float + +--- + +> success = RL_SetMousePosition( Vector2 position ) + +Set mouse position XY + +- Failure return false +- Success return true + +--- + +## Core - File + +--- + +> path = RL_GetBasePath() + +Return game directory ( where main.lua is located ) + +- Success return string + +--- + +> fileExists = RL_FileExists( string fileName ) + +Check if file exists + +- Failure return nil +- Success return bool + +--- + +> dirExists = RL_DirectoryExists( string dirPath ) + +Check if a directory path exists + +- Failure return nil +- Success return bool + +--- + +> hasFileExtension = RL_IsFileExtension( string fileName, string ext ) + +Check file extension ( Including point: .png, .wav ) + +- Failure return nil +- Success return bool + +--- + +> extension = RL_GetFileExtension( string fileName ) + +Get pointer to extension for a filename string ( Includes dot: '.png' ) + +- Failure return false +- Success return string + +--- + +> filePath = RL_GetFileName( string filePath ) + +Get pointer to filename for a path string + +- Failure return false +- Success return string + +--- + +> filePath = RL_GetFileNameWithoutExt( string filePath ) + +Get filename string without extension ( Uses static string ) + +- Failure return false +- Success return string + +--- + +> filePath = RL_GetDirectoryPath( string filePath ) + +Get full path for a given fileName with path ( Uses static string ) + +- Failure return false +- Success return string + +--- + +> filePath = RL_GetPrevDirectoryPath( string dirPath ) + +Get previous directory path for a given path ( Uses static string ) + +- Failure return false +- Success return string + +--- + +> filePath = RL_GetWorkingDirectory() + +Get current working directory ( Uses static string ) + +- Failure return false +- Success return string + +--- + +> fileNames = RL_GetDirectoryFiles( string dirPath ) + +Get filenames in a directory path + +- Failure return false +- Success return string{} + +--- + +> time = RL_GetFileModTime( string fileName ) + +Get file modification time ( Last write time ) + +- Failure return false +- Success return int + +--- + +## Core - Camera + +--- + +> camera = RL_CreateCamera3D() + +Return camera3D id set to default configuration + +- Success return int + +--- + +> success = RL_UnloadCamera3D( int Camera3D ) + +Unload Camera3D + +- Failure return false +- Success return true + +--- + +> success = RL_BeginMode3D( camera3D camera ) + +Initializes 3D mode with custom camera ( 3D ) + +- Failure return false +- Success return true + +--- + +> RL_EndMode3D() + +Ends 3D mode and returns to default 2D orthographic mode + +--- + +> success = RL_SetCamera3DPosition( camera3D camera, Vector3 position ) + +Set camera position ( Remember to call "RL_UpdateCamera3D()" to apply changes ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetCamera3DTarget( camera3D camera, Vector3 target ) + +Set camera target it looks-at + +- Failure return false +- Success return true + +--- + +> success = RL_SetCamera3DUp( camera3D camera, Vector3 up ) + +Set camera up vector ( Rotation over it's axis ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetCamera3DFovy( camera3D camera, Vector3 fovy ) + +Set camera field-of-view apperture in Y ( degrees ) in perspective, used as near plane width in orthographic + +- Failure return false +- Success return true + +--- + +> success = RL_SetCamera3DProjection( camera3D camera, int projection ) + +Set camera projection mode ( CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetCamera3DMode( camera3D camera, int mode ) + +Set camera mode ( CAMERA_CUSTOM, CAMERA_FREE, CAMERA_ORBITAL... ) + +- Failure return false +- Success return true + +--- + +> position = RL_GetCamera3DPosition( camera3D camera ) + +Get camera position + +- Failure return nil +- Success return Vector3 + +--- + +> target = RL_GetCamera3DTarget( camera3D camera ) + +Get camera target it looks-at + +- Failure return nil +- Success return Vector3 + +--- + +> up = RL_GetCamera3DUp( camera3D camera ) + +Get camera up vector ( Rotation over it's axis ) + +- Failure return nil +- Success return Vector3 + +--- + +> fovy = RL_GetCamera3DFovy( camera3D camera ) + +Get camera field-of-view apperture in Y ( degrees ) in perspective, used as near plane width in orthographic + +- Failure return nil +- Success return float + +--- + +> projection = RL_GetCamera3DProjection( camera3D camera ) + +Get camera projection mode + +- Failure return nil +- Success return int + +--- + +> success = RL_UpdateCamera3D( camera3D camera ) + +Update camera position for selected mode + +- Failure return false +- Success return true + +--- + +## Shapes - Drawing + +--- + +> success = RL_DrawPixel( Vector2 pos, Color color ) + +Draw a pixel + +- Failure return false +- Success return true + +--- + +> success = RL_DrawLine( Vector2 startPos, Vector2 endPos, float thickness, Color color ) + +Draw a line defining thickness + +- Failure return false +- Success return true + +--- + +> success = RL_DrawCircle( Vector2 center, float radius, Color color ) + +Draw a color-filled circle + +- Failure return false +- Success return true + +--- + +> success = RL_DrawCircleLines( Vector2 center, float radius, Color color ) + +Draw circle outline + +- Failure return false +- Success return true + +--- + +> success = RL_DrawRectangle( Rectangle rec, Color color ) + +Draw a color-filled rectangle + +- Failure return false +- Success return true + +--- + +> success = RL_DrawRectanglePro( Rectangle rec, Vector2 origin, float rotation, Color color ) + +Draw a color-filled rectangle with pro parameters + +- Failure return false +- Success return true + +--- + +> success = RL_DrawTriangle( Vector2 v1, Vector2 v2, Vector2 v3, Color color ) + +Draw a color-filled triangle ( Vertex in counter-clockwise order! ) + +- Failure return false +- Success return true + +--- + +> success = RL_DrawTriangleLines( Vector2 v1, Vector2 v2, Vector2 v3, Color color ) + +Draw triangle outline ( Vertex in counter-clockwise order! ) + +- Failure return false +- Success return true + +--- + +## Shapes - Collision + +--- + +> collision = RL_CheckCollisionRecs( Rectangle rec1, Rectangle rec2 ) + +Check collision between two rectangles + +- Failure return nil +- Success return bool + +--- + +> collision = RL_CheckCollisionCircles( Vector2 center1, float radius1, Vector2 center2, float radius2 ) + +Check collision between two circles + +- Failure return nil +- Success return bool + +--- + +> collision = RL_CheckCollisionCircleRec( Vector2 center, float radius, Rectangle rec ) + +Check collision between circle and rectangle + +- Failure return nil +- Success return bool + +--- + +> collision = RL_CheckCollisionPointRec( Vector2 point, Rectangle rec ) + +Check if point is inside rectangle + +- Failure return nil +- Success return bool + +--- + +> collision = RL_CheckCollisionPointCircle( Vector2 point, Vector2 center, float radius ) + +Check if point is inside circle + +- Failure return nil +- Success return bool + +--- + +> collision = RL_CheckCollisionPointTriangle( Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3 ) + +Check if point is inside a triangle + +- Failure return nil +- Success return bool + +--- + +> collision, position = RL_CheckCollisionLines( Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2 ) + +Check the collision between two lines defined by two points each, returns collision point by reference + +- Failure return nil +- Success return bool, Vector2 + +--- + +> collision = RL_CheckCollisionPointLine( Vector2 point, Vector2 p1, Vector2 p2, int threshold ) + +Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] + +- Failure return nil +- Success return bool + +--- + +> rectangle = RL_GetCollisionRec( Rectangle rec1, Rectangle rec2 ) + +Get collision rectangle for two rectangles collision + +- Failure return nil +- Success return Rectangle + +--- + +## Textures - Load + +--- + +> image = RL_LoadImage( string fileName ) + +Load image from file into CPU memory ( RAM ) + +- Failure return -1 +- Success return int + +--- + +> image = RL_GenImageColor( int width, int height, Color color ) + +Generate image: plain color + +- Failure return -1 +- Success return int + +--- + +> success = RL_UnloadImage( Image image ) + +Unload image from CPU memory ( RAM ) + +- Failure return false +- Success return true + +--- + +> texture = RL_LoadTexture( string fileName ) + +Load texture from file into GPU memory ( VRAM ) + +- Failure return -1 +- Success return int + +--- + +> texture = RL_LoadTextureFromImage( Image image ) + +Load texture from image data + +- Failure return -1 +- Success return int + +--- + +> success = RL_UnloadTexture( Texture2D texture ) + +Unload texture from GPU memory ( VRAM ) + +- Failure return false +- Success return true + +--- + +> renderTexture = RL_LoadRenderTexture( Vector2 size ) + +Load texture for rendering ( framebuffer ) + +- Failure return -1 +- Success return int + +--- + +> success = RL_UnloadRenderTexture( RenderTexture2D target ) + +Unload render texture from GPU memory ( VRAM ) + +- Failure return false +- Success return true + +--- + +## Textures - Image Drawing + +--- + +> success = RL_ImageClearBackground( Image dst, Color color ) + +Clear image background with given color + +- Failure return false +- Success return true + +--- + +> success = RL_ImageDrawPixel( Image dst, Vector2 position, Color color ) + +Draw pixel within an image + +- Failure return false +- Success return true + +--- + +> success = RL_ImageDrawLine( Image dst, Vector2 start, Vector2 end, Color color ) + +Draw line within an image + +- Failure return false +- Success return true + +--- + +> success = RL_ImageDrawCircle( Image dst, Vector2 center, int radius, Color color ) + +Draw circle within an image + +- Failure return false +- Success return true + +--- + +> success = RL_ImageDrawRectangle( Image dst, Rectangle rec, Color color ) + +Draw rectangle within an image + +- Failure return false +- Success return true + +--- + +> success = RL_DrawRectangleLines( Image dst, Rectangle rec, int thick, Color color ) + +Draw rectangle lines within an image + +- Failure return false +- Success return true + +--- + +> success = RL_ImageDraw( Image dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint ) + +Draw a source image within a destination image ( Tint applied to source ) + +- Failure return false +- Success return true + +--- + +> success = RL_ImageDrawTextEx( Image dst, Font font, string text, Vector2 position, float fontSize, float spacing, Color tint ) + +Draw text ( Custom sprite font ) within an image ( Destination ) + +- Failure return false +- Success return true + +--- + +## Textures - Texture Drawing + +--- + +> success = RL_DrawTexture( Texture2D texture, Vector2 position, Color tint ) + +Draw a Texture2D + +- Failure return false +- Success return true + +--- + +> success = RL_DrawTextureRec( Texture2D texture, Rectangle source, Vector2 position, Color tint ) + +Draw a part of a texture defined by a rectangle + +- Failure return false +- Success return true + +--- + +> success = RL_DrawTextureTiled( Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint ) + +Draw part of a texture ( defined by a rectangle ) with rotation and scale tiled into dest + +- Failure return false +- Success return true + +--- + +> success = RL_DrawTexturePro( Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint ) + +Draw a part of a texture defined by a rectangle with "pro" parameters + +- Failure return false +- Success return true + +--- + +> success = RL_DrawTextureNPatch( Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint ) + +Draws a texture ( or part of it ) that stretches or shrinks nicely + +- Failure return false +- Success return true + +--- + +> success = RL_DrawTexturePoly( Texture2D texture, Vector2 center, Vector2{} points, Vector2{} texcoords, int pointsCount, Color tint ) + +Draw a textured polygon ( Convex ) + +- Failure return false +- Success return true + +--- + +> success = RL_BeginTextureMode( RenderTexture2D target ) + +Begin drawing to render texture + +- Failure return false +- Success return true + +--- + +> RL_EndTextureMode() + +Ends drawing to render texture + +--- + +> success = RL_SetTextureSource( int textureSource ) + +Set what texture source to use ( TEXTURE_SOURCE_TEXTURE or TEXTURE_SOURCE_RENDER_TEXTURE ) + +- Failure return false +- Success return true + +--- + +> textureSource = RL_GetTextureSource() + +Get current texture source type ( TEXTURE_SOURCE_TEXTURE or TEXTURE_SOURCE_RENDER_TEXTURE ) + +- Success return int + +--- + +## Textures - Configure + +--- + +> success = RL_GenTextureMipmaps( Texture2D texture ) + +Generate GPU mipmaps for a texture + +- Failure return false +- Success return true + +--- + +> success = RL_SetTextureFilter( Texture2D texture, int filter ) + +Set texture scaling filter mode ( TEXTURE_FILTER_POINT, TEXTURE_FILTER_BILINEAR... ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetTextureWrap( Texture2D texture, int wrap ) + +Set texture wrapping mode ( TEXTURE_WRAP_REPEAT, TEXTURE_WRAP_CLAMP... ) + +- Failure return false +- Success return true + +--- + +> size = RL_GetTextureSize( Texture2D texture ) + +Get texture size + +- Failure return nil +- Success return Vector2 + +--- + +## Text - Loading + +--- + +> font = RL_LoadFont( string fileName ) + +Load font from file into GPU memory ( VRAM ) + +- Failure return -1 +- Success return int + +--- + +## Text - Draw + +--- + +> success = RL_DrawText( Font font, string text, Vector2 position, float fontSize, float spacing, Color tint ) + +Draw text using font and additional parameters + +- Failure return false +- Success return true + +--- + +## Models - Basic + +--- + +> success = RL_DrawLine3D( Vector3 startPos, Vector3 endPos, Color color ) + +Draw a line in 3D world space + +- Failure return false +- Success return true + +--- + +> success = RL_DrawPoint3D( Vector3 position, Color color ) + +Draw a point in 3D space, actually a small line + +- Failure return false +- Success return true + +--- + +> success = RL_DrawCircle3D( Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color ) + +Draw a circle in 3D world space + +- Failure return false +- Success return true + +--- + +> success = RL_DrawTriangle3D( Vector3 v1, Vector3 v2, Vector3 v3, Color color ) + +Draw a color-filled triangle ( Vertex in counter-clockwise order! ) + +- Failure return false +- Success return true + +--- + +> success = RL_DrawCube( Vector3 position, Vector3 size, Color color ) + +Draw cube + +- Failure return false +- Success return true + +--- + +> success = RL_DrawCubeWires( Vector3 position, Vector3 size, Color color ) + +Draw cube wires + +- Failure return false +- Success return true + +--- + +> success = RL_DrawCubeTexture( Texture2D texture, Vector3 position, Vector3 size, Color color ) + +Draw cube textured + +- Failure return false +- Success return true + +--- + +> success = RL_DrawSphere( Vector3 centerPos, float radius, Color color ) + +Draw sphere + +- Failure return false +- Success return true + +--- + +> success = RL_DrawSphereEx( Vector3 centerPos, float radius, int rings, int slices, Color color ) + +Draw sphere with extended parameters + +- Failure return false +- Success return true + +--- + +> success = RL_DrawSphereWires( Vector3 centerPos, float radius, int rings, int slices, Color color ) + +Draw sphere wires + +- Failure return false +- Success return true + +--- + +> success = RL_DrawCylinder( Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color ) + +Draw a cylinder/cone + +- Failure return false +- Success return true + +--- + +> success = RL_DrawCylinderEx( Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color ) + +Draw a cylinder with base at startPos and top at endPos + +- Failure return false +- Success return true + +--- + +> success = RL_DrawCylinderWires( Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color ) + +Draw a cylinder/cone wires + +- Failure return false +- Success return true + +--- + +> success = RL_DrawCylinderWiresEx( Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color ) + +Draw a cylinder wires with base at startPos and top at endPos + +- Failure return false +- Success return true + +--- + +> success = RL_DrawPlane( Vector3 centerPos, Vector2 size, Color color ) + +Draw a plane XZ + +- Failure return false +- Success return true + +--- + +> success = RL_DrawQuad3DTexture( texture, Vector3{} vertices, Vector2{} texCoords, Color color ) + +Draw 3D quad texture using vertices and texture coordinates. Texture coordinates opengl style 0.0 - 1.0. +Note! Could be replaced something like "DrawPlaneTextureRec" + +- Failure return false +- Success return true + +--- + +> success = RL_DrawRay( Ray ray, Color color ) + +Draw a ray line + +- Failure return false +- Success return true + +--- + +> success = RL_DrawGrid( int slices, float spacing ) + +Draw a grid ( Centered at ( 0, 0, 0 ) ) + +- Failure return false +- Success return true + +--- + +## Models - Mesh + +--- + +> mesh = RL_GenMeshPoly( int sides, float radius ) + +Generate polygonal mesh + +- Failure return -1 +- Success return int + +--- + +> mesh = RL_GenMeshPlane( float width, float length, int resX, int resZ ) + +Generate plane mesh ( With subdivisions ) + +- Failure return -1 +- Success return int + +--- + +> mesh = RL_GenMeshCube( Vector3 size ) + +Generate cuboid mesh + +- Failure return -1 +- Success return int + +--- + +> mesh = RL_GenMeshSphere( float radius, int rings, int slices ) + +Generate sphere mesh ( Standard sphere ) + +- Failure return -1 +- Success return int + +--- + +> mesh = RL_GenMeshCylinder( float radius, float height, int slices ) + +Generate cylinder mesh + +- Failure return -1 +- Success return int + +--- + +> mesh = RL_GenMeshCone( float radius, float height, int slices ) + +Generate cone/pyramid mesh + +- Failure return -1 +- Success return int + +--- + +> mesh = RL_GenMeshTorus( float radius, float size, int radSeg, int sides ) + +Generate torus mesh + +- Failure return -1 +- Success return int + +--- + +> mesh = RL_GenMeshKnot( float radius, float size, int radSeg, int sides ) + +Generate torus mesh + +- Failure return -1 +- Success return int + +--- + +> mesh = RL_GenMeshHeightmap( Image heightmap, Vector3 size ) + +Generate heightmap mesh from image data + +- Failure return -1 +- Success return int + +--- + +> mesh = RL_GenMeshCustom( Vector3{} vertices, Vector2{} texCoords, Vector3{} normals ) + +Generate custom mesh + +- Failure return -1 +- Success return int + +--- + +> success = RL_UnloadMesh( Mesh mesh ) + +Unload mesh data from CPU and GPU + +- Failure return false +- Success return true + +--- + +> success = RL_DrawMesh( Mesh mesh, Material material, Matrix transform ) + +Draw a 3d mesh with material and transform + +- Failure return false +- Success return true + +--- + +> success = RL_DrawMeshInstanced( Mesh mesh, Material material, Matrix{} transforms, int instances ) + +Draw multiple mesh instances with material and different transforms + +- Failure return false +- Success return true + +--- + +> success = RL_SetMeshColor( Mesh mesh, Color color ) + +Updades mesh color vertex attribute buffer +NOTE: Currently only works on custom mesh + +- Failure return false +- Success return true + +--- + +## Models - Material + +--- + +> material = RL_LoadMaterialDefault() + +Load default material + +- Success return int + +--- + +> material = RL_CreateMaterial( material{} ) + +Load material from table. See material table definition + +- Failure return false +- Success return int + +--- + +> success = RL_UnloadMaterial( Material material ) + +Unload material from GPU memory ( VRAM ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetMaterialTexture( Material material, int mapType, Texture2D texture ) + +Set texture for a material map type ( MATERIAL_MAP_ALBEDO, MATERIAL_MAP_METALNESS... ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetMaterialColor( Material material, int mapType, Color color ) + +Set color for a material map type + +- Failure return false +- Success return true + +--- + +> success = RL_SetMaterialValue( Material material, int mapType, float value ) + +Set value for a material map type + +- Failure return false +- Success return true + +--- + +## Models - Model + +--- + +> model = RL_LoadModel( string fileName ) + +Load model from files ( Meshes and materials ) + +- Failure return -1 +- Success return int + +--- + +> model = RL_LoadModelFromMesh( Mesh mesh ) + +Load model from generated mesh ( Default material ) + +- Failure return -1 +- Success return int + +--- + +> success = RL_UnloadModel( Model model ) + +Unload model ( Including meshes ) from memory ( RAM and/or VRAM ) + +- Failure return false +- Success return true + +--- + +> success = RL_DrawModel( Model model, Vector3 position, float scale, Color tint ) + +Draw a model ( With texture if set ) + +- Failure return false +- Success return true + +--- + +> success = RL_DrawModelEx( Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint ) + +Draw a model with extended parameters + +- Failure return false +- Success return true + +--- + +> success = RL_SetModelMaterial( Model model, Material modelMaterial, Material material ) + +Copies material to model material. ( Model material is the material id in models. Material can be deleted if not used elsewhere ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetModelMaterial( Model model, Material modelMaterial, Material material ) + +Set material for a mesh ( Mesh and material on this model ) + +- Failure return false +- Success return true + +--- + +> success = RL_DrawBillboard( Camera camera, Texture2D texture, Vector3 position, float size, Color tint ) + +Draw a billboard texture + +- Failure return false +- Success return true + +--- + +> success = RL_DrawBillboardRec( Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint ) + +Draw a billboard texture defined by source + +- Failure return false +- Success return true + +--- + +## Model - Animations + +--- + +> animations, animationCount = RL_LoadModelAnimations( string fileName ) + +Load model animations from file + +- Failure return -1 +- Success return int, int + +--- + +> success = RL_UpdateModelAnimation( Model model, ModelAnimations animations, int animation, int frame ) + +Update model animation pose + +- Failure return false +- Success return true + +--- + +> boneCount = RL_GetModelAnimationBoneCount( ModelAnimations animations, int animation ) + +Return modelAnimation bone count + +- Failure return false +- Success return int + +--- + +> frameCount = RL_GetModelAnimationFrameCount( ModelAnimations animations, int animation ) + +Return modelAnimation frame count + +- Failure return false +- Success return int + +--- + +## Model - Collision + +--- + +> collision = RL_CheckCollisionSpheres( Vector3 center1, float radius1, Vector3 center2, float radius2 ) + +Check collision between two spheres + +- Failure return nil +- Success return bool + +--- + +> collision = RL_CheckCollisionBoxes( BoundingBox box1, BoundingBox box2 ) + +Check collision between two bounding boxes + +- Failure return nil +- Success return bool + +--- + +> collision = RL_CheckCollisionBoxSphere( BoundingBox box, Vector3 center, float radius ) + +Check collision between box and sphere + +- Failure return nil +- Success return bool + +--- + +> rayCollision = RL_GetRayCollisionSphere( Ray ray, Vector3 center, float radius ) + +Get collision info between ray and sphere. ( RayCollision is Lua table of { hit, distance, point, normal } ) + +- Failure return nil +- Success return RayCollision + +--- + +> rayCollision = RL_GetRayCollisionBox( Ray ray, BoundingBox box ) + +Get collision info between ray and box + +- Failure return nil +- Success return RayCollision + +--- + +> rayCollision = RL_GetRayCollisionModel( Ray ray, Model model ) + +Get collision info between ray and model + +- Failure return nil +- Success return RayCollision + +--- + +> rayCollision = RL_GetRayCollisionMesh( Ray ray, Mesh mesh, Matrix transform ) + +Get collision info between ray and mesh + +- Failure return nil +- Success return RayCollision + +--- + +> rayCollision = RL_GetRayCollisionTriangle( Ray ray, Vector3 p1, Vector3 p2, Vector3 p3 ) + +Get collision info between ray and triangle + +- Failure return nil +- Success return RayCollision + +--- + +> rayCollision = RL_GetRayCollisionQuad( Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4 ) + +Get collision info between ray and quad + +- Failure return nil +- Success return RayCollision + +--- + +## Audio - Sounds + +--- + +> sound = RL_LoadSound( string fileName ) + +Load sound from file + +- Failure return -1 +- Success return int + +--- + +> success = RL_PlaySoundMulti( Sound sound ) + +Play a sound ( Using multichannel buffer pool ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetSoundVolume( Sound sound, float volume ) + +Set volume for a sound ( 1.0 is max level ) + +- Failure return false +- Success return true + +--- + +> success = RL_SetSoundPitch( Sound sound, float pitch ) + +Set pitch for a sound ( 1.0 is base level ) + +- Failure return false +- Success return true + +--- + +> success = RL_UnloadSound( Sound sound ) + +Unload sound + +- Failure return false +- Success return true + +--- + +## Audio - Music + +--- + +> success = RL_LoadMusicStream( string fileName ) + +Load music stream from file + +- Failure return false +- Success return true + +--- + +> PlayMusicStream() + +Start music playing + +--- + +> StopMusicStream() + +Stop music playing + +--- + +> PauseMusicStream() + +Pause music playing + +--- + +> ResumeMusicStream() + +Resume playing paused music + +--- + +> playing = PlayMusicStream() + +Check if music is playing + +- Success return bool + +--- + +> success = RL_SetMusicVolume( float volume ) + +Set volume for music ( 1.0 is max level ) + +- Failure return false +- Success return true + +--- + +## Math - Vector2 + +--- + +> result = RL_Vector2Add( Vector2 v1, Vector2 v2 ) + +Add two vectors (v1 + v2) + +- Failure return false +- Success return Vector2 + +--- + +> result = RL_Vector2Subtract( Vector2 v1, Vector2 v2 ) + +Subtract two vectors (v1 - v2) + +- Failure return false +- Success return Vector2 + +--- + +> result = RL_Vector2Multiply( Vector2 v1, Vector2 v2 ) + +Multiply vector by vector + +- Failure return false +- Success return Vector2 + +--- + +> result = RL_Vector2Length( vector2 vector ) + +Calculate vector length + +- Failure return false +- Success return float + +--- + +> result = RL_Vector2DotProduct( Vector2 v1, Vector2 v2 ) + +Calculate two vectors dot product + +- Failure return false +- Success return float + +--- + +> result = RL_Vector2Distance( Vector2 v1, Vector2 v2 ) + +Calculate distance between two vectors + +- Failure return false +- Success return float + +--- + +> result = RL_Vector2Angle( Vector2 v1, Vector2 v2 ) + +Calculate angle from two vectors + +- Failure return false +- Success return float + +--- + +> result = RL_Vector2Normalize( Vector2 v ) + +Normalize provided vector + +- Failure return false +- Success return Vector2 + +--- + +> result = RL_Vector2Lerp( Vector2 v1, Vector2 v2, float amount ) + +Calculate linear interpolation between two vectors + +- Failure return false +- Success return Vector2 + +--- + +> result = RL_Vector2Reflect( Vector2 v, Vector2 normal ) + +Calculate reflected vector to normal + +- Failure return false +- Success return Vector2 + +--- + +> result = RL_Vector2Rotate( Vector2 v, float angle ) + +Rotate vector by angle + +- Failure return false +- Success return Vector2 + +--- + +> result = RL_Vector2MoveTowards( Vector2 v, Vector2 target, float maxDistance ) + +Move Vector towards target + +- Failure return false +- Success return Vector2 + +--- + +## Math - Vector 3 + +--- + +> result = RL_Vector3CrossProduct( Vector3 v1, Vector3 v2 ) + +Add two vectors + +- Failure return false +- Success return Vector3 + +--- + +> result = RL_Vector3Subtract( Vector3 v1, Vector3 v2 ) + +Subtract two vectors + +- Failure return false +- Success return Vector3 + +--- + +> result = RL_Vector3Subtract( Vector3 v1, Vector3 v2 ) + +Multiply vector by vector + +- Failure return false +- Success return Vector3 + +--- + +> result = RL_Vector3CrossProduct( Vector3 v1, Vector3 v2 ) + +Calculate two vectors cross product + +- Failure return false +- Success return Vector3 + +--- + +> result = RL_Vector3Perpendicular( Vector3 v ) + +Calculate one vector perpendicular vector + +- Failure return false +- Success return Vector3 + +--- + +> result = RL_Vector3Length( Vector3 v ) + +Calculate one vector perpendicular vector + +- Failure return false +- Success return float + +--- + +> result = RL_Vector3LengthSqr( Vector3 v ) + +Calculate vector square length + +- Failure return false +- Success return float + +--- + +> result = RL_Vector3DotProduct( Vector3 v1, Vector3 v2 ) + +Calculate two vectors dot product + +- Failure return false +- Success return float + +--- + +> result = RL_Vector3Distance( Vector3 v1, Vector3 v2 ) + +Calculate distance between two vectors + +- Failure return false +- Success return float + +--- + +> result = RL_Vector3Normalize( Vector3 v ) + +Normalize provided vector + +- Failure return false +- Success return Vector3 + +--- + +> v1, v2 = RL_Vector3OrthoNormalize( Vector3 v1, Vector3 v2 ) + +Orthonormalize provided vectors. Makes vectors normalized and orthogonal to each other. +Gram-Schmidt function implementation + +- Failure return false +- Success return Vector3, Vector3 + +--- + +> result = RL_Vector3Transform( Vector3 v, Matrix mat ) + +Transforms a Vector3 by a given Matrix + +- Failure return false +- Success return Vector3 + +--- + +> result = RL_Vector3RotateByQuaternion( Vector3 v, Quaternion q ) + +Transform a vector by quaternion rotation + +- Failure return false +- Success return Vector3 + +--- + +> result = RL_Vector3Lerp( Vector3 v1, Vector3 v2, float amount ) + +Calculate linear interpolation between two vectors + +- Failure return false +- Success return Vector3 + +--- + +> result = RL_Vector3Reflect( Vector3 v, Vector3 normal ) + +Calculate reflected vector to normal + +- Failure return false +- Success return Vector3 + +--- + +## Math - Matrix + +--- + +> result = RL_MatrixDeterminant( Matrix mat ) + +Compute matrix determinant + +- Failure return false +- Success return float + +--- + +> result = RL_MatrixTranspose( Matrix mat ) + +Transposes provided matrix + +- Failure return false +- Success return Matrix + +--- + +> result = RL_MatrixInvert( Matrix mat ) + +Invert provided matrix + +- Failure return false +- Success return Matrix + +--- + +> result = RL_MatrixNormalize( Matrix mat ) + +Normalize provided matrix + +- Failure return false +- Success return Matrix + +--- + +> result = MatrixIdentity() + +Get identity matrix + +- Success return Matrix + +--- + +> result = RL_MatrixAdd( Matrix left, Matrix right ) + +Add two matrices + +- Failure return false +- Success return Matrix + +--- + +> result = RL_MatrixAdd( Matrix left, Matrix right ) + +Subtract two matrices (left - right) + +- Failure return false +- Success return Matrix + +--- + +> result = RL_MatrixMultiply( Matrix left, Matrix right ) + +Get two matrix multiplication + +- Failure return false +- Success return Matrix + +--- + +> result = RL_MatrixTranslate( Vector3 translate ) + +Get translation matrix + +- Failure return false +- Success return Matrix + +--- + +> result = RL_MatrixRotate( Vector3 axis, float angle ) + +Create rotation matrix from axis and angle. NOTE: Angle should be provided in radians + +- Failure return false +- Success return Matrix + +--- + +> result = RL_MatrixScale( Vector3 scale ) + +Get scaling matrix + +- Failure return false +- Success return Matrix + +--- + +> result = RL_MatrixFrustum( double left, double right, double bottom, double top, double near, double far ) + +Get perspective projection matrix + +- Failure return false +- Success return Matrix + +--- + +> result = RL_MatrixPerspective( double fovy, double aspect, double near, double far ) + +Get perspective projection matrix + +- Failure return false +- Success return Matrix + +--- + +> result = RL_MatrixOrtho( double left, double right, double bottom, double top, double near, double far ) + +Get orthographic projection matrix + +- Failure return false +- Success return Matrix + +--- + +> result = RL_MatrixLookAt( Vector3 eye, Vector3 target, Vector3 up ) + +Get camera look-at matrix ( View matrix ) + +- Failure return false +- Success return Matrix + +--- + +## Gui - Global + +--- + +> RL_GuiEnable() + +Enable gui controls ( Global state ) + +--- + +> RL_GuiDisable() + +Disable gui controls ( Global state ) + +--- + +> RL_GuiLock() + +Lock gui controls ( Global state ) + +--- + +> RL_GuiUnlock() + +Unlock gui controls ( Global state ) + +--- + +## Gui - Font + +--- + +> success = RL_GuiSetFont( Font font ) + +Set gui custom font ( Global state ) + +- Failure return false +- Success return true + +--- + +## Gui - Container + +--- + +> state = RL_GuiWindowBox( Rectangle bounds, string title ) + +Window Box control, shows a window that can be closed + +- Failure return nil +- Success return bool + +--- + +> success = RL_GuiPanel( Rectangle bounds ) + +Panel control, useful to group controls + +- Failure return false +- Success return true + +--- + +> view, scroll = RL_GuiScrollPanel( Rectangle bounds, Rectangle content, Vector2 scroll ) + +Scroll Panel control + +- Failure return false +- Success return Rectangle, Vector2 + +--- + +## Gui - Basic + +--- + +> success = RL_GuiLabel( Rectangle bounds, string text ) + +Label control, shows text + +- Failure return false +- Success return true + +--- + +> clicked = RL_GuiButton( Rectangle bounds, string text ) + +Button control, returns true when clicked + +- Failure return nil +- Success return boolean + +--- + +> active = RL_GuiToggle( Rectangle bounds, string text, bool active ) + +Toggle Button control, returns true when active + +- Failure return nil +- Success return boolean + +--- + +> active = RL_GuiCheckBox( Rectangle bounds, string text, bool checked ) + +Check Box control, returns true when active + +- Failure return nil +- Success return boolean + +--- + +> pressed, text = RL_GuiTextBox( Rectangle bounds, string text, int textSize, bool editMode ) + +Text Box control, updates input text + +- Failure return nil +- Success return boolean, string + +--- + +> pressed, text = RL_GuiTextBoxMulti( Rectangle bounds, string text, int textSize, bool editMode ) + +Text Box control with multiple lines + +- Failure return nil +- Success return boolean, string + +--- + +> pressed, value = RL_GuiSpinner( Rectangle bounds, string text, int value, int minValue, int maxValue, bool editMode ) + +Spinner control, returns selected value + +- Failure return nil +- Success return boolean, int + +--- + +> pressed, value = RL_GuiValueBox( Rectangle bounds, string text, int value, int minValue, int maxValue, bool editMode ) + +Value Box control, updates input text with numbers + +- Failure return nil +- Success return boolean, int + +--- + +> value = RL_GuiSlider( Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue ) + +Slider control, returns selected value + +- Failure return nil +- Success return float + +--- + +> value = RL_GuiSliderBar( Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue ) + +Slider Bar control, returns selected value + +- Failure return nil +- Success return float + +--- + +> value = RL_GuiProgressBar( Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue ) + +Progress Bar control, shows current progress value + +- Failure return nil +- Success return float + +--- + +> value = RL_GuiScrollBar( Rectangle bounds, int value, int minValue, int maxValue ) + +Scroll Bar control + +- Failure return nil +- Success return int + +--- + +> pressed, item = RL_GuiDropdownBox( Rectangle bounds, string text, int active, bool editMode ) + +Dropdown Box control, returns selected item + +- Failure return nil +- Success return bool, int + +--- diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..33fe7a5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required( VERSION 3.15 ) +project( ReiLua ) + +# find_package( raylib 3.7 REQUIRED ) # Requires at least version 3.7 + +set( CMAKE_C_STANDARD 11 ) # Requires C11 standard + +if( UNIX ) + set( CMAKE_C_COMPILER "gcc" ) +elseif( APPLE ) + set( CMAKE_C_COMPILER "clang" ) +endif() + +set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -ggdb -std=c11 -Wall -pedantic -fno-common" ) +# set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=c11 -Wall -pedantic -fno-common" ) + +option( STATIC ON ) +option( DRM OFF ) + +include_directories( include ) +file( GLOB SOURCES "src/*.c" ) +add_executable( ${PROJECT_NAME} ${SOURCES} ) + +if( STATIC ) + message( Static ) + target_link_libraries( ${PROJECT_NAME} ${CMAKE_SOURCE_DIR}/lib/libraylib.a ) + target_link_libraries( ${PROJECT_NAME} ${CMAKE_SOURCE_DIR}/lib/liblua.a ) +else() + find_package( raylib 4.0 REQUIRED ) # Requires at least version 4.0 + message( Shared ) + target_link_libraries( ${PROJECT_NAME} raylib ) + target_link_libraries( ${PROJECT_NAME} lua ) +endif() + +if( UNIX ) + if( DRM ) # Raspberry pi + # target_link_libraries( ${PROJECT_NAME} GLESv2 EGL drm gbm rt bcm_host m dl pthread ) + target_link_libraries( ${PROJECT_NAME} GLESv2 EGL drm gbm rt m dl pthread ) + else() + target_link_libraries( ${PROJECT_NAME} m dl pthread ) + endif() +endif() + +# Checks if OSX and links appropriate frameworks (Only required on MacOS) +if( APPLE ) + target_link_libraries( ${PROJECT_NAME} "-framework IOKit" ) + target_link_libraries( ${PROJECT_NAME} "-framework Cocoa" ) + target_link_libraries( ${PROJECT_NAME} "-framework OpenGL" ) +endif() diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 0000000..f954020 --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,5 @@ +CMakeCache.txt +cmake_install.cmake +Makefile +CMakeFiles +ReiLua \ No newline at end of file diff --git a/devnotes b/devnotes new file mode 100644 index 0000000..20aece2 --- /dev/null +++ b/devnotes @@ -0,0 +1,27 @@ +Backlog { + * Compilation + * Windows + * Web + * Could be better in general + * More and better examples + + * Raygui + * Advanced controls + * Core + * File drop + * Screen-space-related + * Cursor-related + * Text + * Fonts + * More draw functions + * Codepoints + * String management. At least TextSplit + * Audio + * Wave? + * Gestures + * Raymath + * Quaternions + * Physac + + * VR? +} diff --git a/doc_parser.lua b/doc_parser.lua new file mode 100644 index 0000000..dafedb3 --- /dev/null +++ b/doc_parser.lua @@ -0,0 +1,171 @@ +--Create api.md file from c sources. + +local function split( str, sep ) + if sep == nil then + sep = "%s" + end + + local t = {} + + for str in string.gmatch( str, "([^"..sep.."]+)" ) do + table.insert( t, str ) + end + + return t +end + +local apiFile = io.open( "API.md", "w" ) + +-- Header +apiFile:write( "# ReiLua API\n" ) + +-- Usage. + +apiFile:write( "\n## Usage\n" ) +apiFile:write( "\nApplication needs 'main.lua' file as entry point. ReiLua executable will first look it from same directory\ +or it's path can be given by argument. There are three global functions that the engine will call, 'init', 'process' and 'draw'.\n" ) + +apiFile:write( "\n---\n> function init()\n\ +This function will be called first when 'main.lua' is found\n\n---\n" ) +apiFile:write( "\n> function process( delta )\n\ +This function will be called every frame during execution. It will get time duration from last frame on argument 'delta'\n\n---\n" ) +apiFile:write( "\n> function draw()\n\ +This function will be called every frame after process and it should have all rendering related functions.\ +Note: Engine will call Raylib functions 'BeginDrawing()' before this function call and 'EndDrawing()' after it\n\n---\n" ) + +-- Globals. + +local srcFile = io.open( "src/lua_core.c", "r" ) +local writing = false + +repeat + line = srcFile:read( "*l" ) + local lineSplit = split( line, " " ) + + if line == "/*DOC_END*/" then + writing = false + break + end + + if writing then + if lineSplit[1] == "\t/*" then + apiFile:write( "\n## Globals - "..lineSplit[2].."\n" ) + else + -- Remove comma from the end. + apiFile:write( "\n"..lineSplit[2]:sub( 1, -2 ).."\n" ) + end + end + + if line == "/*DOC_START*/" then + writing = true + end +until line == nil + +srcFile:close() + +-- Types. + +apiFile:write( "\n## Types\n\ +Raylib structs in Lua\n\n---\n" ) + +apiFile:write( "\n> Vector2 = { 1.0, 1.0 }\n\ +Vector2 type\n\n---\n" ) +apiFile:write( "\n> Vector3 = { 1.0, 1.0, 1.0 }\n\ +Vector3 type\n\n---\n" ) +apiFile:write( "\n> Vector4 = { 1.0, 1.0, 1.0, 1.0 }\n\ +Vector4 type\n\n---\n" ) +apiFile:write( "\n> Quaternion = { 1.0, 1.0, 1.0, 1.0 }\n\ +Quaternion type\n\n---\n" ) +apiFile:write( "\n> Matrix = { { 1.0, 0.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0, 0.0 }, { 0.0, 0.0, 1.0, 0.0 }, { 0.0, 0.0, 0.0, 1.0 } }\n\ +OpenGL style 4x4. Identity matrix example\n\n---\n" ) +apiFile:write( "\n> Color = { 255, 255, 255, 255 }\n\ +{ r, g, b ,a }. Color type, RGBA (32bit)\n\n---\n" ) +apiFile:write( "\n> Rectangle = { 0.0, 0.0, 1.0, 1.0 }\n\ +{ x, y, w ,h }. Rectangle type\n\n---\n" ) +apiFile:write( "\n> Image = ImageId\n\ +int id. Image type (multiple pixel formats supported). NOTE: Data stored in CPU memory (RAM)\n\n---\n" ) +apiFile:write( "\n> Texture = TextureId\n\ +int id. Texture type (multiple internal formats supported). NOTE: Data stored in GPU memory (VRAM)\n\n---\n" ) +apiFile:write( "\n> RenderTexture = RenderTextureId\n\ +int id. RenderTexture type, for texture rendering\n\n---\n" ) +apiFile:write( "\n> Font = FontId\n\ +int id. Font type, includes texture and chars data\n\n---\n" ) +apiFile:write( "\n> Camera = CameraId\n\ +int id. Defines 3d camera position/orientation\n\n---\n" ) +apiFile:write( "\n> Mesh = MeshId\n\ +int id. Vertex data defining a mesh\n\n---\n" ) +apiFile:write( "\n> Material = MaterialId\n\ +int id. Material type\n\ +```\ +table = {\ + shader = Shader,\ + maps = {\ + {\ + MATERIAL_MAP_ALBEDO,\ + {\ + texture = Texture,\ + color = WHITE,\ + value = 1.0,\ + },\ + },\ + ...\ + },\ + params = { 1.0, 2.0, 3.0, 4.0 },\ +}\ +```\n\n---\n" ) +apiFile:write( "\n> Model = ModelId\n\ +int id. Basic 3d Model type\n\n---\n" ) +apiFile:write( "\n> Ray = { { 0.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 } }\n\ +{ position, direction }. Ray type (useful for raycast)\n\n---\n" ) +apiFile:write( "\n> RayCollision = { hit = true, distance = 1.0, point = { 0.0, 0.0 }, normal = { 0.0, 0.0, 1.0 } }\n\ +Raycast hit information. NOTE: Data in named keys\n\n---\n" ) +apiFile:write( "\n> BoundingBox = { { 0.0, 0.0, 0.0 }, { 1.0, 1.0, 1.0 } }\n\ +{ min, max }. Bounding box type for 3d mesh\n\n---\n" ) +apiFile:write( "\n> Sound = SoundId\n\ +int id. Basic Sound source and buffer\n\n---\n" ) +apiFile:write( "\n> NPatchInfo = { { 0, 0, 24, 24 }, 0, 0, 0, 0, NPATCH_NINE_PATCH }\n\ +{ Rectangle source, int left, int top, int right, int bottom, int layout }.\ +{ Texture source rectangle, Left border offset, Top border offset, Right border offset, Bottom border offset, Layout of the n-patch: 3x3, 1x3 or 3x1 }\n\n---\n" ) +apiFile:write( "\n> ModelAnimations = ModelAnimationsId\n\ +int id. ModelAnimations\n\n---\n" ) + +-- Functions. + +local sourceFiles = { + "src/core.c", + "src/shapes.c", + "src/textures.c", + "src/text.c", + "src/models.c", + "src/audio.c", + "src/rmath.c", + "src/rgui.c", +} + +for _, src in ipairs( sourceFiles ) do + srcFile = io.open( src, "r" ) + local line = "" + local p = false + + repeat + line = srcFile:read( "*l" ) + + if line == "*/" then + p = false + apiFile:write( "\n---\n" ) + end + + if p then + apiFile:write( line.."\n" ) + end + + if line == "/*" then + p = true + apiFile:write( "\n" ) + end + until line == nil + + srcFile:close() +end + +apiFile:close() diff --git a/examples/dungeon_crawler/main.lua b/examples/dungeon_crawler/main.lua new file mode 100644 index 0000000..979b553 --- /dev/null +++ b/examples/dungeon_crawler/main.lua @@ -0,0 +1,147 @@ +local pos = { 2, 0.5, 6 } +local speed = 5.0 +local camera = -1 +local texture = -1 +local mesh = -1 +local textureSize = { 128, 128 } +local res = { 384, 216 } +local winSize = RL_GetWindowSize() +local winScale = 5 +local framebuffer = -1 + +local TILE_SIZE = 32 +local COLOR_WHITE = { 255, 255, 255 } + +local FLOOR = 1 +local CEILING = 2 +local WALL_N = 3 +local WALL_S = 4 +local WALL_W = 5 +local WALL_E = 6 + +local sprites = { + { pos = { 0.5, 0.5 }, tile = { 0, 1 }, dis = 0, size = 0.7 }, + { pos = { 3.5, 0.5 }, tile = { 0, 1 }, dis = 0, size = 0.7 }, +} + +local function getTexCoords( x, y ) + return { + { x * TILE_SIZE / textureSize[1], y * TILE_SIZE / textureSize[2] }, + { x * TILE_SIZE / textureSize[1], ( y * TILE_SIZE + TILE_SIZE ) / textureSize[2] }, + { ( x * TILE_SIZE + TILE_SIZE ) / textureSize[1], ( y * TILE_SIZE + TILE_SIZE ) / textureSize[2] }, + { ( x * TILE_SIZE + TILE_SIZE ) / textureSize[1], y * TILE_SIZE / textureSize[2] }, + } +end + +local function getTileVer( x, y, type ) + local types = { + { { 0, 0, 0 }, { 0, 0, 1 }, { 1, 0, 1 }, { 1, 0, 0 } }, -- Floor. + { { 1, 1, 0 }, { 1, 1, 1 }, { 0, 1, 1 }, { 0, 1, 0 } }, -- Ceiling. + { { 0, 1, 0 }, { 0, 0, 0 }, { 1, 0, 0 }, { 1, 1, 0 } }, -- Wall North. + { { 1, 1, 1 }, { 1, 0, 1 }, { 0, 0, 1 }, { 0, 1, 1 } }, -- Wall South. + { { 0, 1, 1 }, { 0, 0, 1 }, { 0, 0, 0 }, { 0, 1, 0 } }, -- Wall West. + { { 1, 1, 0 }, { 1, 0, 0 }, { 1, 0, 1 }, { 1, 1, 1 } }, -- Wall East. + } + local verts = types[ type ] + + for i = 1, 4 do + verts[i][1] = verts[i][1] + x + verts[i][3] = verts[i][3] + y + end + + return verts +end + +function drawSprites() + for _, sprite in ipairs( sprites ) do + sprite.dis = RL_Vector2Distance( { pos[1], pos[3] }, { sprite.pos[1], sprite.pos[2] } ) + end + + table.sort( sprites, function( a, b ) return a.dis > b.dis end ) + + for _, sprite in ipairs( sprites ) do + RL_DrawBillboardRec( camera, texture, { sprite.tile[1] * TILE_SIZE, sprite.tile[2] * TILE_SIZE, TILE_SIZE, TILE_SIZE }, + { sprite.pos[1], 0.5 * sprite.size, sprite.pos[2] }, { sprite.size, sprite.size }, COLOR_WHITE ) + end +end + +function init() + local monitor = 0 + local mPos = RL_GetMonitorPosition( monitor ) + local mSize = RL_GetMonitorSize( monitor ) + -- RL_SetWindowSize( { 1920, 1080 } ) + winSize = { res[1] * winScale, res[2] * winScale } + -- winSize = { 1920, 1080 } + RL_SetWindowSize( winSize ) + RL_SetExitKey( KEY_ESCAPE ) + -- framebuffer = RL_LoadRenderTexture( res ) + RL_SetWindowState( FLAG_WINDOW_RESIZABLE ) + RL_SetWindowPosition( { mPos[1] + mSize[1] / 2 - winSize[1] / 2, mPos[2] + mSize[2] / 2 - winSize[2] / 2 } ) + + texture = RL_LoadTexture( RL_GetBasePath().."../resources/images/tiles.png" ) + camera = RL_CreateCamera3D() + mesh = RL_GenMeshCube( { 1, 2, 1 } ) + RL_SetCamera3DPosition( camera, pos ) + RL_SetCamera3DTarget( camera, { 0, 0, 0 } ) + RL_SetCamera3DUp( camera, { 0, 1, 0 } ) + RL_SetCamera3DMode( camera, CAMERA_FIRST_PERSON ) + -- RL_SetCamera3DMode( camera, CAMERA_ORBITAL ) + + -- for x = 0, 3 do + -- for y = 0, 9 do + -- table.insert( sprites, { pos = { x + 0.5, y + 0.5 + 1 }, tile = { 1, 1 }, dis = 0, size = 0.8 } ) + -- end + -- end + table.insert( sprites, { pos = { 2.5, 2.5 }, tile = { 1, 1 }, dis = 0, size = 0.8 } ) + + -- for x = 0, 1 do + -- for y = 0, 1 do + -- table.insert( sprites, { pos = { 1.25 + x * 0.5, 2.25 + y * 0.5 }, tile = { 3, 0 }, dis = 0, size = 0.6 } ) + -- end + -- end + table.insert( sprites, { pos = { 1.5, 3.5 }, tile = { 3, 1 }, dis = 0, size = 0.5 } ) + table.insert( sprites, { pos = { 0.5, 3.5 }, tile = { 3, 0 }, dis = 0, size = 0.7 } ) +end + +function process( delta ) + -- RL_SetCamera3DPosition( camera, pos ) +end + +function draw() + RL_UpdateCamera3D( camera ) + pos = RL_GetCamera3DPosition( camera ) + + -- RL_BeginTextureMode( framebuffer ) + RL_ClearBackground( { 100, 150, 150 } ) + + RL_BeginMode3D( camera ) + + -- Floor and ceiling. + for x = 0, 3 do + for y = 0, 10 do + RL_DrawQuad3DTexture( texture, getTileVer( x, y, FLOOR ), getTexCoords( 1, 0 ), COLOR_WHITE ) + RL_DrawQuad3DTexture( texture, getTileVer( x, y, CEILING ), getTexCoords( 2, 0 ), COLOR_WHITE ) + end + end + -- Walls. + RL_DrawQuad3DTexture( texture, getTileVer( 0, 0, WALL_N ), getTexCoords( 0, 0 ), COLOR_WHITE ) + RL_DrawQuad3DTexture( texture, getTileVer( 1, 0, WALL_N ), getTexCoords( 0, 2 ), COLOR_WHITE ) + RL_DrawQuad3DTexture( texture, getTileVer( 2, 0, WALL_N ), getTexCoords( 2, 2 ), COLOR_WHITE ) + RL_DrawQuad3DTexture( texture, getTileVer( 3, 0, WALL_N ), getTexCoords( 0, 0 ), COLOR_WHITE ) + + for x = 0, 3 do + RL_DrawQuad3DTexture( texture, getTileVer( x, 10, WALL_S ), getTexCoords( 0, 0 ), COLOR_WHITE ) + end + for y = 0, 10 do + RL_DrawQuad3DTexture( texture, getTileVer( 0, y, WALL_W ), getTexCoords( 0, 0 ), COLOR_WHITE ) + RL_DrawQuad3DTexture( texture, getTileVer( 3, y, WALL_E ), getTexCoords( 0, 0 ), COLOR_WHITE ) + end + + drawSprites() + RL_EndMode3D() + -- RL_EndTextureMode() + + -- RL_SetTextureSource( TEXTURE_SOURCE_RENDER_TEXTURE ) + -- RL_DrawTexturePro( framebuffer, { 0, 0, res[1], -res[2] }, { 0, 0, winSize[1], winSize[2] }, { 0, 0 }, 0.0, COLOR_WHITE ) + -- RL_SetTextureSource( TEXTURE_SOURCE_TEXTURE ) +end diff --git a/examples/gui/main.lua b/examples/gui/main.lua new file mode 100644 index 0000000..079bd95 --- /dev/null +++ b/examples/gui/main.lua @@ -0,0 +1,70 @@ +local windowOpen = true +local toggled = false +local checkbox = false +local textBoxText = "Edit" +local textBoxActive = false +local spinnerValue = 3 +local spinnerActive = false +local spinnerValueRange = { 0, 10 } +local sliderValue = 5.0 +local sliderValueRange = { 0.0, 10.0 } +local scrollbarValue = 0.0 +local dropdownValue = 0 +local dropdownActive = false + +function init() + local monitor = 0 + local mPos = RL_GetMonitorPosition( monitor ) + local mSize = RL_GetMonitorSize( monitor ) + local winSize = RL_GetWindowSize() + + RL_GuiSetFont( 0 ) + RL_SetWindowState( FLAG_WINDOW_RESIZABLE ) + RL_SetWindowPosition( { mPos[1] + mSize[1] / 2 - winSize[1] / 2, mPos[2] + mSize[2] / 2 - winSize[2] / 2 } ) +end + +function process( delta ) +end + +function draw() + RL_ClearBackground( { 50, 20, 75 } ) + + if RL_GuiButton( { 112, 16, 96, 32 }, "Button" ) then + print( "Button pressed!" ) + end + + if windowOpen and RL_GuiWindowBox( { 300, 16, 200, 320 }, "Window" ) then + windowOpen = false + end + + RL_GuiPanel( { 60, 260, 100, 100 } ) + + toggled = RL_GuiToggle( { 200, 260, 64, 32 }, "Toggle", toggled ) + checkbox = RL_GuiCheckBox( { 200, 300, 16, 16 }, "CheckBox", checkbox ) + + local textBoxToggle = false + textBoxToggle, textBoxText = RL_GuiTextBox( { 32, 400, 120, 32 }, textBoxText, 32, textBoxActive ) + -- textBoxToggle, textBoxText = RL_GuiTextBoxMulti( { 32, 400, 120, 64 }, textBoxText, 120, textBoxActive ) + + if textBoxToggle then + textBoxActive = not textBoxActive + end + + local spinnerToggle = false + spinnerToggle, spinnerValue = RL_GuiSpinner( { 64, 450, 96, 32 }, "Value", spinnerValue, spinnerValueRange[1], spinnerValueRange[2], spinnerActive ) + -- spinnerToggle, spinnerValue = RL_GuiValueBox( { 64, 450, 96, 32 }, "Value", spinnerValue, spinnerValueRange[1], spinnerValueRange[2], spinnerActive ) + + if spinnerToggle then + spinnerActive = not spinnerActive + end + + sliderValue = RL_GuiSliderBar( { 64, 510, 96, 32 }, "min", "max", sliderValue, sliderValueRange[1], sliderValueRange[2] ) + scrollbarValue = RL_GuiScrollBar( { 64, 550, 130, 32 }, scrollbarValue, 0, 10 ) + + local dropdownToggle = false + dropdownToggle, dropdownValue = RL_GuiDropdownBox( { 2, 2, 96, 16 }, "Cat\nDog\nMonkey", dropdownValue, dropdownActive ) + + if dropdownToggle then + dropdownActive = not dropdownActive + end +end diff --git a/examples/image_draw/main.lua b/examples/image_draw/main.lua new file mode 100644 index 0000000..8855fce --- /dev/null +++ b/examples/image_draw/main.lua @@ -0,0 +1,30 @@ +local monitor = 0 +local texture = -1 +local image = -1 +local catImage = -1 + +function init() + local mPos = RL_GetMonitorPosition( monitor ) + local mSize = RL_GetMonitorSize( monitor ) + local winSize = RL_GetWindowSize() + + RL_SetWindowState( FLAG_WINDOW_RESIZABLE ) + RL_SetWindowPosition( { mPos[1] + mSize[1] / 2 - winSize[1] / 2, mPos[2] + mSize[2] / 2 - winSize[2] / 2 } ) + image = RL_GenImageColor( winSize[1], winSize[2], WHITE ) + catImage = RL_LoadImage( RL_GetBasePath().."../resources/images/cat.png" ) + RL_ImageClearBackground( image, { 150, 60, 100 } ) + RL_ImageDrawPixel( image, { 32, 32 }, WHITE ) + RL_ImageDrawLine( image, { 32, 45 }, { 100, 60 }, GREEN ) + RL_ImageDrawCircle( image, { 64, 32 }, 16, BLUE ) + RL_ImageDrawRectangle( image, { 120, 64, 32, 64 }, BLUE ) + RL_ImageDrawRectangleLines( image, { 160, 64, 32, 64 }, 2.0, BLUE ) + RL_ImageDraw( image, catImage, { 143, 25, 230, 250 }, { 200, 200, 230, 250 }, WHITE ) + RL_ImageDrawTextEx( image, 0, "Hello", { 300, 32 }, 48.0, 1.0, WHITE ) + + texture = RL_LoadTextureFromImage( image ) +end + +function draw() + RL_ClearBackground( { 100, 150, 100 } ) + RL_DrawTexture( texture, { 0, 0 }, WHITE ) +end diff --git a/examples/pixelated/main.lua b/examples/pixelated/main.lua new file mode 100644 index 0000000..c0de666 --- /dev/null +++ b/examples/pixelated/main.lua @@ -0,0 +1,55 @@ +local tex = -1 +local pos = { 32, 32 } +local speed = 60.0 +local sound = -1 +local monitor = 0 +local mPos = RL_GetMonitorPosition( monitor ) +local mSize = RL_GetMonitorSize( monitor ) +local framebuffer = -1 +local res = { 320, 180 } +local scale = 5 +local winSize = { res[1] * scale, res[2] * scale } + +function init() + RL_SetWindowState( FLAG_WINDOW_RESIZABLE ) + RL_SetWindowPosition( { mPos[1] + mSize[1] / 2 - winSize[1] / 2, mPos[2] + mSize[2] / 2 - winSize[2] / 2 } ) + RL_SetWindowSize( winSize ) + tex = RL_LoadTexture( RL_GetBasePath().."../resources/images/cat.png" ) + -- Create framebuffer. + framebuffer = RL_LoadRenderTexture( res ) +end + +function process( delta ) + if RL_IsKeyDown( KEY_RIGHT ) then + pos[1] = pos[1] + delta * speed + elseif RL_IsKeyDown( KEY_LEFT ) then + pos[1] = pos[1] - delta * speed + end + + if RL_IsKeyDown( KEY_UP ) then + pos[2] = pos[2] - delta * speed + elseif RL_IsKeyDown( KEY_DOWN ) then + pos[2] = pos[2] + delta * speed + end + + if RL_IsWindowResized() then + winSize = RL_GetWindowSize() + end +end + +function draw() + RL_ClearBackground( { 0, 0, 0 } ) + + RL_BeginTextureMode( framebuffer ) + RL_ClearBackground( { 100, 150, 100 } ) + RL_DrawPixel( { 100, 100 }, { 255, 50, 100 } ) + RL_DrawLine( { 120, 100 }, { 140, 150 }, 2.4, { 255, 150, 255 } ) + RL_DrawRectangle( { 200, 120, 40, 50 }, { 100, 170, 255 } ) + RL_DrawTexturePro( tex, { 166, 138, 128, 128 }, { pos[1], pos[2], 128, 128 }, { 16, 16 }, 0.0, WHITE ) + RL_DrawText( 0, "Cat MIAU!!", { 16, 32 }, 10, 1, { 255, 180, 155 } ) + RL_EndTextureMode() + + RL_SetTextureSource( TEXTURE_SOURCE_RENDER_TEXTURE ) + RL_DrawTexturePro( framebuffer, { 0, 0, res[1], -res[2] }, { 0, 0, winSize[1], winSize[2] }, { 0, 0 }, 0.0, { 255, 255, 255 } ) + RL_SetTextureSource( TEXTURE_SOURCE_TEXTURE ) +end diff --git a/examples/ray/main.lua b/examples/ray/main.lua new file mode 100644 index 0000000..f0f5c2f --- /dev/null +++ b/examples/ray/main.lua @@ -0,0 +1,47 @@ +local camera = -1 +local sphereMesh = -1 +local ray = { { 0.5, 0, 4 }, { 0.1, 0, -1 } } + +local function setupWindow() + local monitor = 0 + local mPos = RL_GetMonitorPosition( monitor ) + local mSize = RL_GetMonitorSize( monitor ) + local winSize = RL_GetWindowSize() + + RL_SetWindowState( FLAG_WINDOW_RESIZABLE ) + RL_SetWindowPosition( { mPos[1] + mSize[1] / 2 - winSize[1] / 2, mPos[2] + mSize[2] / 2 - winSize[2] / 2 } ) +end + +function init() + setupWindow() + + camera = RL_CreateCamera3D() + RL_SetCamera3DPosition( camera, { 0, 2, 4 } ) + RL_SetCamera3DTarget( camera, { 0, 0, 0 } ) + RL_SetCamera3DUp( camera, { 0, 2, 0 } ) + RL_SetCamera3DMode( camera, CAMERA_FREE ) + + sphereMesh = RL_GenMeshSphere( 1.0, 8, 10 ) + + -- local rayCol = RL_GetRayCollisionSphere( { { 0.5, 0, 4 }, { 0, 0, -1 } }, { 0, 0, 0 }, 1.0 ) + local rayCol = RL_GetRayCollisionMesh( ray, sphereMesh, RL_MatrixIdentity() ) + + if rayCol ~= nil and rayCol.hit then + print( "hit", rayCol.hit ) + print( "distance", rayCol.distance ) + print( "point", rayCol.point[1], rayCol.point[2], rayCol.point[3] ) + print( "normal", rayCol.normal[1], rayCol.normal[2], rayCol.normal[3] ) + end +end + +function draw() + RL_ClearBackground( { 100, 150, 100 } ) + RL_UpdateCamera3D( camera ) + + RL_BeginMode3D( camera ) + RL_DrawGrid( 8, 1 ) + RL_DrawRay( ray, { 255, 100, 100 } ) + + RL_DrawMesh( sphereMesh, 0, RL_MatrixIdentity() ) + RL_EndMode3D() +end diff --git a/examples/resources/images/LICENCE b/examples/resources/images/LICENCE new file mode 100644 index 0000000..fca0d39 --- /dev/null +++ b/examples/resources/images/LICENCE @@ -0,0 +1,5 @@ +Resource Author Licence Source +tiles.png Chris Hamons (maintainer) CC0 https://opengameart.org/content/dungeon-crawl-32x32-tiles +apple.png Jussi Viitala CC0 +grass.png Jussi Viitala CC0 +snake.png Jussi Viitala CC0 \ No newline at end of file diff --git a/examples/resources/images/apple.png b/examples/resources/images/apple.png new file mode 100644 index 0000000000000000000000000000000000000000..9526aa638ea45ca48a08f8be7927120e092d739e GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqY)RhkEbHUgGKN%KnI3Ow^L~<4?n@Kq1Kz*N775{M_8syb=cIqSVBa)D(sC z%#sWRcTeAd@J2pyprUY37sn8Z%gKMvA2{$}Q`iH0W|23X{|>xZc<^KW@m~2J9w6`u zaGYVo95Sh~vGJijGY~wk*I<^;Xqnl-KVkg>K~F|yy^~dJY;JBU;{Wnpfv W7Uy0gBjf|Lg2B_(&t;ucLK6VcF-9o> literal 0 HcmV?d00001 diff --git a/examples/resources/images/cat.png b/examples/resources/images/cat.png new file mode 100644 index 0000000000000000000000000000000000000000..db56b9eadbcbe2a750734fd1bc1b5f9b416d2e95 GIT binary patch literal 388467 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≺Lq$x%fi1H<%?q?+t|Pf%U(^I<&D#ScJ&0W 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%0D5X0!j=z)-s`cNvIR1NrSZN|}bQ##|GAxeN< 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
AsN_KAz_ybUu_u!7m>DjEs|@#l_vC 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

gR@NcsWAim5;gitwqDXVDb5u~qt- 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 Z$HH@ws2L4o-(dlMCb!KE>*20R{|6pN_#FTM literal 0 HcmV?d00001 diff --git a/examples/resources/images/grass.png b/examples/resources/images/grass.png new file mode 100644 index 0000000000000000000000000000000000000000..4ba72639b991401ec2cab1cdc4b8bbb48e3a303f GIT binary patch literal 1065 zcmV+^1lIeBP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=HGlH@21MgLjFECC6G#Bwl(h}pp`e=ZoiGPA26 z73u!ym@!d?6tcMJAiG@q=kM}Gd{Bf0eCm-kixXw9xw!3SB zVG8KO@=wd&U}yOAvey--z%CDga$6JI^F&#J*wzB4qP0+JTg}VHEdDyD0t;L|6B#}nkBYV}9F*f#5k+*#a?UnO*PPP^3<+nRs8Jf5S3LbYG z3IpT^3k7$c_r$}JG4|8KVJQVIc3v^gXI_7N{Ybexr7wEVsLv67$NoUt&4iv4;e}AT z`cLg7A9nYMbaH!GIi+jmCwt**iHn>&@%6r2@9o%Ki3wXyYg6Y|<_Lb<$ku!+p(*~v z+er3k8O=}<73XNjA=}U@Wny6L6lteIo!aaxRY;87SkW_A4pbeC1{j&^#2rVY&I-&s z;B%-r4gpraw9s5O&*dr^7T(DmP7fj2I4f^8_bX1lkAfodcI67XcvKox401%y6Wl@pgrMMU~N33G+kNvFyloC5E7CL3N|Q2z=~0Del!}+ zP>~=}M3ShOWGN}6QDG@*8gJC1L8FQ$RW;38oCS*}mQ2koTgjXQ-nf!ckJ)n0rC`y5 z$OTUqIFz#S7Ph#_mNwmN%Uh}JYN)YFO;xMaT+7aT*ipxO+I6=*@1;@0QEIVCOHG@# z+)Bq$>!HUkJ$3EYv+L!ewy=KFzCeu@HJ+q~qh8ctRXsz{c%8^#24Wluz;zIy;Ft$9 zZxkaBa)X)AjH(cZNEvW;8pJ>_He%^?vHKt=E~I<7Ie_3h$hm>;7s!3f?K5h7nIF0^ z35_xwN%aBM2lQ6hO84+>ehl%&{H%B1oA_$~hXx9AWU8f*ZJLnOI(yzqY&hQR>?TR> zZ7J&k$2Up(KhSM~evZ$TnQB# literal 0 HcmV?d00001 diff --git a/examples/resources/images/snake.png b/examples/resources/images/snake.png new file mode 100644 index 0000000000000000000000000000000000000000..e1b00912c59bd91aca9553ae016144f7a5c24558 GIT binary patch literal 1933 zcmV;82Xgp{P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1Y?lI120{Ld+J1WQ5)j>DI#a)TT{E$lb*c6PJ1 zN!FgRc^HuBZnd5EUw?Y~0|yz`l+|i!rCd02%Z)o4^}3IKu5{&dJqJIJ@cWzo^f8Ii zCAiD|vzDiSqaU;BS)tBnosNEeTd3{l!T9z;e-`5U1bF&ifrRb=*Rycy&qBga3A0U@;kkTGIHWA zBp0qVG8CjQRXVPGu7#tgp?yigHOm79*y3~<+UJwboX9<0f!i zZK%gqTWh@|XvfMOvpWX#K6uE;Lq{1l>S&`+PE%%{I?J?KXPbTTl7%{6W!b8$t-fhP zN;_}eW!tX1?e378i)U9aZeG15?;or^w0?K~3)bjijSo{X&^}m0ui0w}3Omu@42*Fm z5N^Q$Nn#Gpu1Yy`Fc+L%jZqcKNK%8GMZp*t%$r!9KDhg2P7_S?xA2xg1pf}^Qc(8` z%ze+>k62s%yy)aG6lEBoUV`d#8J))vT(;RBw6k^H)8}+i^maMVS5MxkI=8%Q-!m@a zeOe|%!;Wl#;lbYBMP0jXMM;4%U{TAD#=T6m0Jb>cXRhlR)QVFZVNmc zNOZg514dBt@==^4Wz-si*b__J38u)@t~*DRY1}Jxm`+EFMCpn|HJ13C{g&xHWzK%D zQm>^0| zm;%%nO?(%XF0Qv9`Wyv5MS{u$&Qd3;I|U-vI=0dr#_4%nV5H>;$d7ppX@$yK@LDat zuCNv03mv1{JF1_++U|M6{P5Wf|9aU@?IW|7mGR;kZ!|Kt2e@Fs-~uqfGO!1vfrVBv ze7mjDThM>Ae|sHNQjvtsh7||$-!It9ltN>n0Ci{`8BXE}purz#^%2H{=g{rs8+uiN zgelyx?Gm2l`IyAx4lr+lHVv-d(%-RsU};J2CkOxl010qNS#tmYE+YT{E+YYWr9XB6 z000McNliru;tUWG1}zNGm;(R+02y>eSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2! zfese{00AvYL_t(I%e9lgO2beT#($?YL#0T;AX=MUr8;yFiU>iZ88Tc4z2)YTSSt8U=W@>d&cE--@xhJ)SS)h@Jl6qW zGF=H4MqwL(+lM!0#@c`F_i)exAbU9!-alY(7l8BoJL=jQ&oTglUPF_pc%F-yJW5gp z(D$2#*m#z$?J_QnM(0t{fzThz{>3SmM<>WAY%7~Y6FfbC63=r9x{mT(2eVk4+(eoG z?E1AZMLm&q0cD7il*KZq?>7m0jXx#cgxaP6&vmvY-3IllPPCYQbf8QzRdxPbp;arW zX#$_Nf1KeLPP3qK TDO8Xj00000NkvXXu0mjfS89EB literal 0 HcmV?d00001 diff --git a/examples/resources/images/tiles.png b/examples/resources/images/tiles.png new file mode 100644 index 0000000000000000000000000000000000000000..4e3e9cc0ffe208d4ec74c369070b9cb3ea96908a GIT binary patch literal 32917 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 KiP{fF!T$@~mqri( literal 0 HcmV?d00001 diff --git a/examples/resources/shaders/glsl100/wave.fs b/examples/resources/shaders/glsl100/wave.fs new file mode 100644 index 0000000..50c4e02 --- /dev/null +++ b/examples/resources/shaders/glsl100/wave.fs @@ -0,0 +1,36 @@ +#version 100 + +precision mediump float; + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; +varying vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +uniform float secondes; + +uniform vec2 size; + +uniform float freqX; +uniform float freqY; +uniform float ampX; +uniform float ampY; +uniform float speedX; +uniform float speedY; + +void main() { + float pixelWidth = 1.0 / size.x; + float pixelHeight = 1.0 / size.y; + float aspect = pixelHeight / pixelWidth; + float boxLeft = 0.0; + float boxTop = 0.0; + + vec2 p = fragTexCoord; + p.x += cos((fragTexCoord.y - boxTop) * freqX / ( pixelWidth * 750.0) + (secondes * speedX)) * ampX * pixelWidth; + p.y += sin((fragTexCoord.x - boxLeft) * freqY * aspect / ( pixelHeight * 750.0) + (secondes * speedY)) * ampY * pixelHeight; + + gl_FragColor = texture2D(texture0, p)*colDiffuse*fragColor; +} diff --git a/examples/resources/shaders/glsl330/wave.fs b/examples/resources/shaders/glsl330/wave.fs new file mode 100644 index 0000000..43efee2 --- /dev/null +++ b/examples/resources/shaders/glsl330/wave.fs @@ -0,0 +1,37 @@ +#version 330 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Output fragment color +out vec4 finalColor; + +uniform float secondes; + +uniform vec2 size; + +uniform float freqX; +uniform float freqY; +uniform float ampX; +uniform float ampY; +uniform float speedX; +uniform float speedY; + +void main() { + float pixelWidth = 1.0 / size.x; + float pixelHeight = 1.0 / size.y; + float aspect = pixelHeight / pixelWidth; + float boxLeft = 0.0; + float boxTop = 0.0; + + vec2 p = fragTexCoord; + p.x += cos((fragTexCoord.y - boxTop) * freqX / ( pixelWidth * 750.0) + (secondes * speedX)) * ampX * pixelWidth; + p.y += sin((fragTexCoord.x - boxLeft) * freqY * aspect / ( pixelHeight * 750.0) + (secondes * speedY)) * ampY * pixelHeight; + + finalColor = texture(texture0, p)*colDiffuse*fragColor; +} diff --git a/examples/shaders/main.lua b/examples/shaders/main.lua new file mode 100644 index 0000000..eea9344 --- /dev/null +++ b/examples/shaders/main.lua @@ -0,0 +1,60 @@ +local monitor = 0 +local shader = -1 +local texture = -1 +local textureSize + +local GLSL_VERSION = "330" -- PLATFORM_DESKTOP +-- local GLSL_VERSION = "100" -- PLATFORM_RPI, PLATFORM_ANDROID, PLATFORM_WEB + +local secondsLoc + +function init() + local mPos = RL_GetMonitorPosition( monitor ) + local mSize = RL_GetMonitorSize( monitor ) + local winSize = RL_GetWindowSize() + + RL_SetWindowState( FLAG_WINDOW_RESIZABLE ) + RL_SetWindowPosition( { mPos[1] + mSize[1] / 2 - winSize[1] / 2, mPos[2] + mSize[2] / 2 - winSize[2] / 2 } ) + + texture = RL_LoadTexture( RL_GetBasePath().."../resources/images/cat.png" ) + textureSize = RL_GetTextureSize( texture ) + shader = RL_LoadShader( nil, RL_GetBasePath().."../resources/shaders/glsl"..GLSL_VERSION.."/wave.fs" ) + + secondsLoc = RL_GetShaderLocation( shader, "secondes" ) + local sizeLoc = RL_GetShaderLocation( shader, "size" ) + local freqXLoc = RL_GetShaderLocation( shader, "freqX" ) + local freqYLoc = RL_GetShaderLocation( shader, "freqY" ) + local ampXLoc = RL_GetShaderLocation( shader, "ampX" ) + local ampYLoc = RL_GetShaderLocation( shader, "ampY" ) + local speedXLoc = RL_GetShaderLocation( shader, "speedX" ) + local speedYLoc = RL_GetShaderLocation( shader, "speedY" ) + + local freqX = 25.0 + local freqY = 25.0 + local ampX = 5.0 + local ampY = 5.0 + local speedX = 8.0 + local speedY = 8.0 + + RL_SetShaderValue( shader, sizeLoc, textureSize, SHADER_UNIFORM_VEC2 ) + RL_SetShaderValue( shader, freqXLoc, { freqX }, SHADER_UNIFORM_FLOAT ) + RL_SetShaderValue( shader, freqYLoc, { freqY }, SHADER_UNIFORM_FLOAT ) + RL_SetShaderValue( shader, ampXLoc, { ampX }, SHADER_UNIFORM_FLOAT ) + RL_SetShaderValue( shader, ampYLoc, { ampY }, SHADER_UNIFORM_FLOAT ) + RL_SetShaderValue( shader, speedXLoc, { speedX }, SHADER_UNIFORM_FLOAT ) + RL_SetShaderValue( shader, speedYLoc, { speedY }, SHADER_UNIFORM_FLOAT ) +end + +local seconds = 0.0 + +function draw() + seconds = seconds + RL_GetFrameTime(); + + RL_SetShaderValue( shader, secondsLoc, { seconds }, SHADER_UNIFORM_FLOAT ); + + RL_ClearBackground( { 100, 150, 100 } ) + + RL_BeginShaderMode( shader ) + RL_DrawTexture( texture, { 0, 0 }, WHITE ); + RL_EndShaderMode() +end diff --git a/examples/snake/main.lua b/examples/snake/main.lua new file mode 100644 index 0000000..74b7191 --- /dev/null +++ b/examples/snake/main.lua @@ -0,0 +1,219 @@ +-- Defines +local RESOLUTION = { 128, 128 } +local TILE_SIZE = 8 +local LEVEL_SIZE = RESOLUTION[1] / TILE_SIZE +local STATE = { TITLE = 0, GAME = 1, OVER = 2 } -- Enum wannabe. + +-- Resources +local framebuffer = -1 +local monitor = 0 +local monitorPos = RL_GetMonitorPosition( monitor ) +local monitorSize = RL_GetMonitorSize( monitor ) +local winScale = 6 +local winSize = { RESOLUTION[1] * winScale, RESOLUTION[2] * winScale } +local gameState = STATE.GAME +local grassTexture = -1 +local snakeTexture = -1 +local appleTexture = -1 +local gameSpeed = 7.0 +local moveTimer = 1.0 +local snake = {} +local applePos = {} + +local function setSnake() + snake = { + heading = { 1, 0 }, + control = { 1, 0 }, + headPos = { LEVEL_SIZE / 2, LEVEL_SIZE / 2 }, + segments = {}, + grow = 2, + } +end + +local function vector2IsEqual( v1, v2 ) + return v1[1] == v2[1] and v1[2] == v2[2] +end + +local function addSegment() + -- If first segment, grow from head and otherwise from tail. New segments are inserted firts. + if #snake.segments == 0 then + table.insert( snake.segments, 1, { pos = snake.headPos, heading = snake.heading } ) + else + table.insert( snake.segments, 1, { pos = snake.segments[ #snake.segments ].pos, + heading = snake.segments[ #snake.segments ].heading } ) + end +end + +local function setApplePos() + applePos = { math.random( 0, LEVEL_SIZE - 1 ), math.random( 0, LEVEL_SIZE - 1 ) } + local search = true + + while search do + search = false + applePos = { math.random( 0, LEVEL_SIZE - 1 ), math.random( 0, LEVEL_SIZE - 1 ) } + + for _, seg in ipairs( snake.segments ) do + search = vector2IsEqual( applePos, seg.pos ) + + if search then + break + end + end + end +end + +-- Init. + +function init() + RL_SetWindowState( FLAG_WINDOW_RESIZABLE ) + RL_SetWindowSize( winSize ) + RL_SetWindowPosition( { monitorPos[1] + monitorSize[1] / 2 - winSize[1] / 2, monitorPos[2] + monitorSize[2] / 2 - winSize[2] / 2 } ) + RL_SetWindowTitle( "Snake" ) + RL_SetWindowIcon( RL_LoadImage( RL_GetBasePath().."../resources/images/apple.png" ) ) + + framebuffer = RL_LoadRenderTexture( RESOLUTION ) + grassTexture = RL_LoadTexture( RL_GetBasePath().."../resources/images/grass.png" ) + snakeTexture = RL_LoadTexture( RL_GetBasePath().."../resources/images/snake.png" ) + appleTexture = RL_LoadTexture( RL_GetBasePath().."../resources/images/apple.png" ) + + setSnake() + setApplePos() +end + +-- Process. + +local function moveSnake() + -- Check if snake has eaten and should grow. + if 0 < snake.grow then + addSegment() + snake.grow = snake.grow - 1 + end + -- Move body. + for i, seg in ipairs( snake.segments ) do + if i < #snake.segments then + seg.pos = snake.segments[ i+1 ].pos + seg.heading = snake.segments[ i+1 ].heading + else + seg.pos = snake.headPos + seg.heading = snake.heading + end + end + -- Move head. + snake.heading = { snake.control[1], snake.control[2] } + snake.headPos = { snake.headPos[1] + snake.heading[1], snake.headPos[2] + snake.heading[2] } + + -- Check appple eating. + if vector2IsEqual( snake.headPos, applePos ) then + snake.grow = snake.grow + 1 + setApplePos() + end + -- Check if hit to body. + for _, seg in ipairs( snake.segments ) do + if vector2IsEqual( snake.headPos, seg.pos ) then + gameState = STATE.OVER + end + end + -- Check if outside or level. + if snake.headPos[1] < 0 or LEVEL_SIZE <= snake.headPos[1] or snake.headPos[2] < 0 or LEVEL_SIZE <= snake.headPos[2] then + gameState = STATE.OVER + end + + moveTimer = moveTimer + 1.0 +end + +function process( delta ) + if gameState == STATE.GAME then -- Run game. + -- Controls. + if RL_IsKeyPressed( KEY_RIGHT ) and 0 <= snake.heading[1] then + snake.control = { 1, 0 } + elseif RL_IsKeyPressed( KEY_LEFT ) and snake.heading[1] <= 0 then + snake.control = { -1, 0 } + elseif RL_IsKeyPressed( KEY_DOWN ) and 0 <= snake.heading[2] then + snake.control = { 0, 1 } + elseif RL_IsKeyPressed( KEY_UP ) and snake.heading[2] <= 0 then + snake.control = { 0, -1 } + end + + moveTimer = moveTimer - gameSpeed * delta + + if moveTimer <= 0.0 then + moveSnake() + end + elseif gameState == STATE.OVER and RL_IsKeyPressed( KEY_ENTER ) then -- Reset game. + setSnake() + setApplePos() + gameState = STATE.GAME + end +end + +-- Drawing. + +local function drawGrass() + for y = 0, LEVEL_SIZE - 1 do + for x = 0, LEVEL_SIZE - 1 do + RL_DrawTexture( grassTexture, { x * TILE_SIZE, y * TILE_SIZE }, WHITE ) + end + end +end + +--[[ Check if next segment is on left side. There are more mathematically elegant solution to this, but there is +only four possibilities so we can just check them all. ]]-- +local function onLeft( this, next ) + return ( vector2IsEqual( this, { 0, -1 } ) and vector2IsEqual( next, { -1, 0 } ) ) + or ( vector2IsEqual( this, { -1, 0 } ) and vector2IsEqual( next, { 0, 1 } ) ) + or ( vector2IsEqual( this, { 0, 1 } ) and vector2IsEqual( next, { 1, 0 } ) ) + or ( vector2IsEqual( this, { 1, 0 } ) and vector2IsEqual( next, { 0, -1 } ) ) +end + +local function drawSnake() + for i, seg in ipairs( snake.segments ) do + local angle = math.deg( RL_Vector2Angle( { 0, 0 }, seg.heading ) ) + local source = { 16, 0, 8, 8 } + + if i == 1 then -- Tail segment. Yes tail is actually the 'first' segment. + source[1] = 8 + + if 1 < #snake.segments then + angle = math.deg( RL_Vector2Angle( { 0, 0 }, snake.segments[ 2 ].heading ) ) + end + elseif i < #snake.segments and not vector2IsEqual( seg.heading, snake.segments[ i+1 ].heading ) then -- Turned middle segments. + source[1] = 0 + -- Mirror turned segment to other way. + if onLeft( seg.heading, snake.segments[ i+1 ].heading ) then + source[4] = -8 + end + elseif i == #snake.segments and not vector2IsEqual( seg.heading, snake.heading ) then -- Turned segment before head. + source[1] = 0 + + if onLeft( seg.heading, snake.heading ) then + source[4] = -8 + end + end + -- Notice that we set the origin to center { 4, 4 } that acts as pivot point. We also have to adjust our dest position by 4. + RL_DrawTexturePro( snakeTexture, source, { seg.pos[1] * TILE_SIZE + 4, seg.pos[2] * TILE_SIZE + 4, 8, 8 }, { 4, 4 }, angle, WHITE ) + end + -- Let's draw the head last to keep it on top. + local angle = math.deg( RL_Vector2Angle( { 0, 0 }, snake.heading ) ) + RL_DrawTexturePro( snakeTexture, { 24, 0, 8, 8 }, { snake.headPos[1] * TILE_SIZE + 4, snake.headPos[2] * TILE_SIZE + 4, 8, 8 }, { 4, 4 }, angle, WHITE ) +end + +local function drawApple() + RL_DrawTexture( appleTexture, { applePos[1] * TILE_SIZE, applePos[2] * TILE_SIZE }, WHITE ) +end + +function draw() + -- Clear the window to black. + RL_ClearBackground( BLACK ) + -- Draw to framebuffer. + RL_BeginTextureMode( framebuffer ) + RL_ClearBackground( BLACK ) + drawGrass() + drawSnake() + drawApple() + RL_EndTextureMode() + + -- Draw framebuffer to window. + RL_SetTextureSource( TEXTURE_SOURCE_RENDER_TEXTURE ) + RL_DrawTexturePro( framebuffer, { 0, 0, RESOLUTION[1], -RESOLUTION[2] }, { 0, 0, winSize[1], winSize[2] }, { 0, 0 }, 0.0, WHITE ) + RL_SetTextureSource( TEXTURE_SOURCE_TEXTURE ) +end diff --git a/include/audio.h b/include/audio.h new file mode 100644 index 0000000..f7f3d39 --- /dev/null +++ b/include/audio.h @@ -0,0 +1,16 @@ +#pragma once + +/* Sounds. */ +int laudioLoadSound( lua_State *L ); +int laudioPlaySoundMulti( lua_State *L ); +int laudioSetSoundVolume( lua_State *L ); +int laudioSetSoundPitch( lua_State *L ); +int laudioUnloadSound( lua_State *L ); +/* Music. */ +int laudioLoadMusicStream( lua_State *L ); +int laudioPlayMusicStream( lua_State *L ); +int laudioStopMusicStream( lua_State *L ); +int laudioPauseMusicStream( lua_State *L ); +int laudioResumeMusicStream( lua_State *L ); +int laudioIsMusicStreamPlaying( lua_State *L ); +int laudioSetMusicVolume( lua_State *L ); diff --git a/include/core.h b/include/core.h new file mode 100644 index 0000000..b277356 --- /dev/null +++ b/include/core.h @@ -0,0 +1,101 @@ +#pragma once + +/* Validators. */ +bool validCamera3D( size_t id ); +/* Window. */ +int lcoreSetWindowMonitor( lua_State *L ); +int lcoreSetWindowPosition( lua_State *L ); +int lcoreSetWindowSize( lua_State *L ); +int lcoreGetMonitorPosition( lua_State *L ); +int lcoreGetMonitorSize( lua_State *L ); +int lcoreGetWindowPosition( lua_State *L ); +int lcoreGetWindowSize( lua_State *L ); +int lcoreSetWindowState( lua_State *L ); +int lcoreIsWindowState( lua_State *L ); +int lcoreClearWindowState( lua_State *L ); +int lcoreIsWindowResized( lua_State *L ); +int lcoreSetWindowIcon( lua_State *L ); +int lcoreSetWindowTitle( lua_State *L ); +/* Timing. */ +int lcoreSetTargetFPS( lua_State *L ); +int lcoreGetFrameTime( lua_State *L ); +int lcoreGetTime( lua_State *L ); +/* Misc. */ +int lcoreTraceLog( lua_State *L ); +int lcoreOpenURL( lua_State *L ); +/* Cursor. */ +int lcoreShowCursor( lua_State *L ); +int lcoreHideCursor( lua_State *L ); +int lcoreIsCursorHidden( lua_State *L ); +int lcoreEnableCursor( lua_State *L ); +int lcoreDisableCursor( lua_State *L ); +int lcoreIsCursorOnScreen( lua_State *L ); +/* Drawing. */ +int lcoreClearBackground( lua_State *L ); +int lcoreBeginBlendMode( lua_State *L ); +int lcoreEndBlendMode( lua_State *L ); +int lcoreBeginScissorMode( lua_State *L ); +int lcoreEndScissorMode( lua_State *L ); +/* Shader. */ +int lcoreLoadShader( lua_State *L ); +int lcoreLoadShaderFromMemory( lua_State *L ); +int lcoreBeginShaderMode( lua_State *L ); +int lcoreEndShaderMode( lua_State *L ); +int lcoreGetShaderLocation( lua_State *L ); +int lcoreGetShaderLocationAttrib( lua_State *L ); +int lcoreSetShaderValueMatrix( lua_State *L ); +int lcoreSetShaderValueTexture( lua_State *L ); +int lcoreSetShaderValue( lua_State *L ); +int lcoreSetShaderValueV( lua_State *L ); +int lcoreUnloadShader( lua_State *L ); +/* File. */ +int lcoreGetBasePath( lua_State *L ); +int lcoreFileExists( lua_State *L ); +int lcoreDirectoryExists( lua_State *L ); +int lcoreIsFileExtension( lua_State *L ); +int lcoreGetFileExtension( lua_State *L ); +int lcoreGetFileName( lua_State *L ); +int lcoreGetFileNameWithoutExt( lua_State *L ); +int lcoreGetDirectoryPath( lua_State *L ); +int lcoreGetPrevDirectoryPath( lua_State *L ); +int lcoreGetWorkingDirectory( lua_State *L ); +int lcoreGetDirectoryFiles( lua_State *L ); +int lcoreGetFileModTime( lua_State *L ); +/* Camera. */ +int lcoreCreateCamera3D( lua_State *L ); +int lcoreUnloadCamera3D( lua_State *L ); +int lcoreBeginMode3D( lua_State *L ); +int lcoreEndMode3D( lua_State *L ); +int lcoreSetCamera3DPosition( lua_State *L ); +int lcoreSetCamera3DTarget( lua_State *L ); +int lcoreSetCamera3DUp( lua_State *L ); +int lcoreSetCamera3DFovy( lua_State *L ); +int lcoreSetCamera3DProjection( lua_State *L ); +int lcoreGetCamera3DPosition( lua_State *L ); +int lcoreGetCamera3DTarget( lua_State *L ); +int lcoreGetCamera3DUp( lua_State *L ); +int lcoreGetCamera3DFovy( lua_State *L ); +int lcoreGetCamera3DProjection( lua_State *L ); +int lcoreUpdateCamera3D( lua_State *L ); +int lcoreSetCamera3DMode( lua_State *L ); +/* Input. */ +int lcoreIsKeyPressed( lua_State *L ); +int lcoreIsKeyDown( lua_State *L ); +int lcoreIsKeyReleased( lua_State *L ); +int lcoreGetKeyPressed( lua_State *L ); +int lcoreGetCharPressed( lua_State *L ); +int lcoreSetExitKey( lua_State *L ); +int lcoreIsGamepadAvailable( lua_State *L ); +int lcoreIsGamepadButtonPressed( lua_State *L ); +int lcoreIsGamepadButtonDown( lua_State *L ); +int lcoreIsGamepadButtonReleased( lua_State *L ); +int lcoreGetGamepadAxisCount( lua_State *L ); +int lcoreGetGamepadAxisMovement( lua_State *L ); +int lcoreGetGamepadName( lua_State *L ); +int lcoreIsMouseButtonPressed( lua_State *L ); +int lcoreIsMouseButtonDown( lua_State *L ); +int lcoreIsMouseButtonReleased( lua_State *L ); +int lcoreGetMousePosition( lua_State *L ); +int lcoreGetMouseDelta( lua_State *L ); +int lcoreGetMouseWheelMove( lua_State *L ); +int lcoreSetMousePosition( lua_State *L ); diff --git a/include/lapi.h b/include/lapi.h new file mode 100644 index 0000000..8e16ad5 --- /dev/null +++ b/include/lapi.h @@ -0,0 +1,24 @@ +/* +** $Id: lapi.h,v 2.9.1.1 2017/04/19 17:20:42 roberto Exp $ +** Auxiliary functions from Lua API +** See Copyright Notice in lua.h +*/ + +#ifndef lapi_h +#define lapi_h + + +#include "llimits.h" +#include "lstate.h" + +#define api_incr_top(L) {L->top++; api_check(L, L->top <= L->ci->top, \ + "stack overflow");} + +#define adjustresults(L,nres) \ + { if ((nres) == LUA_MULTRET && L->ci->top < L->top) L->ci->top = L->top; } + +#define api_checknelems(L,n) api_check(L, (n) < (L->top - L->ci->func), \ + "not enough elements in the stack") + + +#endif diff --git a/include/lauxlib.h b/include/lauxlib.h new file mode 100644 index 0000000..9857d3a --- /dev/null +++ b/include/lauxlib.h @@ -0,0 +1,264 @@ +/* +** $Id: lauxlib.h,v 1.131.1.1 2017/04/19 17:20:42 roberto Exp $ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lauxlib_h +#define lauxlib_h + + +#include +#include + +#include "lua.h" + + + +/* extra error code for 'luaL_loadfilex' */ +#define LUA_ERRFILE (LUA_ERRERR+1) + + +/* key, in the registry, for table of loaded modules */ +#define LUA_LOADED_TABLE "_LOADED" + + +/* key, in the registry, for table of preloaded loaders */ +#define LUA_PRELOAD_TABLE "_PRELOAD" + + +typedef struct luaL_Reg { + const char *name; + lua_CFunction func; +} luaL_Reg; + + +#define LUAL_NUMSIZES (sizeof(lua_Integer)*16 + sizeof(lua_Number)) + +LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz); +#define luaL_checkversion(L) \ + luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES) + +LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); +LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); +LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len); +LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg); +LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg, + size_t *l); +LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg, + const char *def, size_t *l); +LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg); +LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def); + +LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg); +LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg, + lua_Integer def); + +LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); +LUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t); +LUALIB_API void (luaL_checkany) (lua_State *L, int arg); + +LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); +LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); +LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname); +LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); + +LUALIB_API void (luaL_where) (lua_State *L, int lvl); +LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); + +LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def, + const char *const lst[]); + +LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); +LUALIB_API int (luaL_execresult) (lua_State *L, int stat); + +/* predefined references */ +#define LUA_NOREF (-2) +#define LUA_REFNIL (-1) + +LUALIB_API int (luaL_ref) (lua_State *L, int t); +LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); + +LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename, + const char *mode); + +#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL) + +LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, + const char *name, const char *mode); +LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); + +LUALIB_API lua_State *(luaL_newstate) (void); + +LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx); + +LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, + const char *r); + +LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup); + +LUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname); + +LUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1, + const char *msg, int level); + +LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, + lua_CFunction openf, int glb); + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + + +#define luaL_newlibtable(L,l) \ + lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) + +#define luaL_newlib(L,l) \ + (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) + +#define luaL_argcheck(L, cond,arg,extramsg) \ + ((void)((cond) || luaL_argerror(L, (arg), (extramsg)))) +#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) +#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) + +#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) + +#define luaL_dofile(L, fn) \ + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_dostring(L, s) \ + (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) + +#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) + +#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL) + + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + +typedef struct luaL_Buffer { + char *b; /* buffer address */ + size_t size; /* buffer size */ + size_t n; /* number of characters in buffer */ + lua_State *L; + char initb[LUAL_BUFFERSIZE]; /* initial buffer */ +} luaL_Buffer; + + +#define luaL_addchar(B,c) \ + ((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \ + ((B)->b[(B)->n++] = (c))) + +#define luaL_addsize(B,s) ((B)->n += (s)) + +LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); +LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz); +LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); +LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); +LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz); +LUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz); + +#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE) + +/* }====================================================== */ + + + +/* +** {====================================================== +** File handles for IO library +** ======================================================= +*/ + +/* +** A file handle is a userdata with metatable 'LUA_FILEHANDLE' and +** initial structure 'luaL_Stream' (it may contain other fields +** after that initial structure). +*/ + +#define LUA_FILEHANDLE "FILE*" + + +typedef struct luaL_Stream { + FILE *f; /* stream (NULL for incompletely created streams) */ + lua_CFunction closef; /* to close stream (NULL for closed streams) */ +} luaL_Stream; + +/* }====================================================== */ + + + +/* compatibility with old module system */ +#if defined(LUA_COMPAT_MODULE) + +LUALIB_API void (luaL_pushmodule) (lua_State *L, const char *modname, + int sizehint); +LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname, + const luaL_Reg *l, int nup); + +#define luaL_register(L,n,l) (luaL_openlib(L,(n),(l),0)) + +#endif + + +/* +** {================================================================== +** "Abstraction Layer" for basic report of messages and errors +** =================================================================== +*/ + +/* print a string */ +#if !defined(lua_writestring) +#define lua_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) +#endif + +/* print a newline and flush the output */ +#if !defined(lua_writeline) +#define lua_writeline() (lua_writestring("\n", 1), fflush(stdout)) +#endif + +/* print an error message */ +#if !defined(lua_writestringerror) +#define lua_writestringerror(s,p) \ + (fprintf(stderr, (s), (p)), fflush(stderr)) +#endif + +/* }================================================================== */ + + +/* +** {============================================================ +** Compatibility with deprecated conversions +** ============================================================= +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define luaL_checkunsigned(L,a) ((lua_Unsigned)luaL_checkinteger(L,a)) +#define luaL_optunsigned(L,a,d) \ + ((lua_Unsigned)luaL_optinteger(L,a,(lua_Integer)(d))) + +#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) + +#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) + +#endif +/* }============================================================ */ + + + +#endif + + diff --git a/include/lua.h b/include/lua.h new file mode 100644 index 0000000..c236e36 --- /dev/null +++ b/include/lua.h @@ -0,0 +1,486 @@ +/* +** $Id: lua.h,v 1.332.1.2 2018/06/13 16:58:17 roberto Exp $ +** Lua - A Scripting Language +** Lua.org, PUC-Rio, Brazil (http://www.lua.org) +** See Copyright Notice at the end of this file +*/ + + +#ifndef lua_h +#define lua_h + +#include +#include + + +#include "luaconf.h" + + +#define LUA_VERSION_MAJOR "5" +#define LUA_VERSION_MINOR "3" +#define LUA_VERSION_NUM 503 +#define LUA_VERSION_RELEASE "5" + +#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2018 Lua.org, PUC-Rio" +#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" + + +/* mark for precompiled code ('Lua') */ +#define LUA_SIGNATURE "\x1bLua" + +/* option for multiple returns in 'lua_pcall' and 'lua_call' */ +#define LUA_MULTRET (-1) + + +/* +** Pseudo-indices +** (-LUAI_MAXSTACK is the minimum valid index; we keep some free empty +** space after that to help overflow detection) +*/ +#define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000) +#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i)) + + +/* thread status */ +#define LUA_OK 0 +#define LUA_YIELD 1 +#define LUA_ERRRUN 2 +#define LUA_ERRSYNTAX 3 +#define LUA_ERRMEM 4 +#define LUA_ERRGCMM 5 +#define LUA_ERRERR 6 + + +typedef struct lua_State lua_State; + + +/* +** basic types +*/ +#define LUA_TNONE (-1) + +#define LUA_TNIL 0 +#define LUA_TBOOLEAN 1 +#define LUA_TLIGHTUSERDATA 2 +#define LUA_TNUMBER 3 +#define LUA_TSTRING 4 +#define LUA_TTABLE 5 +#define LUA_TFUNCTION 6 +#define LUA_TUSERDATA 7 +#define LUA_TTHREAD 8 + +#define LUA_NUMTAGS 9 + + + +/* minimum Lua stack available to a C function */ +#define LUA_MINSTACK 20 + + +/* predefined values in the registry */ +#define LUA_RIDX_MAINTHREAD 1 +#define LUA_RIDX_GLOBALS 2 +#define LUA_RIDX_LAST LUA_RIDX_GLOBALS + + +/* type of numbers in Lua */ +typedef LUA_NUMBER lua_Number; + + +/* type for integer functions */ +typedef LUA_INTEGER lua_Integer; + +/* unsigned integer type */ +typedef LUA_UNSIGNED lua_Unsigned; + +/* type for continuation-function contexts */ +typedef LUA_KCONTEXT lua_KContext; + + +/* +** Type for C functions registered with Lua +*/ +typedef int (*lua_CFunction) (lua_State *L); + +/* +** Type for continuation functions +*/ +typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx); + + +/* +** Type for functions that read/write blocks when loading/dumping Lua chunks +*/ +typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); + +typedef int (*lua_Writer) (lua_State *L, const void *p, size_t sz, void *ud); + + +/* +** Type for memory-allocation functions +*/ +typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); + + + +/* +** generic extra include file +*/ +#if defined(LUA_USER_H) +#include LUA_USER_H +#endif + + +/* +** RCS ident string +*/ +extern const char lua_ident[]; + + +/* +** state manipulation +*/ +LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); +LUA_API void (lua_close) (lua_State *L); +LUA_API lua_State *(lua_newthread) (lua_State *L); + +LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); + + +LUA_API const lua_Number *(lua_version) (lua_State *L); + + +/* +** basic stack manipulation +*/ +LUA_API int (lua_absindex) (lua_State *L, int idx); +LUA_API int (lua_gettop) (lua_State *L); +LUA_API void (lua_settop) (lua_State *L, int idx); +LUA_API void (lua_pushvalue) (lua_State *L, int idx); +LUA_API void (lua_rotate) (lua_State *L, int idx, int n); +LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx); +LUA_API int (lua_checkstack) (lua_State *L, int n); + +LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); + + +/* +** access functions (stack -> C) +*/ + +LUA_API int (lua_isnumber) (lua_State *L, int idx); +LUA_API int (lua_isstring) (lua_State *L, int idx); +LUA_API int (lua_iscfunction) (lua_State *L, int idx); +LUA_API int (lua_isinteger) (lua_State *L, int idx); +LUA_API int (lua_isuserdata) (lua_State *L, int idx); +LUA_API int (lua_type) (lua_State *L, int idx); +LUA_API const char *(lua_typename) (lua_State *L, int tp); + +LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum); +LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum); +LUA_API int (lua_toboolean) (lua_State *L, int idx); +LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); +LUA_API size_t (lua_rawlen) (lua_State *L, int idx); +LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); +LUA_API void *(lua_touserdata) (lua_State *L, int idx); +LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); +LUA_API const void *(lua_topointer) (lua_State *L, int idx); + + +/* +** Comparison and arithmetic functions +*/ + +#define LUA_OPADD 0 /* ORDER TM, ORDER OP */ +#define LUA_OPSUB 1 +#define LUA_OPMUL 2 +#define LUA_OPMOD 3 +#define LUA_OPPOW 4 +#define LUA_OPDIV 5 +#define LUA_OPIDIV 6 +#define LUA_OPBAND 7 +#define LUA_OPBOR 8 +#define LUA_OPBXOR 9 +#define LUA_OPSHL 10 +#define LUA_OPSHR 11 +#define LUA_OPUNM 12 +#define LUA_OPBNOT 13 + +LUA_API void (lua_arith) (lua_State *L, int op); + +#define LUA_OPEQ 0 +#define LUA_OPLT 1 +#define LUA_OPLE 2 + +LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2); +LUA_API int (lua_compare) (lua_State *L, int idx1, int idx2, int op); + + +/* +** push functions (C -> stack) +*/ +LUA_API void (lua_pushnil) (lua_State *L); +LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); +LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); +LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len); +LUA_API const char *(lua_pushstring) (lua_State *L, const char *s); +LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, + va_list argp); +LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); +LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); +LUA_API void (lua_pushboolean) (lua_State *L, int b); +LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); +LUA_API int (lua_pushthread) (lua_State *L); + + +/* +** get functions (Lua -> stack) +*/ +LUA_API int (lua_getglobal) (lua_State *L, const char *name); +LUA_API int (lua_gettable) (lua_State *L, int idx); +LUA_API int (lua_getfield) (lua_State *L, int idx, const char *k); +LUA_API int (lua_geti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawget) (lua_State *L, int idx); +LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p); + +LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); +LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); +LUA_API int (lua_getmetatable) (lua_State *L, int objindex); +LUA_API int (lua_getuservalue) (lua_State *L, int idx); + + +/* +** set functions (stack -> Lua) +*/ +LUA_API void (lua_setglobal) (lua_State *L, const char *name); +LUA_API void (lua_settable) (lua_State *L, int idx); +LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_seti) (lua_State *L, int idx, lua_Integer n); +LUA_API void (lua_rawset) (lua_State *L, int idx); +LUA_API void (lua_rawseti) (lua_State *L, int idx, lua_Integer n); +LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p); +LUA_API int (lua_setmetatable) (lua_State *L, int objindex); +LUA_API void (lua_setuservalue) (lua_State *L, int idx); + + +/* +** 'load' and 'call' functions (load and run Lua code) +*/ +LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, + lua_KContext ctx, lua_KFunction k); +#define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL) + +LUA_API int (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, + lua_KContext ctx, lua_KFunction k); +#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL) + +LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, + const char *chunkname, const char *mode); + +LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip); + + +/* +** coroutine functions +*/ +LUA_API int (lua_yieldk) (lua_State *L, int nresults, lua_KContext ctx, + lua_KFunction k); +LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg); +LUA_API int (lua_status) (lua_State *L); +LUA_API int (lua_isyieldable) (lua_State *L); + +#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL) + + +/* +** garbage-collection function and options +*/ + +#define LUA_GCSTOP 0 +#define LUA_GCRESTART 1 +#define LUA_GCCOLLECT 2 +#define LUA_GCCOUNT 3 +#define LUA_GCCOUNTB 4 +#define LUA_GCSTEP 5 +#define LUA_GCSETPAUSE 6 +#define LUA_GCSETSTEPMUL 7 +#define LUA_GCISRUNNING 9 + +LUA_API int (lua_gc) (lua_State *L, int what, int data); + + +/* +** miscellaneous functions +*/ + +LUA_API int (lua_error) (lua_State *L); + +LUA_API int (lua_next) (lua_State *L, int idx); + +LUA_API void (lua_concat) (lua_State *L, int n); +LUA_API void (lua_len) (lua_State *L, int idx); + +LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s); + +LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); +LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); + + + +/* +** {============================================================== +** some useful macros +** =============================================================== +*/ + +#define lua_getextraspace(L) ((void *)((char *)(L) - LUA_EXTRASPACE)) + +#define lua_tonumber(L,i) lua_tonumberx(L,(i),NULL) +#define lua_tointeger(L,i) lua_tointegerx(L,(i),NULL) + +#define lua_pop(L,n) lua_settop(L, -(n)-1) + +#define lua_newtable(L) lua_createtable(L, 0, 0) + +#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) + +#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) + +#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) +#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) +#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) +#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) +#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) +#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) +#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) +#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) + +#define lua_pushliteral(L, s) lua_pushstring(L, "" s) + +#define lua_pushglobaltable(L) \ + ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)) + +#define lua_tostring(L,i) lua_tolstring(L, (i), NULL) + + +#define lua_insert(L,idx) lua_rotate(L, (idx), 1) + +#define lua_remove(L,idx) (lua_rotate(L, (idx), -1), lua_pop(L, 1)) + +#define lua_replace(L,idx) (lua_copy(L, -1, (idx)), lua_pop(L, 1)) + +/* }============================================================== */ + + +/* +** {============================================================== +** compatibility macros for unsigned conversions +** =============================================================== +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define lua_pushunsigned(L,n) lua_pushinteger(L, (lua_Integer)(n)) +#define lua_tounsignedx(L,i,is) ((lua_Unsigned)lua_tointegerx(L,i,is)) +#define lua_tounsigned(L,i) lua_tounsignedx(L,(i),NULL) + +#endif +/* }============================================================== */ + +/* +** {====================================================================== +** Debug API +** ======================================================================= +*/ + + +/* +** Event codes +*/ +#define LUA_HOOKCALL 0 +#define LUA_HOOKRET 1 +#define LUA_HOOKLINE 2 +#define LUA_HOOKCOUNT 3 +#define LUA_HOOKTAILCALL 4 + + +/* +** Event masks +*/ +#define LUA_MASKCALL (1 << LUA_HOOKCALL) +#define LUA_MASKRET (1 << LUA_HOOKRET) +#define LUA_MASKLINE (1 << LUA_HOOKLINE) +#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) + +typedef struct lua_Debug lua_Debug; /* activation record */ + + +/* Functions to be called by the debugger in specific events */ +typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); + + +LUA_API int (lua_getstack) (lua_State *L, int level, lua_Debug *ar); +LUA_API int (lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar); +LUA_API const char *(lua_getlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_setlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_getupvalue) (lua_State *L, int funcindex, int n); +LUA_API const char *(lua_setupvalue) (lua_State *L, int funcindex, int n); + +LUA_API void *(lua_upvalueid) (lua_State *L, int fidx, int n); +LUA_API void (lua_upvaluejoin) (lua_State *L, int fidx1, int n1, + int fidx2, int n2); + +LUA_API void (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count); +LUA_API lua_Hook (lua_gethook) (lua_State *L); +LUA_API int (lua_gethookmask) (lua_State *L); +LUA_API int (lua_gethookcount) (lua_State *L); + + +struct lua_Debug { + int event; + const char *name; /* (n) */ + const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */ + const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */ + const char *source; /* (S) */ + int currentline; /* (l) */ + int linedefined; /* (S) */ + int lastlinedefined; /* (S) */ + unsigned char nups; /* (u) number of upvalues */ + unsigned char nparams;/* (u) number of parameters */ + char isvararg; /* (u) */ + char istailcall; /* (t) */ + char short_src[LUA_IDSIZE]; /* (S) */ + /* private part */ + struct CallInfo *i_ci; /* active function */ +}; + +/* }====================================================================== */ + + +/****************************************************************************** +* Copyright (C) 1994-2018 Lua.org, PUC-Rio. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************/ + + +#endif diff --git a/include/lua_core.h b/include/lua_core.h new file mode 100644 index 0000000..66ee22a --- /dev/null +++ b/include/lua_core.h @@ -0,0 +1,27 @@ +#pragma once + +bool luaInit(); +int luaTraceback( lua_State *L ); +bool luaCallMain(); +void luaCallProcess(); +void luaCallDraw(); +void luaRegister(); +/* Lua Util functions */ +Color uluaGetColor( lua_State *L ); +Vector2 uluaGetVector2( lua_State *L ); +Vector3 uluaGetVector3( lua_State *L ); +Rectangle uluaGetRectangle( lua_State *L ); +Quaternion uluaGetQuaternion( lua_State *L ); +Matrix uluaGetMatrix( lua_State *L ); +BoundingBox uluaGetBoundingBox( lua_State *L ); +Ray uluaGetRay( lua_State *L ); +NPatchInfo uluaGetNPatchInfo( lua_State *L ); + +void uluaPushColor( lua_State *L, Color color ); +void uluaPushVector2( lua_State *L, Vector2 vector ); +void uluaPushVector3( lua_State *L, Vector3 vector ); +void uluaPushRectangle( lua_State *L, Rectangle rect ); +void uluaPushMatrix( lua_State *L, Matrix matrix ); +void uluaPushRayCollision( lua_State *L, RayCollision rayCol ); + +int uluaGetTableLen( lua_State *L ); diff --git a/include/luaconf.h b/include/luaconf.h new file mode 100644 index 0000000..9eeeea6 --- /dev/null +++ b/include/luaconf.h @@ -0,0 +1,790 @@ +/* +** $Id: luaconf.h,v 1.259.1.1 2017/04/19 17:29:57 roberto Exp $ +** Configuration file for Lua +** See Copyright Notice in lua.h +*/ + + +#ifndef luaconf_h +#define luaconf_h + +#include +#include + + +/* +** =================================================================== +** Search for "@@" to find all configurable definitions. +** =================================================================== +*/ + + +/* +** {==================================================================== +** System Configuration: macros to adapt (if needed) Lua to some +** particular platform, for instance compiling it with 32-bit numbers or +** restricting it to C89. +** ===================================================================== +*/ + +/* +@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. You +** can also define LUA_32BITS in the make file, but changing here you +** ensure that all software connected to Lua will be compiled with the +** same configuration. +*/ +/* #define LUA_32BITS */ + + +/* +@@ LUA_USE_C89 controls the use of non-ISO-C89 features. +** Define it if you want Lua to avoid the use of a few C99 features +** or Windows-specific features on Windows. +*/ +/* #define LUA_USE_C89 */ + + +/* +** By default, Lua on Windows use (some) specific Windows features +*/ +#if !defined(LUA_USE_C89) && defined(_WIN32) && !defined(_WIN32_WCE) +#define LUA_USE_WINDOWS /* enable goodies for regular Windows */ +#endif + + +#if defined(LUA_USE_WINDOWS) +#define LUA_DL_DLL /* enable support for DLL */ +#define LUA_USE_C89 /* broadly, Windows is C89 */ +#endif + + +#if defined(LUA_USE_LINUX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* needs an extra library: -ldl */ +#define LUA_USE_READLINE /* needs some extra libraries */ +#endif + + +#if defined(LUA_USE_MACOSX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* MacOS does not need -ldl */ +#define LUA_USE_READLINE /* needs an extra library: -lreadline */ +#endif + + +/* +@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for +** C89 ('long' and 'double'); Windows always has '__int64', so it does +** not need to use this case. +*/ +#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS) +#define LUA_C89_NUMBERS +#endif + + + +/* +@@ LUAI_BITSINT defines the (minimum) number of bits in an 'int'. +*/ +/* avoid undefined shifts */ +#if ((INT_MAX >> 15) >> 15) >= 1 +#define LUAI_BITSINT 32 +#else +/* 'int' always must have at least 16 bits */ +#define LUAI_BITSINT 16 +#endif + + +/* +@@ LUA_INT_TYPE defines the type for Lua integers. +@@ LUA_FLOAT_TYPE defines the type for Lua floats. +** Lua should work fine with any mix of these options (if supported +** by your C compiler). The usual configurations are 64-bit integers +** and 'double' (the default), 32-bit integers and 'float' (for +** restricted platforms), and 'long'/'double' (for C compilers not +** compliant with C99, which may not have support for 'long long'). +*/ + +/* predefined options for LUA_INT_TYPE */ +#define LUA_INT_INT 1 +#define LUA_INT_LONG 2 +#define LUA_INT_LONGLONG 3 + +/* predefined options for LUA_FLOAT_TYPE */ +#define LUA_FLOAT_FLOAT 1 +#define LUA_FLOAT_DOUBLE 2 +#define LUA_FLOAT_LONGDOUBLE 3 + +#if defined(LUA_32BITS) /* { */ +/* +** 32-bit integers and 'float' +*/ +#if LUAI_BITSINT >= 32 /* use 'int' if big enough */ +#define LUA_INT_TYPE LUA_INT_INT +#else /* otherwise use 'long' */ +#define LUA_INT_TYPE LUA_INT_LONG +#endif +#define LUA_FLOAT_TYPE LUA_FLOAT_FLOAT + +#elif defined(LUA_C89_NUMBERS) /* }{ */ +/* +** largest types available for C89 ('long' and 'double') +*/ +#define LUA_INT_TYPE LUA_INT_LONG +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE + +#endif /* } */ + + +/* +** default configuration for 64-bit Lua ('long long' and 'double') +*/ +#if !defined(LUA_INT_TYPE) +#define LUA_INT_TYPE LUA_INT_LONGLONG +#endif + +#if !defined(LUA_FLOAT_TYPE) +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE +#endif + +/* }================================================================== */ + + + + +/* +** {================================================================== +** Configuration for Paths. +** =================================================================== +*/ + +/* +** LUA_PATH_SEP is the character that separates templates in a path. +** LUA_PATH_MARK is the string that marks the substitution points in a +** template. +** LUA_EXEC_DIR in a Windows path is replaced by the executable's +** directory. +*/ +#define LUA_PATH_SEP ";" +#define LUA_PATH_MARK "?" +#define LUA_EXEC_DIR "!" + + +/* +@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for +** Lua libraries. +@@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for +** C libraries. +** CHANGE them if your machine has a non-conventional directory +** hierarchy or if you want to install your libraries in +** non-conventional directories. +*/ +#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#if defined(_WIN32) /* { */ +/* +** In Windows, any exclamation mark ('!') in the path is replaced by the +** path of the directory of the executable file of the current process. +*/ +#define LUA_LDIR "!\\lua\\" +#define LUA_CDIR "!\\" +#define LUA_SHRDIR "!\\..\\share\\lua\\" LUA_VDIR "\\" +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" \ + LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \ + ".\\?.lua;" ".\\?\\init.lua" +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.dll;" \ + LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \ + LUA_CDIR"loadall.dll;" ".\\?.dll" + +#else /* }{ */ + +#define LUA_ROOT "/usr/local/" +#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR "/" +#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR "/" +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" \ + "./?.lua;" "./?/init.lua" +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so" +#endif /* } */ + + +/* +@@ LUA_DIRSEP is the directory separator (for submodules). +** CHANGE it if your machine does not use "/" as the directory separator +** and is not Windows. (On Windows Lua automatically uses "\".) +*/ +#if defined(_WIN32) +#define LUA_DIRSEP "\\" +#else +#define LUA_DIRSEP "/" +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Marks for exported symbols in the C code +** =================================================================== +*/ + +/* +@@ LUA_API is a mark for all core API functions. +@@ LUALIB_API is a mark for all auxiliary library functions. +@@ LUAMOD_API is a mark for all standard library opening functions. +** CHANGE them if you need to define those functions in some special way. +** For instance, if you want to create one Windows DLL with the core and +** the libraries, you may want to use the following definition (define +** LUA_BUILD_AS_DLL to get it). +*/ +#if defined(LUA_BUILD_AS_DLL) /* { */ + +#if defined(LUA_CORE) || defined(LUA_LIB) /* { */ +#define LUA_API __declspec(dllexport) +#else /* }{ */ +#define LUA_API __declspec(dllimport) +#endif /* } */ + +#else /* }{ */ + +#define LUA_API extern + +#endif /* } */ + + +/* more often than not the libs go together with the core */ +#define LUALIB_API LUA_API +#define LUAMOD_API LUALIB_API + + +/* +@@ LUAI_FUNC is a mark for all extern functions that are not to be +** exported to outside modules. +@@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables +** that are not to be exported to outside modules (LUAI_DDEF for +** definitions and LUAI_DDEC for declarations). +** CHANGE them if you need to mark them in some special way. Elf/gcc +** (versions 3.2 and later) mark them as "hidden" to optimize access +** when Lua is compiled as a shared library. Not all elf targets support +** this attribute. Unfortunately, gcc does not offer a way to check +** whether the target offers that support, and those without support +** give a warning about it. To avoid these warnings, change to the +** default definition. +*/ +#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ + defined(__ELF__) /* { */ +#define LUAI_FUNC __attribute__((visibility("hidden"))) extern +#else /* }{ */ +#define LUAI_FUNC extern +#endif /* } */ + +#define LUAI_DDEC LUAI_FUNC +#define LUAI_DDEF /* empty */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Compatibility with previous versions +** =================================================================== +*/ + +/* +@@ LUA_COMPAT_5_2 controls other macros for compatibility with Lua 5.2. +@@ LUA_COMPAT_5_1 controls other macros for compatibility with Lua 5.1. +** You can define it to get all options, or change specific options +** to fit your specific needs. +*/ +#if defined(LUA_COMPAT_5_2) /* { */ + +/* +@@ LUA_COMPAT_MATHLIB controls the presence of several deprecated +** functions in the mathematical library. +*/ +#define LUA_COMPAT_MATHLIB + +/* +@@ LUA_COMPAT_BITLIB controls the presence of library 'bit32'. +*/ +#define LUA_COMPAT_BITLIB + +/* +@@ LUA_COMPAT_IPAIRS controls the effectiveness of the __ipairs metamethod. +*/ +#define LUA_COMPAT_IPAIRS + +/* +@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for +** manipulating other integer types (lua_pushunsigned, lua_tounsigned, +** luaL_checkint, luaL_checklong, etc.) +*/ +#define LUA_COMPAT_APIINTCASTS + +#endif /* } */ + + +#if defined(LUA_COMPAT_5_1) /* { */ + +/* Incompatibilities from 5.2 -> 5.3 */ +#define LUA_COMPAT_MATHLIB +#define LUA_COMPAT_APIINTCASTS + +/* +@@ LUA_COMPAT_UNPACK controls the presence of global 'unpack'. +** You can replace it with 'table.unpack'. +*/ +#define LUA_COMPAT_UNPACK + +/* +@@ LUA_COMPAT_LOADERS controls the presence of table 'package.loaders'. +** You can replace it with 'package.searchers'. +*/ +#define LUA_COMPAT_LOADERS + +/* +@@ macro 'lua_cpcall' emulates deprecated function lua_cpcall. +** You can call your C function directly (with light C functions). +*/ +#define lua_cpcall(L,f,u) \ + (lua_pushcfunction(L, (f)), \ + lua_pushlightuserdata(L,(u)), \ + lua_pcall(L,1,0,0)) + + +/* +@@ LUA_COMPAT_LOG10 defines the function 'log10' in the math library. +** You can rewrite 'log10(x)' as 'log(x, 10)'. +*/ +#define LUA_COMPAT_LOG10 + +/* +@@ LUA_COMPAT_LOADSTRING defines the function 'loadstring' in the base +** library. You can rewrite 'loadstring(s)' as 'load(s)'. +*/ +#define LUA_COMPAT_LOADSTRING + +/* +@@ LUA_COMPAT_MAXN defines the function 'maxn' in the table library. +*/ +#define LUA_COMPAT_MAXN + +/* +@@ The following macros supply trivial compatibility for some +** changes in the API. The macros themselves document how to +** change your code to avoid using them. +*/ +#define lua_strlen(L,i) lua_rawlen(L, (i)) + +#define lua_objlen(L,i) lua_rawlen(L, (i)) + +#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) +#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT) + +/* +@@ LUA_COMPAT_MODULE controls compatibility with previous +** module functions 'module' (Lua) and 'luaL_register' (C). +*/ +#define LUA_COMPAT_MODULE + +#endif /* } */ + + +/* +@@ LUA_COMPAT_FLOATSTRING makes Lua format integral floats without a +@@ a float mark ('.0'). +** This macro is not on by default even in compatibility mode, +** because this is not really an incompatibility. +*/ +/* #define LUA_COMPAT_FLOATSTRING */ + +/* }================================================================== */ + + + +/* +** {================================================================== +** Configuration for Numbers. +** Change these definitions if no predefined LUA_FLOAT_* / LUA_INT_* +** satisfy your needs. +** =================================================================== +*/ + +/* +@@ LUA_NUMBER is the floating-point type used by Lua. +@@ LUAI_UACNUMBER is the result of a 'default argument promotion' +@@ over a floating number. +@@ l_mathlim(x) corrects limit name 'x' to the proper float type +** by prefixing it with one of FLT/DBL/LDBL. +@@ LUA_NUMBER_FRMLEN is the length modifier for writing floats. +@@ LUA_NUMBER_FMT is the format for writing floats. +@@ lua_number2str converts a float to a string. +@@ l_mathop allows the addition of an 'l' or 'f' to all math operations. +@@ l_floor takes the floor of a float. +@@ lua_str2number converts a decimal numeric string to a number. +*/ + + +/* The following definitions are good for most cases here */ + +#define l_floor(x) (l_mathop(floor)(x)) + +#define lua_number2str(s,sz,n) \ + l_sprintf((s), sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)(n)) + +/* +@@ lua_numbertointeger converts a float number to an integer, or +** returns 0 if float is not within the range of a lua_Integer. +** (The range comparisons are tricky because of rounding. The tests +** here assume a two-complement representation, where MININTEGER always +** has an exact representation as a float; MAXINTEGER may not have one, +** and therefore its conversion to float may have an ill-defined value.) +*/ +#define lua_numbertointeger(n,p) \ + ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ + (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ + (*(p) = (LUA_INTEGER)(n), 1)) + + +/* now the variable definitions */ + +#if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT /* { single float */ + +#define LUA_NUMBER float + +#define l_mathlim(n) (FLT_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.7g" + +#define l_mathop(op) op##f + +#define lua_str2number(s,p) strtof((s), (p)) + + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE /* }{ long double */ + +#define LUA_NUMBER long double + +#define l_mathlim(n) (LDBL_##n) + +#define LUAI_UACNUMBER long double + +#define LUA_NUMBER_FRMLEN "L" +#define LUA_NUMBER_FMT "%.19Lg" + +#define l_mathop(op) op##l + +#define lua_str2number(s,p) strtold((s), (p)) + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_DOUBLE /* }{ double */ + +#define LUA_NUMBER double + +#define l_mathlim(n) (DBL_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.14g" + +#define l_mathop(op) op + +#define lua_str2number(s,p) strtod((s), (p)) + +#else /* }{ */ + +#error "numeric float type not defined" + +#endif /* } */ + + + +/* +@@ LUA_INTEGER is the integer type used by Lua. +** +@@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER. +** +@@ LUAI_UACINT is the result of a 'default argument promotion' +@@ over a lUA_INTEGER. +@@ LUA_INTEGER_FRMLEN is the length modifier for reading/writing integers. +@@ LUA_INTEGER_FMT is the format for writing integers. +@@ LUA_MAXINTEGER is the maximum value for a LUA_INTEGER. +@@ LUA_MININTEGER is the minimum value for a LUA_INTEGER. +@@ lua_integer2str converts an integer to a string. +*/ + + +/* The following definitions are good for most cases here */ + +#define LUA_INTEGER_FMT "%" LUA_INTEGER_FRMLEN "d" + +#define LUAI_UACINT LUA_INTEGER + +#define lua_integer2str(s,sz,n) \ + l_sprintf((s), sz, LUA_INTEGER_FMT, (LUAI_UACINT)(n)) + +/* +** use LUAI_UACINT here to avoid problems with promotions (which +** can turn a comparison between unsigneds into a signed comparison) +*/ +#define LUA_UNSIGNED unsigned LUAI_UACINT + + +/* now the variable definitions */ + +#if LUA_INT_TYPE == LUA_INT_INT /* { int */ + +#define LUA_INTEGER int +#define LUA_INTEGER_FRMLEN "" + +#define LUA_MAXINTEGER INT_MAX +#define LUA_MININTEGER INT_MIN + +#elif LUA_INT_TYPE == LUA_INT_LONG /* }{ long */ + +#define LUA_INTEGER long +#define LUA_INTEGER_FRMLEN "l" + +#define LUA_MAXINTEGER LONG_MAX +#define LUA_MININTEGER LONG_MIN + +#elif LUA_INT_TYPE == LUA_INT_LONGLONG /* }{ long long */ + +/* use presence of macro LLONG_MAX as proxy for C99 compliance */ +#if defined(LLONG_MAX) /* { */ +/* use ISO C99 stuff */ + +#define LUA_INTEGER long long +#define LUA_INTEGER_FRMLEN "ll" + +#define LUA_MAXINTEGER LLONG_MAX +#define LUA_MININTEGER LLONG_MIN + +#elif defined(LUA_USE_WINDOWS) /* }{ */ +/* in Windows, can use specific Windows types */ + +#define LUA_INTEGER __int64 +#define LUA_INTEGER_FRMLEN "I64" + +#define LUA_MAXINTEGER _I64_MAX +#define LUA_MININTEGER _I64_MIN + +#else /* }{ */ + +#error "Compiler does not support 'long long'. Use option '-DLUA_32BITS' \ + or '-DLUA_C89_NUMBERS' (see file 'luaconf.h' for details)" + +#endif /* } */ + +#else /* }{ */ + +#error "numeric integer type not defined" + +#endif /* } */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Dependencies with C99 and other C details +** =================================================================== +*/ + +/* +@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89. +** (All uses in Lua have only one format item.) +*/ +#if !defined(LUA_USE_C89) +#define l_sprintf(s,sz,f,i) snprintf(s,sz,f,i) +#else +#define l_sprintf(s,sz,f,i) ((void)(sz), sprintf(s,f,i)) +#endif + + +/* +@@ lua_strx2number converts an hexadecimal numeric string to a number. +** In C99, 'strtod' does that conversion. Otherwise, you can +** leave 'lua_strx2number' undefined and Lua will provide its own +** implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_strx2number(s,p) lua_str2number(s,p) +#endif + + +/* +@@ lua_pointer2str converts a pointer to a readable string in a +** non-specified way. +*/ +#define lua_pointer2str(buff,sz,p) l_sprintf(buff,sz,"%p",p) + + +/* +@@ lua_number2strx converts a float to an hexadecimal numeric string. +** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that. +** Otherwise, you can leave 'lua_number2strx' undefined and Lua will +** provide its own implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_number2strx(L,b,sz,f,n) \ + ((void)L, l_sprintf(b,sz,f,(LUAI_UACNUMBER)(n))) +#endif + + +/* +** 'strtof' and 'opf' variants for math functions are not valid in +** C89. Otherwise, the macro 'HUGE_VALF' is a good proxy for testing the +** availability of these variants. ('math.h' is already included in +** all files that use these macros.) +*/ +#if defined(LUA_USE_C89) || (defined(HUGE_VAL) && !defined(HUGE_VALF)) +#undef l_mathop /* variants not available */ +#undef lua_str2number +#define l_mathop(op) (lua_Number)op /* no variant */ +#define lua_str2number(s,p) ((lua_Number)strtod((s), (p))) +#endif + + +/* +@@ LUA_KCONTEXT is the type of the context ('ctx') for continuation +** functions. It must be a numerical type; Lua will use 'intptr_t' if +** available, otherwise it will use 'ptrdiff_t' (the nearest thing to +** 'intptr_t' in C89) +*/ +#define LUA_KCONTEXT ptrdiff_t + +#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 199901L +#include +#if defined(INTPTR_MAX) /* even in C99 this type is optional */ +#undef LUA_KCONTEXT +#define LUA_KCONTEXT intptr_t +#endif +#endif + + +/* +@@ lua_getlocaledecpoint gets the locale "radix character" (decimal point). +** Change that if you do not want to use C locales. (Code using this +** macro must include header 'locale.h'.) +*/ +#if !defined(lua_getlocaledecpoint) +#define lua_getlocaledecpoint() (localeconv()->decimal_point[0]) +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Language Variations +** ===================================================================== +*/ + +/* +@@ LUA_NOCVTN2S/LUA_NOCVTS2N control how Lua performs some +** coercions. Define LUA_NOCVTN2S to turn off automatic coercion from +** numbers to strings. Define LUA_NOCVTS2N to turn off automatic +** coercion from strings to numbers. +*/ +/* #define LUA_NOCVTN2S */ +/* #define LUA_NOCVTS2N */ + + +/* +@@ LUA_USE_APICHECK turns on several consistency checks on the C API. +** Define it as a help when debugging C code. +*/ +#if defined(LUA_USE_APICHECK) +#include +#define luai_apicheck(l,e) assert(e) +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Macros that affect the API and must be stable (that is, must be the +** same when you compile Lua and when you compile code that links to +** Lua). You probably do not want/need to change them. +** ===================================================================== +*/ + +/* +@@ LUAI_MAXSTACK limits the size of the Lua stack. +** CHANGE it if you need a different limit. This limit is arbitrary; +** its only purpose is to stop Lua from consuming unlimited stack +** space (and to reserve some numbers for pseudo-indices). +*/ +#if LUAI_BITSINT >= 32 +#define LUAI_MAXSTACK 1000000 +#else +#define LUAI_MAXSTACK 15000 +#endif + + +/* +@@ LUA_EXTRASPACE defines the size of a raw memory area associated with +** a Lua state with very fast access. +** CHANGE it if you need a different size. +*/ +#define LUA_EXTRASPACE (sizeof(void *)) + + +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +@@ of a function in debug information. +** CHANGE it if you want a different size. +*/ +#define LUA_IDSIZE 60 + + +/* +@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. +** CHANGE it if it uses too much C-stack space. (For long double, +** 'string.format("%.99f", -1e4932)' needs 5034 bytes, so a +** smaller buffer would force a memory allocation for each call to +** 'string.format'.) +*/ +#if LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE +#define LUAL_BUFFERSIZE 8192 +#else +#define LUAL_BUFFERSIZE ((int)(0x80 * sizeof(void*) * sizeof(lua_Integer))) +#endif + +/* }================================================================== */ + + +/* +@@ LUA_QL describes how error messages quote program elements. +** Lua does not use these macros anymore; they are here for +** compatibility only. +*/ +#define LUA_QL(x) "'" x "'" +#define LUA_QS LUA_QL("%s") + + + + +/* =================================================================== */ + +/* +** Local configuration. You can use this space to add your redefinitions +** without modifying the main part of the file. +*/ + + + + + +#endif + diff --git a/include/lualib.h b/include/lualib.h new file mode 100644 index 0000000..f5304aa --- /dev/null +++ b/include/lualib.h @@ -0,0 +1,61 @@ +/* +** $Id: lualib.h,v 1.45.1.1 2017/04/19 17:20:42 roberto Exp $ +** Lua standard libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lualib_h +#define lualib_h + +#include "lua.h" + + +/* version suffix for environment variable names */ +#define LUA_VERSUFFIX "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR + + +LUAMOD_API int (luaopen_base) (lua_State *L); + +#define LUA_COLIBNAME "coroutine" +LUAMOD_API int (luaopen_coroutine) (lua_State *L); + +#define LUA_TABLIBNAME "table" +LUAMOD_API int (luaopen_table) (lua_State *L); + +#define LUA_IOLIBNAME "io" +LUAMOD_API int (luaopen_io) (lua_State *L); + +#define LUA_OSLIBNAME "os" +LUAMOD_API int (luaopen_os) (lua_State *L); + +#define LUA_STRLIBNAME "string" +LUAMOD_API int (luaopen_string) (lua_State *L); + +#define LUA_UTF8LIBNAME "utf8" +LUAMOD_API int (luaopen_utf8) (lua_State *L); + +#define LUA_BITLIBNAME "bit32" +LUAMOD_API int (luaopen_bit32) (lua_State *L); + +#define LUA_MATHLIBNAME "math" +LUAMOD_API int (luaopen_math) (lua_State *L); + +#define LUA_DBLIBNAME "debug" +LUAMOD_API int (luaopen_debug) (lua_State *L); + +#define LUA_LOADLIBNAME "package" +LUAMOD_API int (luaopen_package) (lua_State *L); + + +/* open all previous libraries */ +LUALIB_API void (luaL_openlibs) (lua_State *L); + + + +#if !defined(lua_assert) +#define lua_assert(x) ((void)0) +#endif + + +#endif diff --git a/include/main.h b/include/main.h new file mode 100644 index 0000000..43b0a4c --- /dev/null +++ b/include/main.h @@ -0,0 +1,16 @@ +#pragma once + +#define STRING_LEN 1024 + +#define VERSION_MAJOR 0 +#define VERSION_MINOR 1 + +#include +#include +#include +#include "raylib.h" +#include "raymath.h" +#include "rlgl.h" +#include +#include +#include diff --git a/include/models.h b/include/models.h new file mode 100644 index 0000000..51193bb --- /dev/null +++ b/include/models.h @@ -0,0 +1,68 @@ +#pragma once + +/* Basic. */ +int lmodelsDrawLine3D( lua_State *L ); +int lmodelsDrawPoint3D( lua_State *L ); +int lmodelsDrawCircle3D( lua_State *L ); +int lmodelsDrawTriangle3D( lua_State *L ); +int lmodelsDrawCube( lua_State *L ); +int lmodelsDrawCubeWires( lua_State *L ); +int lmodelsDrawCubeTexture( lua_State *L ); +int lmodelsDrawSphere( lua_State *L ); +int lmodelsDrawSphereEx( lua_State *L ); +int lmodelsDrawSphereWires( lua_State *L ); +int lmodelsDrawCylinder( lua_State *L ); +int lmodelsDrawCylinderEx( lua_State *L ); +int lmodelsDrawCylinderWires( lua_State *L ); +int lmodelsDrawCylinderWiresEx( lua_State *L ); +int lmodelsDrawPlane( lua_State *L ); +int lmodelDrawQuad3DTexture( lua_State *L ); +int lmodelsDrawRay( lua_State *L ); +int lmodelsDrawGrid( lua_State *L ); +/* Mesh. */ +int lmodelsGenMeshPoly( lua_State *L ); +int lmodelsGenMeshPlane( lua_State *L ); +int lmodelsGenMeshCube( lua_State *L ); +int lmodelsGenMeshSphere( lua_State *L ); +int lmodelsGenMeshCylinder( lua_State *L ); +int lmodelsGenMeshCone( lua_State *L ); +int lmodelsGenMeshTorus( lua_State *L ); +int lmodelsGenMeshKnot( lua_State *L ); +int lmodelsGenMeshHeightmap( lua_State *L ); +int lmodelsGenMeshCustom( lua_State *L ); +int lmodelsUnloadMesh( lua_State *L ); +int lmodelsDrawMesh( lua_State *L ); +int lmodelsDrawMeshInstanced( lua_State *L ); +int lmodelsSetMeshColor( lua_State *L ); +/* Material. */ +int lmodelsLoadMaterialDefault( lua_State *L ); +int lmodelsCreateMaterial( lua_State *L ); +int lmodelsUnloadMaterial( lua_State *L ); +int lmodelsSetMaterialTexture( lua_State *L ); +int lmodelsSetMaterialColor( lua_State *L ); +int lmodelsSetMaterialValue( lua_State *L ); +/* Model. */ +int lmodelsLoadModel( lua_State *L ); +int lmodelsLoadModelFromMesh( lua_State *L ); +int lmodelsUnloadModel( lua_State *L ); +int lmodelsDrawModel( lua_State *L ); +int lmodelsDrawModelEx( lua_State *L ); +int lmodelsSetModelMaterial( lua_State *L ); +int lmodelsSetModelMeshMaterial( lua_State *L ); +int lmodelsDrawBillboard( lua_State *L ); +int lmodelsDrawBillboardRec( lua_State *L ); +/* Animations. */ +int lmodelsLoadModelAnimations( lua_State *L ); +int lmodelsUpdateModelAnimation( lua_State *L ); +int lmodelsGetModelAnimationBoneCount( lua_State *L ); +int lmodelsGetModelAnimationFrameCount( lua_State *L ); +/* Collision. */ +int lmodelsCheckCollisionSpheres( lua_State *L ); +int lmodelsCheckCollisionBoxes( lua_State *L ); +int lmodelsCheckCollisionBoxSphere( lua_State *L ); +int lmodelsGetRayCollisionSphere( lua_State *L ); +int lmodelsGetRayCollisionBox( lua_State *L ); +int lmodelsGetRayCollisionModel( lua_State *L ); +int lmodelsGetRayCollisionMesh( lua_State *L ); +int lmodelsGetRayCollisionTriangle( lua_State *L ); +int lmodelsGetRayCollisionQuad( lua_State *L ); diff --git a/include/raudio.h b/include/raudio.h new file mode 100644 index 0000000..7e3c42f --- /dev/null +++ b/include/raudio.h @@ -0,0 +1,198 @@ +/********************************************************************************************** +* +* raudio v1.0 - A simple and easy-to-use audio library based on miniaudio +* +* FEATURES: +* - Manage audio device (init/close) +* - Load and unload audio files +* - Format wave data (sample rate, size, channels) +* - Play/Stop/Pause/Resume loaded audio +* - Manage mixing channels +* - Manage raw audio context +* +* DEPENDENCIES: +* miniaudio.h - Audio device management lib (https://github.com/dr-soft/miniaudio) +* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* dr_mp3.h - MP3 audio file loading (https://github.com/mackron/dr_libs) +* dr_flac.h - FLAC audio file loading (https://github.com/mackron/dr_libs) +* jar_xm.h - XM module file loading +* jar_mod.h - MOD audio file loading +* +* CONTRIBUTORS: +* David Reid (github: @mackron) (Nov. 2017): +* - Complete port to miniaudio library +* +* Joshua Reisenauer (github: @kd7tck) (2015) +* - XM audio module support (jar_xm) +* - MOD audio module support (jar_mod) +* - Mixing channels support +* - Raw audio context support +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAUDIO_H +#define RAUDIO_H + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(p) free(p) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#ifndef __cplusplus +// Boolean type + #if !defined(_STDBOOL_H) + typedef enum { false, true } bool; + #define _STDBOOL_H + #endif +#endif + +// Wave, audio wave data +typedef struct Wave { + unsigned int frameCount; // Total number of frames (considering channels) + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) + void *data; // Buffer data pointer +} Wave; + +typedef struct rAudioBuffer rAudioBuffer; + +// AudioStream, custom audio stream +typedef struct AudioStream { + rAudioBuffer *buffer; // Pointer to internal data used by the audio system + + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) +} AudioStream; + +// Sound +typedef struct Sound { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) +} Sound; + +// Music, audio stream, anything longer than ~10 seconds should be streamed +typedef struct Music { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) + bool looping; // Music looping enable + + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type +} Music; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +// Audio device management functions +void InitAudioDevice(void); // Initialize audio device and context +void CloseAudioDevice(void); // Close the audio device and context +bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +void SetMasterVolume(float volume); // Set master volume (listener) + +// Wave/Sound loading/unloading functions +Wave LoadWave(const char *fileName); // Load wave data from file +Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. ".wav" +Sound LoadSound(const char *fileName); // Load sound from file +Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +void UpdateSound(Sound sound, const void *data, int samplesCount);// Update sound buffer with new data +void UnloadWave(Wave wave); // Unload wave data +void UnloadSound(Sound sound); // Unload sound +bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success +bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success + +// Wave/Sound management functions +void PlaySound(Sound sound); // Play a sound +void StopSound(Sound sound); // Stop playing a sound +void PauseSound(Sound sound); // Pause a sound +void ResumeSound(Sound sound); // Resume a paused sound +void PlaySoundMulti(Sound sound); // Play a sound (using multichannel buffer pool) +void StopSoundMulti(void); // Stop any sound playing (using multichannel buffer pool) +int GetSoundsPlaying(void); // Get number of sounds playing in the multichannel +bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing +void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +Wave WaveCopy(Wave wave); // Copy a wave to a new wave +void WaveCrop(Wave *wave, int initSample, int finalSample); // Crop a wave to defined samples range +float *LoadWaveSamples(Wave wave); // Load samples data from wave as a floats array +void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() + +// Music management functions +Music LoadMusicStream(const char *fileName); // Load music stream from file +Music LoadMusicStreamFromMemory(const char *fileType, unsigned char* data, int dataSize); // Load music stream from data +void UnloadMusicStream(Music music); // Unload music stream +void PlayMusicStream(Music music); // Start music playing +bool IsMusicStreamPlaying(Music music); // Check if music is playing +void UpdateMusicStream(Music music); // Updates buffers for music streaming +void StopMusicStream(Music music); // Stop music playing +void PauseMusicStream(Music music); // Pause music playing +void ResumeMusicStream(Music music); // Resume playing paused music +void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) +void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +float GetMusicTimeLength(Music music); // Get music time length (in seconds) +float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) +void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount); // Update audio stream buffers with data +void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory +bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +void PlayAudioStream(AudioStream stream); // Play audio stream +void PauseAudioStream(AudioStream stream); // Pause audio stream +void ResumeAudioStream(AudioStream stream); // Resume audio stream +bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +void StopAudioStream(AudioStream stream); // Stop audio stream +void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams + +#ifdef __cplusplus +} +#endif + +#endif // RAUDIO_H diff --git a/include/raygui.h b/include/raygui.h new file mode 100644 index 0000000..2f5aa05 --- /dev/null +++ b/include/raygui.h @@ -0,0 +1,4342 @@ +/******************************************************************************************* +* +* raygui v3.0 - A simple and easy-to-use immediate-mode gui library +* +* DESCRIPTION: +* +* raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also +* available as a standalone library, as long as input and drawing functions are provided. +* +* Controls provided: +* +* # Container/separators Controls +* - WindowBox +* - GroupBox +* - Line +* - Panel +* +* # Basic Controls +* - Label +* - Button +* - LabelButton --> Label +* - Toggle +* - ToggleGroup --> Toggle +* - CheckBox +* - ComboBox +* - DropdownBox +* - TextBox +* - TextBoxMulti +* - ValueBox --> TextBox +* - Spinner --> Button, ValueBox +* - Slider +* - SliderBar --> Slider +* - ProgressBar +* - StatusBar +* - ScrollBar +* - ScrollPanel +* - DummyRec +* - Grid +* +* # Advance Controls +* - ListView +* - ColorPicker --> ColorPanel, ColorBarHue +* - MessageBox --> Window, Label, Button +* - TextInputBox --> Window, Label, TextBox, Button +* +* It also provides a set of functions for styling the controls based on its properties (size, color). +* +* +* GUI STYLE (guiStyle): +* +* raygui uses a global data array for all gui style properties (allocated on data segment by default), +* when a new style is loaded, it is loaded over the global style... but a default gui style could always be +* recovered with GuiLoadStyleDefault() function, that overwrites the current style to the default one +* +* The global style array size is fixed and depends on the number of controls and properties: +* +* static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)]; +* +* guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +* +* Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style +* used for all controls, when any of those base values is set, it is automatically populated to all +* controls, so, specific control values overwriting generic style should be set after base values. +* +* After the first BASE set we have the EXTENDED properties (by default guiStyle[16..23]), those +* properties are actually common to all controls and can not be overwritten individually (like BASE ones) +* Some of those properties are: TEXT_SIZE, TEXT_SPACING, LINE_COLOR, BACKGROUND_COLOR +* +* Custom control properties can be defined using the EXTENDED properties for each independent control. +* +* TOOL: rGuiStyler is a visual tool to customize raygui style. +* +* +* GUI ICONS (guiIcons): +* +* raygui could use a global array containing icons data (allocated on data segment by default), +* a custom icons set could be loaded over this array using GuiLoadIcons(), but loaded icons set +* must be same RICON_SIZE and no more than RICON_MAX_ICONS will be loaded +* +* Every icon is codified in binary form, using 1 bit per pixel, so, every 16x16 icon +* requires 8 integers (16*16/32) to be stored in memory. +* +* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set. +* +* The global icons array size is fixed and depends on the number of icons and size: +* +* static unsigned int guiIcons[RICON_MAX_ICONS*RICON_DATA_ELEMENTS]; +* +* guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +* +* TOOL: rGuiIcons is a visual tool to customize raygui icons. +* +* +* CONFIGURATION: +* +* #define RAYGUI_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RAYGUI_STANDALONE +* Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined +* internally in the library and input management and drawing functions must be provided by +* the user (check library implementation for further details). +* +* #define RAYGUI_NO_RICONS +* Avoid including embedded ricons data (256 icons, 16x16 pixels, 1-bit per pixel, 2KB) +* +* #define RAYGUI_CUSTOM_RICONS +* Includes custom ricons.h header defining a set of custom icons, +* this file can be generated using rGuiIcons tool +* +* +* VERSIONS HISTORY: +* +* 3.0 (xx-Sep-2021) Integrated ricons data to avoid external file +* REDESIGNED: GuiTextBoxMulti() +* REMOVED: GuiImageButton*() +* Multiple minor tweaks and bugs corrected +* 2.9 (17-Mar-2021) REMOVED: Tooltip API +* 2.8 (03-May-2020) Centralized rectangles drawing to GuiDrawRectangle() +* 2.7 (20-Feb-2020) ADDED: Possible tooltips API +* 2.6 (09-Sep-2019) ADDED: GuiTextInputBox() +* REDESIGNED: GuiListView*(), GuiDropdownBox(), GuiSlider*(), GuiProgressBar(), GuiMessageBox() +* REVIEWED: GuiTextBox(), GuiSpinner(), GuiValueBox(), GuiLoadStyle() +* Replaced property INNER_PADDING by TEXT_PADDING, renamed some properties +* ADDED: 8 new custom styles ready to use +* Multiple minor tweaks and bugs corrected +* 2.5 (28-May-2019) Implemented extended GuiTextBox(), GuiValueBox(), GuiSpinner() +* 2.3 (29-Apr-2019) ADDED: rIcons auxiliar library and support for it, multiple controls reviewed +* Refactor all controls drawing mechanism to use control state +* 2.2 (05-Feb-2019) ADDED: GuiScrollBar(), GuiScrollPanel(), reviewed GuiListView(), removed Gui*Ex() controls +* 2.1 (26-Dec-2018) REDESIGNED: GuiCheckBox(), GuiComboBox(), GuiDropdownBox(), GuiToggleGroup() > Use combined text string +* REDESIGNED: Style system (breaking change) +* 2.0 (08-Nov-2018) ADDED: Support controls guiLock and custom fonts +* REVIEWED: GuiComboBox(), GuiListView()... +* 1.9 (09-Oct-2018) REVIEWED: GuiGrid(), GuiTextBox(), GuiTextBoxMulti(), GuiValueBox()... +* 1.8 (01-May-2018) Lot of rework and redesign to align with rGuiStyler and rGuiLayout +* 1.5 (21-Jun-2017) Working in an improved styles system +* 1.4 (15-Jun-2017) Rewritten all GUI functions (removed useless ones) +* 1.3 (12-Jun-2017) Complete redesign of style system +* 1.1 (01-Jun-2017) Complete review of the library +* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria. +* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria. +* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria. +* +* +* CONTRIBUTORS: +* +* Ramon Santamaria: Supervision, review, redesign, update and maintenance +* Vlad Adrian: Complete rewrite of GuiTextBox() to support extended features (2019) +* Sergio Martinez: Review, testing (2015) and redesign of multiple controls (2018) +* Adria Arranz: Testing and Implementation of additional controls (2018) +* Jordi Jorba: Testing and Implementation of additional controls (2018) +* Albert Martos: Review and testing of the library (2015) +* Ian Eito: Review and testing of the library (2015) +* Kevin Gato: Initial implementation of basic components (2014) +* Daniel Nicolas: Initial implementation of basic components (2014) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYGUI_H +#define RAYGUI_H + +#define RAYGUI_VERSION "3.0" + +#if !defined(RAYGUI_STANDALONE) + #include "raylib.h" +#endif + +// Function specifiers in case library is build/used as a shared library (Windows) +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +#if defined(_WIN32) + #if defined(BUILD_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif +#endif + +// Function specifiers definition +#ifndef RAYGUIAPI + #define RAYGUIAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Allow custom memory allocators +#ifndef RAYGUI_MALLOC + #define RAYGUI_MALLOC(sz) malloc(sz) +#endif +#ifndef RAYGUI_CALLOC + #define RAYGUI_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RAYGUI_FREE + #define RAYGUI_FREE(p) free(p) +#endif + +// TODO: Implement custom TraceLog() +#define TRACELOG(level, ...) (void)0 + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Some types are required for RAYGUI_STANDALONE usage +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + #ifndef __cplusplus + // Boolean type + #ifndef true + typedef enum { false, true } bool; + #endif + #endif + + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Vector3 type // -- ConvertHSVtoRGB(), ConvertRGBtoHSV() + typedef struct Vector3 { + float x; + float y; + float z; + } Vector3; + + // Color type, RGBA (32bit) + typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + } Color; + + // Rectangle type + typedef struct Rectangle { + float x; + float y; + float width; + float height; + } Rectangle; + + // TODO: Texture2D type is very coupled to raylib, required by Font type + // It should be redesigned to be provided by user + typedef struct Texture2D { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Texture2D; + + // GlyphInfo, font characters glyphs info + typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data + } GlyphInfo; + + // TODO: Font type is very coupled to raylib, mostly required by GuiLoadStyle() + // It should be redesigned to be provided by user + typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of characters + Texture2D texture; // Characters texture atlas + Rectangle *recs; // Characters rectangles in texture + GlyphInfo *chars; // Characters info data + } Font; +#endif + +// Style property +typedef struct GuiStyleProp { + unsigned short controlId; + unsigned short propertyId; + int propertyValue; +} GuiStyleProp; + +// Gui control state +typedef enum { + GUI_STATE_NORMAL = 0, + GUI_STATE_FOCUSED, + GUI_STATE_PRESSED, + GUI_STATE_DISABLED, +} GuiControlState; + +// Gui control text alignment +typedef enum { + GUI_TEXT_ALIGN_LEFT = 0, + GUI_TEXT_ALIGN_CENTER, + GUI_TEXT_ALIGN_RIGHT, +} GuiTextAlignment; + +// Gui controls +typedef enum { + DEFAULT = 0, // Generic control -> populates to all controls when set + LABEL, // Used also for: LABELBUTTON + BUTTON, + TOGGLE, // Used also for: TOGGLEGROUP + SLIDER, // Used also for: SLIDERBAR + PROGRESSBAR, + CHECKBOX, + COMBOBOX, + DROPDOWNBOX, + TEXTBOX, // Used also for: TEXTBOXMULTI + VALUEBOX, + SPINNER, + LISTVIEW, + COLORPICKER, + SCROLLBAR, + STATUSBAR +} GuiControl; + +// Gui base properties for every control +// NOTE: RAYGUI_MAX_PROPS_BASE properties (by default 16 properties) +typedef enum { + BORDER_COLOR_NORMAL = 0, + BASE_COLOR_NORMAL, + TEXT_COLOR_NORMAL, + BORDER_COLOR_FOCUSED, + BASE_COLOR_FOCUSED, + TEXT_COLOR_FOCUSED, + BORDER_COLOR_PRESSED, + BASE_COLOR_PRESSED, + TEXT_COLOR_PRESSED, + BORDER_COLOR_DISABLED, + BASE_COLOR_DISABLED, + TEXT_COLOR_DISABLED, + BORDER_WIDTH, + TEXT_PADDING, + TEXT_ALIGNMENT, + RESERVED +} GuiControlProperty; + +// Gui extended properties depend on control +// NOTE: RAYGUI_MAX_PROPS_EXTENDED properties (by default 8 properties) + +// DEFAULT extended properties +// NOTE: Those properties are actually common to all controls +typedef enum { + TEXT_SIZE = 16, + TEXT_SPACING, + LINE_COLOR, + BACKGROUND_COLOR, +} GuiDefaultProperty; + +// Label +//typedef enum { } GuiLabelProperty; + +// Button +//typedef enum { } GuiButtonProperty; + +// Toggle/ToggleGroup +typedef enum { + GROUP_PADDING = 16, +} GuiToggleProperty; + +// Slider/SliderBar +typedef enum { + SLIDER_WIDTH = 16, + SLIDER_PADDING +} GuiSliderProperty; + +// ProgressBar +typedef enum { + PROGRESS_PADDING = 16, +} GuiProgressBarProperty; + +// CheckBox +typedef enum { + CHECK_PADDING = 16 +} GuiCheckBoxProperty; + +// ComboBox +typedef enum { + COMBO_BUTTON_WIDTH = 16, + COMBO_BUTTON_PADDING +} GuiComboBoxProperty; + +// DropdownBox +typedef enum { + ARROW_PADDING = 16, + DROPDOWN_ITERL_PADDING +} GuiDropdownBoxProperty; + +// TextBox/TextBoxMulti/ValueBox/Spinner +typedef enum { + TEXT_INNER_PADDING = 16, + TEXT_LINES_PADDING, + COLOR_SELECTED_FG, + COLOR_SELECTED_BG +} GuiTextBoxProperty; + +// Spinner +typedef enum { + SPIN_BUTTON_WIDTH = 16, + SPIN_BUTTON_PADDING, +} GuiSpinnerProperty; + +// ScrollBar +typedef enum { + ARROWS_SIZE = 16, + ARROWS_VISIBLE, + SCROLL_SLIDER_PADDING, + SCROLL_SLIDER_SIZE, + SCROLL_PADDING, + SCROLL_SPEED, +} GuiScrollBarProperty; + +// ScrollBar side +typedef enum { + SCROLLBAR_LEFT_SIDE = 0, + SCROLLBAR_RIGHT_SIDE +} GuiScrollBarSide; + +// ListView +typedef enum { + LIST_ITERL_HEIGHT = 16, + LIST_ITERL_PADDING, + SCROLLBAR_WIDTH, + SCROLLBAR_SIDE, +} GuiListViewProperty; + +// ColorPicker +typedef enum { + COLOR_SELECTOR_SIZE = 16, + HUEBAR_WIDTH, // Right hue bar width + HUEBAR_PADDING, // Right hue bar separation from panel + HUEBAR_SELECTOR_HEIGHT, // Right hue bar selector height + HUEBAR_SELECTOR_OVERFLOW // Right hue bar selector overflow +} GuiColorPickerProperty; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Global gui state control functions +RAYGUIAPI void GuiEnable(void); // Enable gui controls (global state) +RAYGUIAPI void GuiDisable(void); // Disable gui controls (global state) +RAYGUIAPI void GuiLock(void); // Lock gui controls (global state) +RAYGUIAPI void GuiUnlock(void); // Unlock gui controls (global state) +RAYGUIAPI bool GuiIsLocked(void); // Check if gui is locked (global state) +RAYGUIAPI void GuiFade(float alpha); // Set gui controls alpha (global state), alpha goes from 0.0f to 1.0f +RAYGUIAPI void GuiSetState(int state); // Set gui state (global state) +RAYGUIAPI int GuiGetState(void); // Get gui state (global state) + +// Font set/get functions +RAYGUIAPI void GuiSetFont(Font font); // Set gui custom font (global state) +RAYGUIAPI Font GuiGetFont(void); // Get gui custom font (global state) + +// Style set/get functions +RAYGUIAPI void GuiSetStyle(int control, int property, int value); // Set one style property +RAYGUIAPI int GuiGetStyle(int control, int property); // Get one style property + +// Container/separator controls, useful for controls organization +RAYGUIAPI bool GuiWindowBox(Rectangle bounds, const char *title); // Window Box control, shows a window that can be closed +RAYGUIAPI void GuiGroupBox(Rectangle bounds, const char *text); // Group Box control with text name +RAYGUIAPI void GuiLine(Rectangle bounds, const char *text); // Line separator control, could contain text +RAYGUIAPI void GuiPanel(Rectangle bounds); // Panel control, useful to group controls +RAYGUIAPI Rectangle GuiScrollPanel(Rectangle bounds, Rectangle content, Vector2 *scroll); // Scroll Panel control + +// Basic controls set +RAYGUIAPI void GuiLabel(Rectangle bounds, const char *text); // Label control, shows text +RAYGUIAPI bool GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked +RAYGUIAPI bool GuiLabelButton(Rectangle bounds, const char *text); // Label button control, show true when clicked +RAYGUIAPI bool GuiToggle(Rectangle bounds, const char *text, bool active); // Toggle Button control, returns true when active +RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int active); // Toggle Group control, returns active toggle index +RAYGUIAPI bool GuiCheckBox(Rectangle bounds, const char *text, bool checked); // Check Box control, returns true when active +RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int active); // Combo Box control, returns selected item index +RAYGUIAPI bool GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control, returns selected item +RAYGUIAPI bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control, returns selected value +RAYGUIAPI bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers +RAYGUIAPI bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text +RAYGUIAPI bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control with multiple lines +RAYGUIAPI float GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Slider control, returns selected value +RAYGUIAPI float GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Slider Bar control, returns selected value +RAYGUIAPI float GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Progress Bar control, shows current progress value +RAYGUIAPI void GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text +RAYGUIAPI void GuiDummyRec(Rectangle bounds, const char *text); // Dummy control for placeholders +RAYGUIAPI int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue); // Scroll Bar control +RAYGUIAPI Vector2 GuiGrid(Rectangle bounds, float spacing, int subdivs); // Grid control + + +// Advance controls set +RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int active); // List View control, returns selected list item index +RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *focus, int *scrollIndex, int active); // List View with extended parameters +RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message +RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text); // Text Input Box control, ask for text +RAYGUIAPI Color GuiColorPicker(Rectangle bounds, Color color); // Color Picker control (multiple color controls) +RAYGUIAPI Color GuiColorPanel(Rectangle bounds, Color color); // Color Panel control +RAYGUIAPI float GuiColorBarAlpha(Rectangle bounds, float alpha); // Color Bar Alpha control +RAYGUIAPI float GuiColorBarHue(Rectangle bounds, float value); // Color Bar Hue control + +// Styles loading functions +RAYGUIAPI void GuiLoadStyle(const char *fileName); // Load style file over global style variable (.rgs) +RAYGUIAPI void GuiLoadStyleDefault(void); // Load style default over global style + +/* +typedef GuiStyle (unsigned int *) +RAYGUIAPI GuiStyle LoadGuiStyle(const char *fileName); // Load style from file (.rgs) +RAYGUIAPI void UnloadGuiStyle(GuiStyle style); // Unload style +*/ + +RAYGUIAPI const char *GuiIconText(int iconId, const char *text); // Get text with icon id prepended (if supported) + +#if !defined(RAYGUI_NO_RICONS) +// Gui icons functionality +RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); + +RAYGUIAPI unsigned int *GuiGetIcons(void); // Get full icons data pointer +RAYGUIAPI unsigned int *GuiGetIconData(int iconId); // Get icon bit data +RAYGUIAPI void GuiSetIconData(int iconId, unsigned int *data); // Set icon bit data + +RAYGUIAPI void GuiSetIconPixel(int iconId, int x, int y); // Set icon pixel value +RAYGUIAPI void GuiClearIconPixel(int iconId, int x, int y); // Clear icon pixel value +RAYGUIAPI bool GuiCheckIconPixel(int iconId, int x, int y); // Check icon pixel value +#endif + +#if defined(__cplusplus) +} // Prevents name mangling of functions +#endif + +#endif // RAYGUI_H + +/*********************************************************************************** +* +* RAYGUI IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RAYGUI_IMPLEMENTATION) + +#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: strlen() [GuiTextBox(), GuiTextBoxMulti(), GuiValueBox()], memset(), memcpy() +#include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] +#include // Required for: roundf() [GuiColorPicker()] + +#ifdef __cplusplus + #define RAYGUI_CLITERAL(name) name +#else + #define RAYGUI_CLITERAL(name) (name) +#endif + +#if !defined(RAYGUI_NO_RICONS) + +#if defined(RAYGUI_CUSTOM_RICONS) + +#define RICONS_IMPLEMENTATION +#include "ricons.h" // External icons data provided, it can be generated with rGuiIcons tool + +#else // Embedded raygui icons, no external file provided + +#define RICON_SIZE 16 // Size of icons (squared) +#define RICON_MAX_ICONS 256 // Maximum number of icons +#define RICON_MAX_NAME_LENGTH 32 // Maximum length of icon name id + +// Icons data is defined by bit array (every bit represents one pixel) +// Those arrays are stored as unsigned int data arrays, so every array +// element defines 32 pixels (bits) of information +// Number of elemens depend on RICON_SIZE (by default 16x16 pixels) +#define RICON_DATA_ELEMENTS (RICON_SIZE*RICON_SIZE/32) + +//---------------------------------------------------------------------------------- +// Icons enumeration +//---------------------------------------------------------------------------------- +typedef enum { + RICON_NONE = 0, + RICON_FOLDER_FILE_OPEN = 1, + RICON_FILE_SAVE_CLASSIC = 2, + RICON_FOLDER_OPEN = 3, + RICON_FOLDER_SAVE = 4, + RICON_FILE_OPEN = 5, + RICON_FILE_SAVE = 6, + RICON_FILE_EXPORT = 7, + RICON_FILE_NEW = 8, + RICON_FILE_DELETE = 9, + RICON_FILETYPE_TEXT = 10, + RICON_FILETYPE_AUDIO = 11, + RICON_FILETYPE_IMAGE = 12, + RICON_FILETYPE_PLAY = 13, + RICON_FILETYPE_VIDEO = 14, + RICON_FILETYPE_INFO = 15, + RICON_FILE_COPY = 16, + RICON_FILE_CUT = 17, + RICON_FILE_PASTE = 18, + RICON_CURSOR_HAND = 19, + RICON_CURSOR_POINTER = 20, + RICON_CURSOR_CLASSIC = 21, + RICON_PENCIL = 22, + RICON_PENCIL_BIG = 23, + RICON_BRUSH_CLASSIC = 24, + RICON_BRUSH_PAINTER = 25, + RICON_WATER_DROP = 26, + RICON_COLOR_PICKER = 27, + RICON_RUBBER = 28, + RICON_COLOR_BUCKET = 29, + RICON_TEXT_T = 30, + RICON_TEXT_A = 31, + RICON_SCALE = 32, + RICON_RESIZE = 33, + RICON_FILTER_POINT = 34, + RICON_FILTER_BILINEAR = 35, + RICON_CROP = 36, + RICON_CROP_ALPHA = 37, + RICON_SQUARE_TOGGLE = 38, + RICON_SYMMETRY = 39, + RICON_SYMMETRY_HORIZONTAL = 40, + RICON_SYMMETRY_VERTICAL = 41, + RICON_LENS = 42, + RICON_LENS_BIG = 43, + RICON_EYE_ON = 44, + RICON_EYE_OFF = 45, + RICON_FILTER_TOP = 46, + RICON_FILTER = 47, + RICON_TARGET_POINT = 48, + RICON_TARGET_SMALL = 49, + RICON_TARGET_BIG = 50, + RICON_TARGET_MOVE = 51, + RICON_CURSOR_MOVE = 52, + RICON_CURSOR_SCALE = 53, + RICON_CURSOR_SCALE_RIGHT = 54, + RICON_CURSOR_SCALE_LEFT = 55, + RICON_UNDO = 56, + RICON_REDO = 57, + RICON_REREDO = 58, + RICON_MUTATE = 59, + RICON_ROTATE = 60, + RICON_REPEAT = 61, + RICON_SHUFFLE = 62, + RICON_EMPTYBOX = 63, + RICON_TARGET = 64, + RICON_TARGET_SMALL_FILL = 65, + RICON_TARGET_BIG_FILL = 66, + RICON_TARGET_MOVE_FILL = 67, + RICON_CURSOR_MOVE_FILL = 68, + RICON_CURSOR_SCALE_FILL = 69, + RICON_CURSOR_SCALE_RIGHT_FILL = 70, + RICON_CURSOR_SCALE_LEFT_FILL = 71, + RICON_UNDO_FILL = 72, + RICON_REDO_FILL = 73, + RICON_REREDO_FILL = 74, + RICON_MUTATE_FILL = 75, + RICON_ROTATE_FILL = 76, + RICON_REPEAT_FILL = 77, + RICON_SHUFFLE_FILL = 78, + RICON_EMPTYBOX_SMALL = 79, + RICON_BOX = 80, + RICON_BOX_TOP = 81, + RICON_BOX_TOP_RIGHT = 82, + RICON_BOX_RIGHT = 83, + RICON_BOX_BOTTOM_RIGHT = 84, + RICON_BOX_BOTTOM = 85, + RICON_BOX_BOTTOM_LEFT = 86, + RICON_BOX_LEFT = 87, + RICON_BOX_TOP_LEFT = 88, + RICON_BOX_CENTER = 89, + RICON_BOX_CIRCLE_MASK = 90, + RICON_POT = 91, + RICON_ALPHA_MULTIPLY = 92, + RICON_ALPHA_CLEAR = 93, + RICON_DITHERING = 94, + RICON_MIPMAPS = 95, + RICON_BOX_GRID = 96, + RICON_GRID = 97, + RICON_BOX_CORNERS_SMALL = 98, + RICON_BOX_CORNERS_BIG = 99, + RICON_FOUR_BOXES = 100, + RICON_GRID_FILL = 101, + RICON_BOX_MULTISIZE = 102, + RICON_ZOOM_SMALL = 103, + RICON_ZOOM_MEDIUM = 104, + RICON_ZOOM_BIG = 105, + RICON_ZOOM_ALL = 106, + RICON_ZOOM_CENTER = 107, + RICON_BOX_DOTS_SMALL = 108, + RICON_BOX_DOTS_BIG = 109, + RICON_BOX_CONCENTRIC = 110, + RICON_BOX_GRID_BIG = 111, + RICON_OK_TICK = 112, + RICON_CROSS = 113, + RICON_ARROW_LEFT = 114, + RICON_ARROW_RIGHT = 115, + RICON_ARROW_DOWN = 116, + RICON_ARROW_UP = 117, + RICON_ARROW_LEFT_FILL = 118, + RICON_ARROW_RIGHT_FILL = 119, + RICON_ARROW_DOWN_FILL = 120, + RICON_ARROW_UP_FILL = 121, + RICON_AUDIO = 122, + RICON_FX = 123, + RICON_WAVE = 124, + RICON_WAVE_SINUS = 125, + RICON_WAVE_SQUARE = 126, + RICON_WAVE_TRIANGULAR = 127, + RICON_CROSS_SMALL = 128, + RICON_PLAYER_PREVIOUS = 129, + RICON_PLAYER_PLAY_BACK = 130, + RICON_PLAYER_PLAY = 131, + RICON_PLAYER_PAUSE = 132, + RICON_PLAYER_STOP = 133, + RICON_PLAYER_NEXT = 134, + RICON_PLAYER_RECORD = 135, + RICON_MAGNET = 136, + RICON_LOCK_CLOSE = 137, + RICON_LOCK_OPEN = 138, + RICON_CLOCK = 139, + RICON_TOOLS = 140, + RICON_GEAR = 141, + RICON_GEAR_BIG = 142, + RICON_BIN = 143, + RICON_HAND_POINTER = 144, + RICON_LASER = 145, + RICON_COIN = 146, + RICON_EXPLOSION = 147, + RICON_1UP = 148, + RICON_PLAYER = 149, + RICON_PLAYER_JUMP = 150, + RICON_KEY = 151, + RICON_DEMON = 152, + RICON_TEXT_POPUP = 153, + RICON_GEAR_EX = 154, + RICON_CRACK = 155, + RICON_CRACK_POINTS = 156, + RICON_STAR = 157, + RICON_DOOR = 158, + RICON_EXIT = 159, + RICON_MODE_2D = 160, + RICON_MODE_3D = 161, + RICON_CUBE = 162, + RICON_CUBE_FACE_TOP = 163, + RICON_CUBE_FACE_LEFT = 164, + RICON_CUBE_FACE_FRONT = 165, + RICON_CUBE_FACE_BOTTOM = 166, + RICON_CUBE_FACE_RIGHT = 167, + RICON_CUBE_FACE_BACK = 168, + RICON_CAMERA = 169, + RICON_SPECIAL = 170, + RICON_LINK_NET = 171, + RICON_LINK_BOXES = 172, + RICON_LINK_MULTI = 173, + RICON_LINK = 174, + RICON_LINK_BROKE = 175, + RICON_TEXT_NOTES = 176, + RICON_NOTEBOOK = 177, + RICON_SUITCASE = 178, + RICON_SUITCASE_ZIP = 179, + RICON_MAILBOX = 180, + RICON_MONITOR = 181, + RICON_PRINTER = 182, + RICON_PHOTO_CAMERA = 183, + RICON_PHOTO_CAMERA_FLASH = 184, + RICON_HOUSE = 185, + RICON_HEART = 186, + RICON_CORNER = 187, + RICON_VERTICAL_BARS = 188, + RICON_VERTICAL_BARS_FILL = 189, + RICON_LIFE_BARS = 190, + RICON_INFO = 191, + RICON_CROSSLINE = 192, + RICON_HELP = 193, + RICON_FILETYPE_ALPHA = 194, + RICON_FILETYPE_HOME = 195, + RICON_LAYERS_VISIBLE = 196, + RICON_LAYERS = 197, + RICON_WINDOW = 198, + RICON_HIDPI = 199, + RICON_200 = 200, + RICON_201 = 201, + RICON_202 = 202, + RICON_203 = 203, + RICON_204 = 204, + RICON_205 = 205, + RICON_206 = 206, + RICON_207 = 207, + RICON_208 = 208, + RICON_209 = 209, + RICON_210 = 210, + RICON_211 = 211, + RICON_212 = 212, + RICON_213 = 213, + RICON_214 = 214, + RICON_215 = 215, + RICON_216 = 216, + RICON_217 = 217, + RICON_218 = 218, + RICON_219 = 219, + RICON_220 = 220, + RICON_221 = 221, + RICON_222 = 222, + RICON_223 = 223, + RICON_224 = 224, + RICON_225 = 225, + RICON_226 = 226, + RICON_227 = 227, + RICON_228 = 228, + RICON_229 = 229, + RICON_230 = 230, + RICON_231 = 231, + RICON_232 = 232, + RICON_233 = 233, + RICON_234 = 234, + RICON_235 = 235, + RICON_236 = 236, + RICON_237 = 237, + RICON_238 = 238, + RICON_239 = 239, + RICON_240 = 240, + RICON_241 = 241, + RICON_242 = 242, + RICON_243 = 243, + RICON_244 = 244, + RICON_245 = 245, + RICON_246 = 246, + RICON_247 = 247, + RICON_248 = 248, + RICON_249 = 249, + RICON_250 = 250, + RICON_251 = 251, + RICON_252 = 252, + RICON_253 = 253, + RICON_254 = 254, + RICON_255 = 255, +} guiIconName; + +//---------------------------------------------------------------------------------- +// Icons data for all gui possible icons (allocated on data segment by default) +// +// NOTE 1: Every icon is codified in binary form, using 1 bit per pixel, so, +// every 16x16 icon requires 8 integers (16*16/32) to be stored +// +// NOTE 2: A new icon set could be loaded over this array using GuiLoadIcons(), +// but loaded icons set must be same RICON_SIZE and no more than RICON_MAX_ICONS +// +// guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +//---------------------------------------------------------------------------------- +static unsigned int guiIcons[RICON_MAX_ICONS*RICON_DATA_ELEMENTS] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_NONE + 0x3ff80000, 0x2f082008, 0x2042207e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x00007ffe, // RICON_FOLDER_FILE_OPEN + 0x3ffe0000, 0x44226422, 0x400247e2, 0x5ffa4002, 0x57ea500a, 0x500a500a, 0x40025ffa, 0x00007ffe, // RICON_FILE_SAVE_CLASSIC + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024002, 0x44424282, 0x793e4102, 0x00000100, // RICON_FOLDER_OPEN + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024102, 0x44424102, 0x793e4282, 0x00000000, // RICON_FOLDER_SAVE + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x24442284, 0x21042104, 0x20042104, 0x00003ffc, // RICON_FILE_OPEN + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x21042104, 0x22842444, 0x20042104, 0x00003ffc, // RICON_FILE_SAVE + 0x3ff00000, 0x201c2010, 0x00042004, 0x20041004, 0x20844784, 0x00841384, 0x20042784, 0x00003ffc, // RICON_FILE_EXPORT + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x22042204, 0x22042f84, 0x20042204, 0x00003ffc, // RICON_FILE_NEW + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x25042884, 0x25042204, 0x20042884, 0x00003ffc, // RICON_FILE_DELETE + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // RICON_FILETYPE_TEXT + 0x3ff00000, 0x201c2010, 0x27042004, 0x244424c4, 0x26442444, 0x20642664, 0x20042004, 0x00003ffc, // RICON_FILETYPE_AUDIO + 0x3ff00000, 0x201c2010, 0x26042604, 0x20042004, 0x35442884, 0x2414222c, 0x20042004, 0x00003ffc, // RICON_FILETYPE_IMAGE + 0x3ff00000, 0x201c2010, 0x20c42004, 0x22442144, 0x22442444, 0x20c42144, 0x20042004, 0x00003ffc, // RICON_FILETYPE_PLAY + 0x3ff00000, 0x3ffc2ff0, 0x3f3c2ff4, 0x3dbc2eb4, 0x3dbc2bb4, 0x3f3c2eb4, 0x3ffc2ff4, 0x00002ff4, // RICON_FILETYPE_VIDEO + 0x3ff00000, 0x201c2010, 0x21842184, 0x21842004, 0x21842184, 0x21842184, 0x20042184, 0x00003ffc, // RICON_FILETYPE_INFO + 0x0ff00000, 0x381c0810, 0x28042804, 0x28042804, 0x28042804, 0x28042804, 0x20102ffc, 0x00003ff0, // RICON_FILE_COPY + 0x00000000, 0x701c0000, 0x079c1e14, 0x55a000f0, 0x079c00f0, 0x701c1e14, 0x00000000, 0x00000000, // RICON_FILE_CUT + 0x01c00000, 0x13e41bec, 0x3f841004, 0x204420c4, 0x20442044, 0x20442044, 0x207c2044, 0x00003fc0, // RICON_FILE_PASTE + 0x00000000, 0x3aa00fe0, 0x2abc2aa0, 0x2aa42aa4, 0x20042aa4, 0x20042004, 0x3ffc2004, 0x00000000, // RICON_CURSOR_HAND + 0x00000000, 0x003c000c, 0x030800c8, 0x30100c10, 0x10202020, 0x04400840, 0x01800280, 0x00000000, // RICON_CURSOR_POINTER + 0x00000000, 0x00180000, 0x01f00078, 0x03e007f0, 0x07c003e0, 0x04000e40, 0x00000000, 0x00000000, // RICON_CURSOR_CLASSIC + 0x00000000, 0x04000000, 0x11000a00, 0x04400a80, 0x01100220, 0x00580088, 0x00000038, 0x00000000, // RICON_PENCIL + 0x04000000, 0x15000a00, 0x50402880, 0x14102820, 0x05040a08, 0x015c028c, 0x007c00bc, 0x00000000, // RICON_PENCIL_BIG + 0x01c00000, 0x01400140, 0x01400140, 0x0ff80140, 0x0ff80808, 0x0aa80808, 0x0aa80aa8, 0x00000ff8, // RICON_BRUSH_CLASSIC + 0x1ffc0000, 0x5ffc7ffe, 0x40004000, 0x00807f80, 0x01c001c0, 0x01c001c0, 0x01c001c0, 0x00000080, // RICON_BRUSH_PAINTER + 0x00000000, 0x00800000, 0x01c00080, 0x03e001c0, 0x07f003e0, 0x036006f0, 0x000001c0, 0x00000000, // RICON_WATER_DROP + 0x00000000, 0x3e003800, 0x1f803f80, 0x0c201e40, 0x02080c10, 0x00840104, 0x00380044, 0x00000000, // RICON_COLOR_PICKER + 0x00000000, 0x07800300, 0x1fe00fc0, 0x3f883fd0, 0x0e021f04, 0x02040402, 0x00f00108, 0x00000000, // RICON_RUBBER + 0x00c00000, 0x02800140, 0x08200440, 0x20081010, 0x2ffe3004, 0x03f807fc, 0x00e001f0, 0x00000040, // RICON_COLOR_BUCKET + 0x00000000, 0x21843ffc, 0x01800180, 0x01800180, 0x01800180, 0x01800180, 0x03c00180, 0x00000000, // RICON_TEXT_T + 0x00800000, 0x01400180, 0x06200340, 0x0c100620, 0x1ff80c10, 0x380c1808, 0x70067004, 0x0000f80f, // RICON_TEXT_A + 0x78000000, 0x50004000, 0x00004800, 0x03c003c0, 0x03c003c0, 0x00100000, 0x0002000a, 0x0000000e, // RICON_SCALE + 0x75560000, 0x5e004002, 0x54001002, 0x41001202, 0x408200fe, 0x40820082, 0x40820082, 0x00006afe, // RICON_RESIZE + 0x00000000, 0x3f003f00, 0x3f003f00, 0x3f003f00, 0x00400080, 0x001c0020, 0x001c001c, 0x00000000, // RICON_FILTER_POINT + 0x6d800000, 0x00004080, 0x40804080, 0x40800000, 0x00406d80, 0x001c0020, 0x001c001c, 0x00000000, // RICON_FILTER_BILINEAR + 0x40080000, 0x1ffe2008, 0x14081008, 0x11081208, 0x10481088, 0x10081028, 0x10047ff8, 0x00001002, // RICON_CROP + 0x00100000, 0x3ffc0010, 0x2ab03550, 0x22b02550, 0x20b02150, 0x20302050, 0x2000fff0, 0x00002000, // RICON_CROP_ALPHA + 0x40000000, 0x1ff82000, 0x04082808, 0x01082208, 0x00482088, 0x00182028, 0x35542008, 0x00000002, // RICON_SQUARE_TOGGLE + 0x00000000, 0x02800280, 0x06c006c0, 0x0ea00ee0, 0x1e901eb0, 0x3e883e98, 0x7efc7e8c, 0x00000000, // RICON_SIMMETRY + 0x01000000, 0x05600100, 0x1d480d50, 0x7d423d44, 0x3d447d42, 0x0d501d48, 0x01000560, 0x00000100, // RICON_SIMMETRY_HORIZONTAL + 0x01800000, 0x04200240, 0x10080810, 0x00001ff8, 0x00007ffe, 0x0ff01ff8, 0x03c007e0, 0x00000180, // RICON_SIMMETRY_VERTICAL + 0x00000000, 0x010800f0, 0x02040204, 0x02040204, 0x07f00308, 0x1c000e00, 0x30003800, 0x00000000, // RICON_LENS + 0x00000000, 0x061803f0, 0x08240c0c, 0x08040814, 0x0c0c0804, 0x23f01618, 0x18002400, 0x00000000, // RICON_LENS_BIG + 0x00000000, 0x00000000, 0x1c7007c0, 0x638e3398, 0x1c703398, 0x000007c0, 0x00000000, 0x00000000, // RICON_EYE_ON + 0x00000000, 0x10002000, 0x04700fc0, 0x610e3218, 0x1c703098, 0x001007a0, 0x00000008, 0x00000000, // RICON_EYE_OFF + 0x00000000, 0x00007ffc, 0x40047ffc, 0x10102008, 0x04400820, 0x02800280, 0x02800280, 0x00000100, // RICON_FILTER_TOP + 0x00000000, 0x40027ffe, 0x10082004, 0x04200810, 0x02400240, 0x02400240, 0x01400240, 0x000000c0, // RICON_FILTER + 0x00800000, 0x00800080, 0x00000080, 0x3c9e0000, 0x00000000, 0x00800080, 0x00800080, 0x00000000, // RICON_TARGET_POINT + 0x00800000, 0x00800080, 0x00800080, 0x3f7e01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // RICON_TARGET_SMALL + 0x00800000, 0x00800080, 0x03e00080, 0x3e3e0220, 0x03e00220, 0x00800080, 0x00800080, 0x00000000, // RICON_TARGET_BIG + 0x01000000, 0x04400280, 0x01000100, 0x43842008, 0x43849ab2, 0x01002008, 0x04400100, 0x01000280, // RICON_TARGET_MOVE + 0x01000000, 0x04400280, 0x01000100, 0x41042108, 0x41049ff2, 0x01002108, 0x04400100, 0x01000280, // RICON_CURSOR_MOVE + 0x781e0000, 0x500a4002, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x4002500a, 0x0000781e, // RICON_CURSOR_SCALE + 0x00000000, 0x20003c00, 0x24002800, 0x01000200, 0x00400080, 0x00140024, 0x003c0004, 0x00000000, // RICON_CURSOR_SCALE_RIGHT + 0x00000000, 0x0004003c, 0x00240014, 0x00800040, 0x02000100, 0x28002400, 0x3c002000, 0x00000000, // RICON_CURSOR_SCALE_LEFT + 0x00000000, 0x00100020, 0x10101fc8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // RICON_UNDO + 0x00000000, 0x08000400, 0x080813f8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // RICON_REDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3f902020, 0x00400020, 0x00000000, // RICON_REREDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3fc82010, 0x00200010, 0x00000000, // RICON_MUTATE + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18101020, 0x00100fc8, 0x00000020, // RICON_ROTATE + 0x00000000, 0x04000200, 0x240429fc, 0x20042204, 0x20442004, 0x3f942024, 0x00400020, 0x00000000, // RICON_REPEAT + 0x00000000, 0x20001000, 0x22104c0e, 0x00801120, 0x11200040, 0x4c0e2210, 0x10002000, 0x00000000, // RICON_SHUFFLE + 0x7ffe0000, 0x50024002, 0x44024802, 0x41024202, 0x40424082, 0x40124022, 0x4002400a, 0x00007ffe, // RICON_EMPTYBOX + 0x00800000, 0x03e00080, 0x08080490, 0x3c9e0808, 0x08080808, 0x03e00490, 0x00800080, 0x00000000, // RICON_TARGET + 0x00800000, 0x00800080, 0x00800080, 0x3ffe01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // RICON_TARGET_SMALL_FILL + 0x00800000, 0x00800080, 0x03e00080, 0x3ffe03e0, 0x03e003e0, 0x00800080, 0x00800080, 0x00000000, // RICON_TARGET_BIG_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x638c2008, 0x638cfbbe, 0x01002008, 0x07c00100, 0x01000380, // RICON_TARGET_MOVE_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x610c2108, 0x610cfffe, 0x01002108, 0x07c00100, 0x01000380, // RICON_CURSOR_MOVE_FILL + 0x781e0000, 0x6006700e, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x700e6006, 0x0000781e, // RICON_CURSOR_SCALE_FILL + 0x00000000, 0x38003c00, 0x24003000, 0x01000200, 0x00400080, 0x000c0024, 0x003c001c, 0x00000000, // RICON_CURSOR_SCALE_RIGHT + 0x00000000, 0x001c003c, 0x0024000c, 0x00800040, 0x02000100, 0x30002400, 0x3c003800, 0x00000000, // RICON_CURSOR_SCALE_LEFT + 0x00000000, 0x00300020, 0x10301ff8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // RICON_UNDO_FILL + 0x00000000, 0x0c000400, 0x0c081ff8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // RICON_REDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3ff02060, 0x00400060, 0x00000000, // RICON_REREDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3ff82030, 0x00200030, 0x00000000, // RICON_MUTATE_FILL + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18301020, 0x00300ff8, 0x00000020, // RICON_ROTATE_FILL + 0x00000000, 0x06000200, 0x26042ffc, 0x20042204, 0x20442004, 0x3ff42064, 0x00400060, 0x00000000, // RICON_REPEAT_FILL + 0x00000000, 0x30001000, 0x32107c0e, 0x00801120, 0x11200040, 0x7c0e3210, 0x10003000, 0x00000000, // RICON_SHUFFLE_FILL + 0x00000000, 0x30043ffc, 0x24042804, 0x21042204, 0x20442084, 0x20142024, 0x3ffc200c, 0x00000000, // RICON_EMPTYBOX_SMALL + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // RICON_BOX + 0x00000000, 0x23c43ffc, 0x23c423c4, 0x200423c4, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // RICON_BOX_TOP + 0x00000000, 0x3e043ffc, 0x3e043e04, 0x20043e04, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // RICON_BOX_TOP_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x3e043e04, 0x3e043e04, 0x20042004, 0x3ffc2004, 0x00000000, // RICON_BOX_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x3e042004, 0x3e043e04, 0x3ffc3e04, 0x00000000, // RICON_BOX_BOTTOM_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x23c42004, 0x23c423c4, 0x3ffc23c4, 0x00000000, // RICON_BOX_BOTTOM + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x207c2004, 0x207c207c, 0x3ffc207c, 0x00000000, // RICON_BOX_BOTTOM_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x207c207c, 0x207c207c, 0x20042004, 0x3ffc2004, 0x00000000, // RICON_BOX_LEFT + 0x00000000, 0x207c3ffc, 0x207c207c, 0x2004207c, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // RICON_BOX_TOP_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x23c423c4, 0x23c423c4, 0x20042004, 0x3ffc2004, 0x00000000, // RICON_BOX_CIRCLE_MASK + 0x7ffe0000, 0x40024002, 0x47e24182, 0x4ff247e2, 0x47e24ff2, 0x418247e2, 0x40024002, 0x00007ffe, // RICON_BOX_CENTER + 0x7fff0000, 0x40014001, 0x40014001, 0x49555ddd, 0x4945495d, 0x400149c5, 0x40014001, 0x00007fff, // RICON_POT + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x404e40ce, 0x48125432, 0x4006540e, 0x00007ffe, // RICON_ALPHA_MULTIPLY + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x5c4e40ce, 0x44124432, 0x40065c0e, 0x00007ffe, // RICON_ALPHA_CLEAR + 0x7ffe0000, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x00007ffe, // RICON_DITHERING + 0x07fe0000, 0x1ffa0002, 0x7fea000a, 0x402a402a, 0x5b2a512a, 0x5128552a, 0x40205128, 0x00007fe0, // RICON_MIPMAPS + 0x00000000, 0x1ff80000, 0x12481248, 0x12481ff8, 0x1ff81248, 0x12481248, 0x00001ff8, 0x00000000, // RICON_BOX_GRID + 0x12480000, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x00001248, // RICON_GRID + 0x00000000, 0x1c380000, 0x1c3817e8, 0x08100810, 0x08100810, 0x17e81c38, 0x00001c38, 0x00000000, // RICON_BOX_CORNERS_SMALL + 0x700e0000, 0x700e5ffa, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x5ffa700e, 0x0000700e, // RICON_BOX_CORNERS_BIG + 0x3f7e0000, 0x21422142, 0x21422142, 0x00003f7e, 0x21423f7e, 0x21422142, 0x3f7e2142, 0x00000000, // RICON_FOUR_BOXES + 0x00000000, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x00000000, // RICON_GRID_FILL + 0x7ffe0000, 0x7ffe7ffe, 0x77fe7000, 0x77fe77fe, 0x777e7700, 0x777e777e, 0x777e777e, 0x0000777e, // RICON_BOX_MULTISIZE + 0x781e0000, 0x40024002, 0x00004002, 0x01800000, 0x00000180, 0x40020000, 0x40024002, 0x0000781e, // RICON_ZOOM_SMALL + 0x781e0000, 0x40024002, 0x00004002, 0x03c003c0, 0x03c003c0, 0x40020000, 0x40024002, 0x0000781e, // RICON_ZOOM_MEDIUM + 0x781e0000, 0x40024002, 0x07e04002, 0x07e007e0, 0x07e007e0, 0x400207e0, 0x40024002, 0x0000781e, // RICON_ZOOM_BIG + 0x781e0000, 0x5ffa4002, 0x1ff85ffa, 0x1ff81ff8, 0x1ff81ff8, 0x5ffa1ff8, 0x40025ffa, 0x0000781e, // RICON_ZOOM_ALL + 0x00000000, 0x2004381c, 0x00002004, 0x00000000, 0x00000000, 0x20040000, 0x381c2004, 0x00000000, // RICON_ZOOM_CENTER + 0x00000000, 0x1db80000, 0x10081008, 0x10080000, 0x00001008, 0x10081008, 0x00001db8, 0x00000000, // RICON_BOX_DOTS_SMALL + 0x35560000, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x35562002, 0x00000000, // RICON_BOX_DOTS_BIG + 0x7ffe0000, 0x40024002, 0x48124ff2, 0x49924812, 0x48124992, 0x4ff24812, 0x40024002, 0x00007ffe, // RICON_BOX_CONCENTRIC + 0x00000000, 0x10841ffc, 0x10841084, 0x1ffc1084, 0x10841084, 0x10841084, 0x00001ffc, 0x00000000, // RICON_BOX_GRID_BIG + 0x00000000, 0x00000000, 0x10000000, 0x04000800, 0x01040200, 0x00500088, 0x00000020, 0x00000000, // RICON_OK_TICK + 0x00000000, 0x10080000, 0x04200810, 0x01800240, 0x02400180, 0x08100420, 0x00001008, 0x00000000, // RICON_CROSS + 0x00000000, 0x02000000, 0x00800100, 0x00200040, 0x00200010, 0x00800040, 0x02000100, 0x00000000, // RICON_ARROW_LEFT + 0x00000000, 0x00400000, 0x01000080, 0x04000200, 0x04000800, 0x01000200, 0x00400080, 0x00000000, // RICON_ARROW_RIGHT + 0x00000000, 0x00000000, 0x00000000, 0x08081004, 0x02200410, 0x00800140, 0x00000000, 0x00000000, // RICON_ARROW_DOWN + 0x00000000, 0x00000000, 0x01400080, 0x04100220, 0x10040808, 0x00000000, 0x00000000, 0x00000000, // RICON_ARROW_UP + 0x00000000, 0x02000000, 0x03800300, 0x03e003c0, 0x03e003f0, 0x038003c0, 0x02000300, 0x00000000, // RICON_ARROW_LEFT_FILL + 0x00000000, 0x00400000, 0x01c000c0, 0x07c003c0, 0x07c00fc0, 0x01c003c0, 0x004000c0, 0x00000000, // RICON_ARROW_RIGHT_FILL + 0x00000000, 0x00000000, 0x00000000, 0x0ff81ffc, 0x03e007f0, 0x008001c0, 0x00000000, 0x00000000, // RICON_ARROW_DOWN_FILL + 0x00000000, 0x00000000, 0x01c00080, 0x07f003e0, 0x1ffc0ff8, 0x00000000, 0x00000000, 0x00000000, // RICON_ARROW_UP_FILL + 0x00000000, 0x18a008c0, 0x32881290, 0x24822686, 0x26862482, 0x12903288, 0x08c018a0, 0x00000000, // RICON_AUDIO + 0x00000000, 0x04800780, 0x004000c0, 0x662000f0, 0x08103c30, 0x130a0e18, 0x0000318e, 0x00000000, // RICON_FX + 0x00000000, 0x00800000, 0x08880888, 0x2aaa0a8a, 0x0a8a2aaa, 0x08880888, 0x00000080, 0x00000000, // RICON_WAVE + 0x00000000, 0x00600000, 0x01080090, 0x02040108, 0x42044204, 0x24022402, 0x00001800, 0x00000000, // RICON_WAVE_SINUS + 0x00000000, 0x07f80000, 0x04080408, 0x04080408, 0x04080408, 0x7c0e0408, 0x00000000, 0x00000000, // RICON_WAVE_SQUARE + 0x00000000, 0x00000000, 0x00a00040, 0x22084110, 0x08021404, 0x00000000, 0x00000000, 0x00000000, // RICON_WAVE_TRIANGULAR + 0x00000000, 0x00000000, 0x04200000, 0x01800240, 0x02400180, 0x00000420, 0x00000000, 0x00000000, // RICON_CROSS_SMALL + 0x00000000, 0x18380000, 0x12281428, 0x10a81128, 0x112810a8, 0x14281228, 0x00001838, 0x00000000, // RICON_PLAYER_PREVIOUS + 0x00000000, 0x18000000, 0x11801600, 0x10181060, 0x10601018, 0x16001180, 0x00001800, 0x00000000, // RICON_PLAYER_PLAY_BACK + 0x00000000, 0x00180000, 0x01880068, 0x18080608, 0x06081808, 0x00680188, 0x00000018, 0x00000000, // RICON_PLAYER_PLAY + 0x00000000, 0x1e780000, 0x12481248, 0x12481248, 0x12481248, 0x12481248, 0x00001e78, 0x00000000, // RICON_PLAYER_PAUSE + 0x00000000, 0x1ff80000, 0x10081008, 0x10081008, 0x10081008, 0x10081008, 0x00001ff8, 0x00000000, // RICON_PLAYER_STOP + 0x00000000, 0x1c180000, 0x14481428, 0x15081488, 0x14881508, 0x14281448, 0x00001c18, 0x00000000, // RICON_PLAYER_NEXT + 0x00000000, 0x03c00000, 0x08100420, 0x10081008, 0x10081008, 0x04200810, 0x000003c0, 0x00000000, // RICON_PLAYER_RECORD + 0x00000000, 0x0c3007e0, 0x13c81818, 0x14281668, 0x14281428, 0x1c381c38, 0x08102244, 0x00000000, // RICON_MAGNET + 0x07c00000, 0x08200820, 0x3ff80820, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // RICON_LOCK_CLOSE + 0x07c00000, 0x08000800, 0x3ff80800, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // RICON_LOCK_OPEN + 0x01c00000, 0x0c180770, 0x3086188c, 0x60832082, 0x60034781, 0x30062002, 0x0c18180c, 0x01c00770, // RICON_CLOCK + 0x0a200000, 0x1b201b20, 0x04200e20, 0x04200420, 0x04700420, 0x0e700e70, 0x0e700e70, 0x04200e70, // RICON_TOOLS + 0x01800000, 0x3bdc318c, 0x0ff01ff8, 0x7c3e1e78, 0x1e787c3e, 0x1ff80ff0, 0x318c3bdc, 0x00000180, // RICON_GEAR + 0x01800000, 0x3ffc318c, 0x1c381ff8, 0x781e1818, 0x1818781e, 0x1ff81c38, 0x318c3ffc, 0x00000180, // RICON_GEAR_BIG + 0x00000000, 0x08080ff8, 0x08081ffc, 0x0aa80aa8, 0x0aa80aa8, 0x0aa80aa8, 0x08080aa8, 0x00000ff8, // RICON_BIN + 0x00000000, 0x00000000, 0x20043ffc, 0x08043f84, 0x04040f84, 0x04040784, 0x000007fc, 0x00000000, // RICON_HAND_POINTER + 0x00000000, 0x24400400, 0x00001480, 0x6efe0e00, 0x00000e00, 0x24401480, 0x00000400, 0x00000000, // RICON_LASER + 0x00000000, 0x03c00000, 0x08300460, 0x11181118, 0x11181118, 0x04600830, 0x000003c0, 0x00000000, // RICON_COIN + 0x00000000, 0x10880080, 0x06c00810, 0x366c07e0, 0x07e00240, 0x00001768, 0x04200240, 0x00000000, // RICON_EXPLOSION + 0x00000000, 0x3d280000, 0x2528252c, 0x3d282528, 0x05280528, 0x05e80528, 0x00000000, 0x00000000, // RICON_1UP + 0x01800000, 0x03c003c0, 0x018003c0, 0x0ff007e0, 0x0bd00bd0, 0x0a500bd0, 0x02400240, 0x02400240, // RICON_PLAYER + 0x01800000, 0x03c003c0, 0x118013c0, 0x03c81ff8, 0x07c003c8, 0x04400440, 0x0c080478, 0x00000000, // RICON_PLAYER_JUMP + 0x3ff80000, 0x30183ff8, 0x30183018, 0x3ff83ff8, 0x03000300, 0x03c003c0, 0x03e00300, 0x000003e0, // RICON_KEY + 0x3ff80000, 0x3ff83ff8, 0x33983ff8, 0x3ff83398, 0x3ff83ff8, 0x00000540, 0x0fe00aa0, 0x00000fe0, // RICON_DEMON + 0x00000000, 0x0ff00000, 0x20041008, 0x25442004, 0x10082004, 0x06000bf0, 0x00000300, 0x00000000, // RICON_TEXT_POPUP + 0x00000000, 0x11440000, 0x07f00be8, 0x1c1c0e38, 0x1c1c0c18, 0x07f00e38, 0x11440be8, 0x00000000, // RICON_GEAR_EX + 0x00000000, 0x20080000, 0x0c601010, 0x07c00fe0, 0x07c007c0, 0x0c600fe0, 0x20081010, 0x00000000, // RICON_CRACK + 0x00000000, 0x20080000, 0x0c601010, 0x04400fe0, 0x04405554, 0x0c600fe0, 0x20081010, 0x00000000, // RICON_CRACK_POINTS + 0x00000000, 0x00800080, 0x01c001c0, 0x1ffc3ffe, 0x03e007f0, 0x07f003e0, 0x0c180770, 0x00000808, // RICON_STAR + 0x0ff00000, 0x08180810, 0x08100818, 0x0a100810, 0x08180810, 0x08100818, 0x08100810, 0x00001ff8, // RICON_DOOR + 0x0ff00000, 0x08100810, 0x08100810, 0x10100010, 0x4f902010, 0x10102010, 0x08100010, 0x00000ff0, // RICON_EXIT + 0x00040000, 0x001f000e, 0x0ef40004, 0x12f41284, 0x0ef41214, 0x10040004, 0x7ffc3004, 0x10003000, // RICON_MODE_2D + 0x78040000, 0x501f600e, 0x0ef44004, 0x12f41284, 0x0ef41284, 0x10140004, 0x7ffc300c, 0x10003000, // RICON_MODE_3D + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // RICON_CUBE + 0x7fe00000, 0x5ff87ff0, 0x47fe4ffc, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // RICON_CUBE_FACE_TOP + 0x7fe00000, 0x50386030, 0x47fe483c, 0x443e443e, 0x443e443e, 0x241e75fe, 0x0c06140e, 0x000007fe, // RICON_CUBE_FACE_LEFT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x47fe47fe, 0x47fe47fe, 0x27fe77fe, 0x0ffe17fe, 0x000007fe, // RICON_CUBE_FACE_FRONT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x3ff27fe2, 0x0ffe1ffa, 0x000007fe, // RICON_CUBE_FACE_BOTTOM + 0x7fe00000, 0x70286030, 0x7ffe7804, 0x7c227c02, 0x7c227c22, 0x3c127de2, 0x0c061c0a, 0x000007fe, // RICON_CUBE_FACE_RIGHT + 0x7fe00000, 0x7fe87ff0, 0x7ffe7fe4, 0x7fe27fe2, 0x7fe27fe2, 0x24127fe2, 0x0c06140a, 0x000007fe, // RICON_CUBE_FACE_BACK + 0x00000000, 0x2a0233fe, 0x22022602, 0x22022202, 0x2a022602, 0x00a033fe, 0x02080110, 0x00000000, // RICON_CAMERA + 0x00000000, 0x200c3ffc, 0x000c000c, 0x3ffc000c, 0x30003000, 0x30003000, 0x3ffc3004, 0x00000000, // RICON_SPECIAL + 0x00000000, 0x0022003e, 0x012201e2, 0x0100013e, 0x01000100, 0x79000100, 0x4f004900, 0x00007800, // RICON_LINK_NET + 0x00000000, 0x44007c00, 0x45004600, 0x00627cbe, 0x00620022, 0x45007cbe, 0x44004600, 0x00007c00, // RICON_LINK_BOXES + 0x00000000, 0x0044007c, 0x0010007c, 0x3f100010, 0x3f1021f0, 0x3f100010, 0x3f0021f0, 0x00000000, // RICON_LINK_MULTI + 0x00000000, 0x0044007c, 0x00440044, 0x0010007c, 0x00100010, 0x44107c10, 0x440047f0, 0x00007c00, // RICON_LINK + 0x00000000, 0x0044007c, 0x00440044, 0x0000007c, 0x00000010, 0x44007c10, 0x44004550, 0x00007c00, // RICON_LINK_BROKE + 0x02a00000, 0x22a43ffc, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // RICON_TEXT_NOTES + 0x3ffc0000, 0x20042004, 0x245e27c4, 0x27c42444, 0x2004201e, 0x201e2004, 0x20042004, 0x00003ffc, // RICON_NOTEBOOK + 0x00000000, 0x07e00000, 0x04200420, 0x24243ffc, 0x24242424, 0x24242424, 0x3ffc2424, 0x00000000, // RICON_SUITCASE + 0x00000000, 0x0fe00000, 0x08200820, 0x40047ffc, 0x7ffc5554, 0x40045554, 0x7ffc4004, 0x00000000, // RICON_SUITCASE_ZIP + 0x00000000, 0x20043ffc, 0x3ffc2004, 0x13c81008, 0x100813c8, 0x10081008, 0x1ff81008, 0x00000000, // RICON_MAILBOX + 0x00000000, 0x40027ffe, 0x5ffa5ffa, 0x5ffa5ffa, 0x40025ffa, 0x03c07ffe, 0x1ff81ff8, 0x00000000, // RICON_MONITOR + 0x0ff00000, 0x6bfe7ffe, 0x7ffe7ffe, 0x68167ffe, 0x08106816, 0x08100810, 0x0ff00810, 0x00000000, // RICON_PRINTER + 0x3ff80000, 0xfffe2008, 0x870a8002, 0x904a888a, 0x904a904a, 0x870a888a, 0xfffe8002, 0x00000000, // RICON_PHOTO_CAMERA + 0x0fc00000, 0xfcfe0cd8, 0x8002fffe, 0x84428382, 0x84428442, 0x80028382, 0xfffe8002, 0x00000000, // RICON_PHOTO_CAMERA_FLASH + 0x00000000, 0x02400180, 0x08100420, 0x20041008, 0x23c42004, 0x22442244, 0x3ffc2244, 0x00000000, // RICON_HOUSE + 0x00000000, 0x1c700000, 0x3ff83ef8, 0x3ff83ff8, 0x0fe01ff0, 0x038007c0, 0x00000100, 0x00000000, // RICON_HEART + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0xe000c000, // RICON_CORNER + 0x00000000, 0x14001c00, 0x15c01400, 0x15401540, 0x155c1540, 0x15541554, 0x1ddc1554, 0x00000000, // RICON_VERTICAL_BARS + 0x00000000, 0x03000300, 0x1b001b00, 0x1b601b60, 0x1b6c1b60, 0x1b6c1b6c, 0x1b6c1b6c, 0x00000000, // RICON_VERTICAL_BARS_FILL + 0x00000000, 0x00000000, 0x403e7ffe, 0x7ffe403e, 0x7ffe0000, 0x43fe43fe, 0x00007ffe, 0x00000000, // RICON_LIFE_BARS + 0x7ffc0000, 0x43844004, 0x43844284, 0x43844004, 0x42844284, 0x42844284, 0x40044384, 0x00007ffc, // RICON_INFO + 0x40008000, 0x10002000, 0x04000800, 0x01000200, 0x00400080, 0x00100020, 0x00040008, 0x00010002, // RICON_CROSSLINE + 0x00000000, 0x1ff01ff0, 0x18301830, 0x1f001830, 0x03001f00, 0x00000300, 0x03000300, 0x00000000, // RICON_HELP + 0x3ff00000, 0x2abc3550, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x00003ffc, // RICON_FILETYPE_ALPHA + 0x3ff00000, 0x201c2010, 0x22442184, 0x28142424, 0x29942814, 0x2ff42994, 0x20042004, 0x00003ffc, // RICON_FILETYPE_HOME + 0x07fe0000, 0x04020402, 0x7fe20402, 0x44224422, 0x44224422, 0x402047fe, 0x40204020, 0x00007fe0, // RICON_LAYERS_VISIBLE + 0x07fe0000, 0x04020402, 0x7c020402, 0x44024402, 0x44024402, 0x402047fe, 0x40204020, 0x00007fe0, // RICON_LAYERS + 0x00000000, 0x40027ffe, 0x7ffe4002, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // RICON_WINDOW + 0x09100000, 0x09f00910, 0x09100910, 0x00000910, 0x24a2779e, 0x27a224a2, 0x709e20a2, 0x00000000, // RICON_HIDPI + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_200 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_201 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_202 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_203 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_204 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_205 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_206 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_207 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_208 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_209 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_210 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_211 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_212 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_213 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_214 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_215 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_216 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_217 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_218 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_219 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_220 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_221 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_222 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_223 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_224 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_225 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_226 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_227 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_228 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_229 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_230 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_231 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_232 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_233 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_234 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_235 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_236 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_237 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_238 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_239 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_240 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_241 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_242 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_243 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_244 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_245 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_246 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_247 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_248 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_249 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_250 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_251 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_252 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_253 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_254 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // RICON_255 +}; + +#endif // RAYGUI_CUSTOM_RICONS + +#endif // !RAYGUI_NO_RICONS + +#ifndef RICON_SIZE + #define RICON_SIZE 0 +#endif + +#define RAYGUI_MAX_CONTROLS 16 // Maximum number of standard controls +#define RAYGUI_MAX_PROPS_BASE 16 // Maximum number of standard properties +#define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Gui control property style color element +typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static GuiControlState guiState = GUI_STATE_NORMAL; + +static Font guiFont = { 0 }; // Gui current font (WARNING: highly coupled to raylib) +static bool guiLocked = false; // Gui lock state (no inputs processed) +static float guiAlpha = 1.0f; // Gui element transpacency on drawing + +//---------------------------------------------------------------------------------- +// Style data array for all gui style properties (allocated on data segment by default) +// +// NOTE 1: First set of BASE properties are generic to all controls but could be individually +// overwritten per control, first set of EXTENDED properties are generic to all controls and +// can not be overwritten individually but custom EXTENDED properties can be used by control +// +// NOTE 2: A new style set could be loaded over this array using GuiLoadStyle(), +// but default gui style could always be recovered with GuiLoadStyleDefault() +// +// guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +//---------------------------------------------------------------------------------- +static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)] = { 0 }; + +static bool guiStyleLoaded = false; // Style loaded flag for lazy style initialization + +//---------------------------------------------------------------------------------- +// Standalone Mode Functions Declaration +// +// NOTE: raygui depend on some raylib input and drawing functions +// To use raygui as standalone library, below functions must be defined by the user +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + +#define KEY_RIGHT 262 +#define KEY_LEFT 263 +#define KEY_DOWN 264 +#define KEY_UP 265 +#define KEY_BACKSPACE 259 +#define KEY_ENTER 257 + +#define MOUSE_LEFT_BUTTON 0 + +// Input required functions +//------------------------------------------------------------------------------- +static Vector2 GetMousePosition(void); +static float GetMouseWheelMove(void); +static bool IsMouseButtonDown(int button); +static bool IsMouseButtonPressed(int button); +static bool IsMouseButtonReleased(int button); + +static bool IsKeyDown(int key); +static bool IsKeyPressed(int key); +static int GetCharPressed(void); // -- GuiTextBox(), GuiTextBoxMulti(), GuiValueBox() +//------------------------------------------------------------------------------- + +// Drawing required functions +//------------------------------------------------------------------------------- +static void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle(), GuiDrawIcon() + +static void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() +//------------------------------------------------------------------------------- + +// Text required functions +//------------------------------------------------------------------------------- +static Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int glyphCount); // -- GuiLoadStyle() +static Font GetFontDefault(void); // -- GuiLoadStyleDefault() +static Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle() +static void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle() +static char *LoadFileText(const char *fileName); // -- GuiLoadStyle() +static const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle() + +static Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // -- GetTextWidth(), GuiTextBoxMulti() +static void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // -- GuiDrawText() +//------------------------------------------------------------------------------- + +// raylib functions already implemented in raygui +//------------------------------------------------------------------------------- +static Color GetColor(int hexValue); // Returns a Color struct from hexadecimal value +static int ColorToInt(Color color); // Returns hexadecimal value for a Color +static Color Fade(Color color, float alpha); // Color fade-in or fade-out, alpha goes from 0.0f to 1.0f +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' +static const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +static int TextToInteger(const char *text); // Get integer value from text +static int GetCodepoint(const char *text, int *bytesProcessed); // Get next codepoint in a UTF-8 encoded text +static const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode codepoint into UTF-8 text (char array size returned as parameter) + +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2); // Draw rectangle vertical gradient +//------------------------------------------------------------------------------- + +#endif // RAYGUI_STANDALONE + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static int GetTextWidth(const char *text); // Gui get text width using default font +static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds +static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor + +static void GuiDrawText(const char *text, Rectangle bounds, int alignment, Color tint); // Gui draw text using default font +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color); // Gui draw rectangle using default raygui style + +static const char **GuiTextSplit(const char *text, int *count, int *textRow); // Split controls text into multiple strings +static Vector3 ConvertHSVtoRGB(Vector3 hsv); // Convert color data from HSV to RGB +static Vector3 ConvertRGBtoHSV(Vector3 rgb); // Convert color data from RGB to HSV + +//---------------------------------------------------------------------------------- +// Gui Setup Functions Definition +//---------------------------------------------------------------------------------- +// Enable gui global state +void GuiEnable(void) { guiState = GUI_STATE_NORMAL; } + +// Disable gui global state +void GuiDisable(void) { guiState = GUI_STATE_DISABLED; } + +// Lock gui global state +void GuiLock(void) { guiLocked = true; } + +// Unlock gui global state +void GuiUnlock(void) { guiLocked = false; } + +// Check if gui is locked (global state) +bool GuiIsLocked(void) { return guiLocked; } + +// Set gui controls alpha global state +void GuiFade(float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + guiAlpha = alpha; +} + +// Set gui state (global state) +void GuiSetState(int state) { guiState = (GuiControlState)state; } + +// Get gui state (global state) +int GuiGetState(void) { return guiState; } + +// Set custom gui font +// NOTE: Font loading/unloading is external to raygui +void GuiSetFont(Font font) +{ + if (font.texture.id > 0) + { + // NOTE: If we try to setup a font but default style has not been + // lazily loaded before, it will be overwritten, so we need to force + // default style loading first + if (!guiStyleLoaded) GuiLoadStyleDefault(); + + guiFont = font; + GuiSetStyle(DEFAULT, TEXT_SIZE, font.baseSize); + } +} + +// Get custom gui font +Font GuiGetFont(void) +{ + return guiFont; +} + +// Set control style property value +void GuiSetStyle(int control, int property, int value) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + + // Default properties are propagated to all controls + if ((control == 0) && (property < RAYGUI_MAX_PROPS_BASE)) + { + for (int i = 1; i < RAYGUI_MAX_CONTROLS; i++) guiStyle[i*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + } +} + +// Get control style property value +int GuiGetStyle(int control, int property) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + return guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property]; +} + +//---------------------------------------------------------------------------------- +// Gui Controls Functions Definition +//---------------------------------------------------------------------------------- + +// Window Box control +bool GuiWindowBox(Rectangle bounds, const char *title) +{ + // NOTE: This define is also used by GuiMessageBox() and GuiTextInputBox() + #define WINDOW_STATUSBAR_HEIGHT 22 + + //GuiControlState state = guiState; + bool clicked = false; + + int statusBarHeight = WINDOW_STATUSBAR_HEIGHT + 2*GuiGetStyle(STATUSBAR, BORDER_WIDTH); + statusBarHeight += (statusBarHeight%2); + + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; + if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; + + Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight }; + Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - 20, + statusBar.y + statusBarHeight/2.0f - 18.0f/2.0f, 18, 18 }; + + // Update control + //-------------------------------------------------------------------- + // NOTE: Logic is directly managed by button + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiStatusBar(statusBar, title); // Draw window header as status bar + GuiPanel(windowPanel); // Draw window base + + // Draw window close button + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); +#if defined(RAYGUI_NO_RICONS) + clicked = GuiButton(closeButtonRec, "x"); +#else + clicked = GuiButton(closeButtonRec, GuiIconText(RICON_CROSS_SMALL, NULL)); +#endif + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); + //-------------------------------------------------------------------- + + return clicked; +} + +// Group Box control with text name +void GuiGroupBox(Rectangle bounds, const char *text) +{ + #define GROUPBOX_LINE_THICK 1 + #define GROUPBOX_TEXT_PADDING 10 + + GuiControlState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, Fade(GetColor(GuiGetStyle(DEFAULT, (state == GUI_STATE_DISABLED)? BORDER_COLOR_DISABLED : LINE_COLOR)), guiAlpha)); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, GROUPBOX_LINE_THICK }, 0, BLANK, Fade(GetColor(GuiGetStyle(DEFAULT, (state == GUI_STATE_DISABLED)? BORDER_COLOR_DISABLED : LINE_COLOR)), guiAlpha)); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y, GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, Fade(GetColor(GuiGetStyle(DEFAULT, (state == GUI_STATE_DISABLED)? BORDER_COLOR_DISABLED : LINE_COLOR)), guiAlpha)); + + GuiLine(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, bounds.width, 1 }, text); + //-------------------------------------------------------------------- +} + +// Line control +void GuiLine(Rectangle bounds, const char *text) +{ + #define LINE_TEXT_PADDING 10 + + GuiControlState state = guiState; + + Color color = Fade(GetColor(GuiGetStyle(DEFAULT, (state == GUI_STATE_DISABLED)? BORDER_COLOR_DISABLED : LINE_COLOR)), guiAlpha); + + // Draw control + //-------------------------------------------------------------------- + if (text == NULL) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, bounds.width, 1 }, 0, BLANK, color); + else + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(text); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + LINE_TEXT_PADDING; + textBounds.y = bounds.y - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + // Draw line with embedded text label: "--- text --------------" + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, LINE_TEXT_PADDING - 2, 1 }, 0, BLANK, color); + GuiLabel(textBounds, text); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + LINE_TEXT_PADDING + textBounds.width + 4, bounds.y, bounds.width - textBounds.width - LINE_TEXT_PADDING - 4, 1 }, 0, BLANK, color); + } + //-------------------------------------------------------------------- +} + +// Panel control +void GuiPanel(Rectangle bounds) +{ + #define PANEL_BORDER_WIDTH 1 + + GuiControlState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, PANEL_BORDER_WIDTH, Fade(GetColor(GuiGetStyle(DEFAULT, (state == GUI_STATE_DISABLED)? BORDER_COLOR_DISABLED: LINE_COLOR)), guiAlpha), + Fade(GetColor(GuiGetStyle(DEFAULT, (state == GUI_STATE_DISABLED)? BASE_COLOR_DISABLED : BACKGROUND_COLOR)), guiAlpha)); + //-------------------------------------------------------------------- +} + +// Scroll Panel control +Rectangle GuiScrollPanel(Rectangle bounds, Rectangle content, Vector2 *scroll) +{ + GuiControlState state = guiState; + + Vector2 scrollPos = { 0.0f, 0.0f }; + if (scroll != NULL) scrollPos = *scroll; + + bool hasHorizontalScrollBar = (content.width > bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + bool hasVerticalScrollBar = (content.height > bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + + // Recheck to account for the other scrollbar being visible + if (!hasHorizontalScrollBar) hasHorizontalScrollBar = (hasVerticalScrollBar && (content.width > (bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + if (!hasVerticalScrollBar) hasVerticalScrollBar = (hasHorizontalScrollBar && (content.height > (bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + + const int horizontalScrollBarWidth = hasHorizontalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + const int verticalScrollBarWidth = hasVerticalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + const Rectangle horizontalScrollBar = { (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + verticalScrollBarWidth : (float)bounds.x) + GuiGetStyle(DEFAULT, BORDER_WIDTH), (float)bounds.y + bounds.height - horizontalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH), (float)bounds.width - verticalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH), (float)horizontalScrollBarWidth }; + const Rectangle verticalScrollBar = { (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)bounds.x + bounds.width - verticalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH)), (float)bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), (float)verticalScrollBarWidth, (float)bounds.height - horizontalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) }; + + // Calculate view area (area without the scrollbars) + Rectangle view = (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? + RAYGUI_CLITERAL(Rectangle){ bounds.x + verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth } : + RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth }; + + // Clip view area to the actual content size + if (view.width > content.width) view.width = content.width; + if (view.height > content.height) view.height = content.height; + + const float horizontalMin = hasHorizontalScrollBar? ((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH); + const float horizontalMax = hasHorizontalScrollBar? content.width - bounds.width + (float)verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH) - (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)verticalScrollBarWidth : 0) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + const float verticalMin = hasVerticalScrollBar? (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + const float verticalMax = hasVerticalScrollBar? content.height - bounds.height + (float)horizontalScrollBarWidth + (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = GUI_STATE_PRESSED; + else state = GUI_STATE_FOCUSED; + + if (hasHorizontalScrollBar) + { + if (IsKeyDown(KEY_RIGHT)) scrollPos.x -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_LEFT)) scrollPos.x += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } + + if (hasVerticalScrollBar) + { + if (IsKeyDown(KEY_DOWN)) scrollPos.y -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_UP)) scrollPos.y += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } + + float wheelMove = GetMouseWheelMove(); + + // Horizontal scroll (Shift + Mouse wheel) + if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT))) scrollPos.x += wheelMove*20; + else scrollPos.y += wheelMove*20; // Vertical scroll + } + } + + // Normalize scroll values + if (scrollPos.x > -horizontalMin) scrollPos.x = -horizontalMin; + if (scrollPos.x < -horizontalMax) scrollPos.x = -horizontalMax; + if (scrollPos.y > -verticalMin) scrollPos.y = -verticalMin; + if (scrollPos.y < -verticalMax) scrollPos.y = -verticalMax; + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Save size of the scrollbar slider + const int slider = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + + // Draw horizontal scrollbar if visible + if (hasHorizontalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content width and the widget width + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth)/(int)content.width)*((int)bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth))); + scrollPos.x = (float)-GuiScrollBar(horizontalScrollBar, (int)-scrollPos.x, (int)horizontalMin, (int)horizontalMax); + } + + // Draw vertical scrollbar if visible + if (hasVerticalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content height and the widget height + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth)/(int)content.height)*((int)bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth))); + scrollPos.y = (float)-GuiScrollBar(verticalScrollBar, (int)-scrollPos.y, (int)verticalMin, (int)verticalMax); + } + + // Draw detail corner rectangle if both scroll bars are visible + if (hasHorizontalScrollBar && hasVerticalScrollBar) + { + Rectangle corner = { (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE) ? (bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) + 2) : (horizontalScrollBar.x + horizontalScrollBar.width + 2), verticalScrollBar.y + verticalScrollBar.height + 2, (float)horizontalScrollBarWidth - 4, (float)verticalScrollBarWidth - 4 }; + GuiDrawRectangle(corner, 0, BLANK, Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT + (state*3))), guiAlpha)); + } + + // Draw scrollbar lines depending on current state + GuiDrawRectangle(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), guiAlpha), BLANK); + + // Set scrollbar slider size back to the way it was before + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, slider); + //-------------------------------------------------------------------- + + if (scroll != NULL) *scroll = scrollPos; + + return view; +} + +// Label control +void GuiLabel(Rectangle bounds, const char *text) +{ + GuiControlState state = guiState; + + // Update control + //-------------------------------------------------------------------- + // ... + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LABEL, (state == GUI_STATE_DISABLED)? TEXT_COLOR_DISABLED : TEXT_COLOR_NORMAL)), guiAlpha)); + //-------------------------------------------------------------------- +} + +// Button control, returns true when clicked +bool GuiButton(Rectangle bounds, const char *text) +{ + GuiControlState state = guiState; + bool pressed = false; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = GUI_STATE_PRESSED; + else state = GUI_STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) pressed = true; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(BUTTON, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(BUTTON, BORDER + (state*3))), guiAlpha), Fade(GetColor(GuiGetStyle(BUTTON, BASE + (state*3))), guiAlpha)); + GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(BUTTON, TEXT + (state*3))), guiAlpha)); + //------------------------------------------------------------------ + + return pressed; +} + +// Label button control +bool GuiLabelButton(Rectangle bounds, const char *text) +{ + GuiControlState state = guiState; + bool pressed = false; + + // NOTE: We force bounds.width to be all text + float textWidth = MeasureTextEx(guiFont, text, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)).x; + if (bounds.width < textWidth) bounds.width = textWidth; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = GUI_STATE_PRESSED; + else state = GUI_STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) pressed = true; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LABEL, TEXT + (state*3))), guiAlpha)); + //-------------------------------------------------------------------- + + return pressed; +} + +// Toggle Button control, returns true when active +bool GuiToggle(Rectangle bounds, const char *text, bool active) +{ + GuiControlState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check toggle button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = GUI_STATE_PRESSED; + else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + state = GUI_STATE_NORMAL; + active = !active; + } + else state = GUI_STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == GUI_STATE_NORMAL) + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TOGGLE, (active? BORDER_COLOR_PRESSED : (BORDER + state*3)))), guiAlpha), Fade(GetColor(GuiGetStyle(TOGGLE, (active? BASE_COLOR_PRESSED : (BASE + state*3)))), guiAlpha)); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TOGGLE, (active? TEXT_COLOR_PRESSED : (TEXT + state*3)))), guiAlpha)); + } + else + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TOGGLE, BORDER + state*3)), guiAlpha), Fade(GetColor(GuiGetStyle(TOGGLE, BASE + state*3)), guiAlpha)); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TOGGLE, TEXT + state*3)), guiAlpha)); + } + //-------------------------------------------------------------------- + + return active; +} + +// Toggle Group control, returns toggled button index +int GuiToggleGroup(Rectangle bounds, const char *text, int active) +{ + #if !defined(TOGGLEGROUP_MAX_ELEMENTS) + #define TOGGLEGROUP_MAX_ELEMENTS 32 + #endif + + float initBoundsX = bounds.x; + + // Get substrings items from text (items pointers) + int rows[TOGGLEGROUP_MAX_ELEMENTS] = { 0 }; + int itemCount = 0; + const char **items = GuiTextSplit(text, &itemCount, rows); + + int prevRow = rows[0]; + + for (int i = 0; i < itemCount; i++) + { + if (prevRow != rows[i]) + { + bounds.x = initBoundsX; + bounds.y += (bounds.height + GuiGetStyle(TOGGLE, GROUP_PADDING)); + prevRow = rows[i]; + } + + if (i == active) GuiToggle(bounds, items[i], true); + else if (GuiToggle(bounds, items[i], false) == true) active = i; + + bounds.x += (bounds.width + GuiGetStyle(TOGGLE, GROUP_PADDING)); + } + + return active; +} + +// Check Box control, returns true when active +bool GuiCheckBox(Rectangle bounds, const char *text, bool checked) +{ + GuiControlState state = guiState; + + Rectangle textBounds = { 0 }; + + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(CHECKBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + Rectangle totalBounds = { + (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_LEFT)? textBounds.x : bounds.x, + bounds.y, + bounds.width + textBounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING), + bounds.height, + }; + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, totalBounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = GUI_STATE_PRESSED; + else state = GUI_STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) checked = !checked; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(CHECKBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(CHECKBOX, BORDER + (state*3))), guiAlpha), BLANK); + + if (checked) + { + Rectangle check = { bounds.x + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.y + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.width - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)), + bounds.height - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)) }; + GuiDrawRectangle(check, 0, BLANK, Fade(GetColor(GuiGetStyle(CHECKBOX, TEXT + state*3)), guiAlpha)); + } + + GuiDrawText(text, textBounds, (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_RIGHT)? GUI_TEXT_ALIGN_LEFT : GUI_TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(LABEL, TEXT + (state*3))), guiAlpha)); + //-------------------------------------------------------------------- + + return checked; +} + +// Combo Box control, returns selected item index +int GuiComboBox(Rectangle bounds, const char *text, int active) +{ + GuiControlState state = guiState; + + bounds.width -= (GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH) + GuiGetStyle(COMBOBOX, COMBO_BUTTON_PADDING)); + + Rectangle selector = { (float)bounds.x + bounds.width + GuiGetStyle(COMBOBOX, COMBO_BUTTON_PADDING), + (float)bounds.y, (float)GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH), (float)bounds.height }; + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, &itemCount, NULL); + + if (active < 0) active = 0; + else if (active > itemCount - 1) active = itemCount - 1; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked && (itemCount > 1)) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds) || + CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + active += 1; + if (active >= itemCount) active = 0; + } + + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = GUI_STATE_PRESSED; + else state = GUI_STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + // Draw combo box main + GuiDrawRectangle(bounds, GuiGetStyle(COMBOBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(COMBOBOX, BORDER + (state*3))), guiAlpha), Fade(GetColor(GuiGetStyle(COMBOBOX, BASE + (state*3))), guiAlpha)); + GuiDrawText(items[active], GetTextBounds(COMBOBOX, bounds), GuiGetStyle(COMBOBOX, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(COMBOBOX, TEXT + (state*3))), guiAlpha)); + + // Draw selector using a custom button + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); + + GuiButton(selector, TextFormat("%i/%i", active + 1, itemCount)); + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + //-------------------------------------------------------------------- + + return active; +} + +// Dropdown Box control +// NOTE: Returns mouse click +bool GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode) +{ + GuiControlState state = guiState; + int itemSelected = *active; + int itemFocused = -1; + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, &itemCount, NULL); + + Rectangle boundsOpen = bounds; + boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITERL_PADDING)); + + Rectangle itemBounds = bounds; + + bool pressed = false; // Check mouse button pressed + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1)) + { + Vector2 mousePoint = GetMousePosition(); + + if (editMode) + { + state = GUI_STATE_PRESSED; + + // Check if mouse has been pressed or released outside limits + if (!CheckCollisionPointRec(mousePoint, boundsOpen)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) pressed = true; + } + + // Check if already selected item has been pressed again + if (CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) pressed = true; + + // Check focused and selected item + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITERL_PADDING)); + + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = i; + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + itemSelected = i; + pressed = true; // Item selected, change to editMode = false + } + break; + } + } + + itemBounds = bounds; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + pressed = true; + state = GUI_STATE_PRESSED; + } + else state = GUI_STATE_FOCUSED; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (editMode) GuiPanel(boundsOpen); + + GuiDrawRectangle(bounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(DROPDOWNBOX, BORDER + state*3)), guiAlpha), Fade(GetColor(GuiGetStyle(DROPDOWNBOX, BASE + state*3)), guiAlpha)); + GuiDrawText(items[itemSelected], GetTextBounds(DEFAULT, bounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + state*3)), guiAlpha)); + + if (editMode) + { + // Draw visible items + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITERL_PADDING)); + + if (i == itemSelected) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_PRESSED)), guiAlpha), Fade(GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_PRESSED)), guiAlpha)); + GuiDrawText(items[i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_PRESSED)), guiAlpha)); + } + else if (i == itemFocused) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_FOCUSED)), guiAlpha), Fade(GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_FOCUSED)), guiAlpha)); + GuiDrawText(items[i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_FOCUSED)), guiAlpha)); + } + else GuiDrawText(items[i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_NORMAL)), guiAlpha)); + } + } + + // Draw arrows (using icon if available) +#if defined(RAYGUI_NO_RICONS) + GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, + GUI_TEXT_ALIGN_CENTER, Fade(GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3))), guiAlpha)); +#else + GuiDrawText("#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, + GUI_TEXT_ALIGN_CENTER, Fade(GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3))), guiAlpha)); // RICON_ARROW_DOWN_FILL +#endif + //-------------------------------------------------------------------- + + *active = itemSelected; + return pressed; +} + +// Text Box control, updates input text +// NOTE 2: Returns if KEY_ENTER pressed (useful for data validation) +bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) +{ + GuiControlState state = guiState; + bool pressed = false; + + Rectangle cursor = { + bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text) + 2, + bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), + 4, + (float)GuiGetStyle(DEFAULT, TEXT_SIZE)*2 + }; + + if (cursor.height > bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (editMode) + { + state = GUI_STATE_PRESSED; + + int key = GetCharPressed(); // Returns codepoint as Unicode + int keyCount = (int)strlen(text); + + // Only allow keys in range [32..125] + if (keyCount < (textSize - 1)) + { + float maxWidth = (bounds.width - (GuiGetStyle(TEXTBOX, TEXT_INNER_PADDING)*2)); + + if ((GetTextWidth(text) < (maxWidth - GuiGetStyle(DEFAULT, TEXT_SIZE))) && (key >= 32)) + { + int byteSize = 0; + const char *textUTF8 = CodepointToUTF8(key, &byteSize); + + for (int i = 0; i < byteSize; i++) + { + text[keyCount] = textUTF8[i]; + keyCount++; + } + + text[keyCount] = '\0'; + } + } + + // Delete text + if (keyCount > 0) + { + if (IsKeyPressed(KEY_BACKSPACE)) + { + keyCount--; + text[keyCount] = '\0'; + if (keyCount < 0) keyCount = 0; + } + } + + if (IsKeyPressed(KEY_ENTER) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) pressed = true; + + // Check text alignment to position cursor properly + int textAlignment = GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT); + if (textAlignment == GUI_TEXT_ALIGN_CENTER) cursor.x = bounds.x + GetTextWidth(text)/2 + bounds.width/2 + 1; + else if (textAlignment == GUI_TEXT_ALIGN_RIGHT) cursor.x = bounds.x + bounds.width - GuiGetStyle(TEXTBOX, TEXT_INNER_PADDING); + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = GUI_STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) pressed = true; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == GUI_STATE_PRESSED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha), Fade(GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)), guiAlpha)); + } + else if (state == GUI_STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha), Fade(GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED)), guiAlpha)); + } + else GuiDrawRectangle(bounds, 1, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha), BLANK); + + GuiDrawText(text, GetTextBounds(TEXTBOX, bounds), GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))), guiAlpha)); + + // Draw cursor + if (editMode) GuiDrawRectangle(cursor, 0, BLANK, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); + //-------------------------------------------------------------------- + + return pressed; +} + +// Spinner control, returns selected value +bool GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + GuiControlState state = guiState; + + bool pressed = false; + int tempValue = *value; + + Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_PADDING), bounds.y, + bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_PADDING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check spinner state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = GUI_STATE_PRESSED; + else state = GUI_STATE_FOCUSED; + } + } + + if (!editMode) + { + if (tempValue < minValue) tempValue = minValue; + if (tempValue > maxValue) tempValue = maxValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + // TODO: Set Spinner properties for ValueBox + pressed = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); + + // Draw value selector custom buttons + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); + +#if defined(RAYGUI_NO_RICONS) + if (GuiButton(leftButtonBound, "<")) tempValue--; + if (GuiButton(rightButtonBound, ">")) tempValue++; +#else + if (GuiButton(leftButtonBound, GuiIconText(RICON_ARROW_LEFT_FILL, NULL))) tempValue--; + if (GuiButton(rightButtonBound, GuiIconText(RICON_ARROW_RIGHT_FILL, NULL))) tempValue++; +#endif + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_RIGHT)? GUI_TEXT_ALIGN_LEFT : GUI_TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(LABEL, TEXT + (state*3))), guiAlpha)); + //-------------------------------------------------------------------- + + *value = tempValue; + return pressed; +} + +// Value Box control, updates input text with numbers +// NOTE: Requires static variables: frameCounter +bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + #if !defined(VALUEBOX_MAX_CHARS) + #define VALUEBOX_MAX_CHARS 32 + #endif + + GuiControlState state = guiState; + bool pressed = false; + + char textValue[VALUEBOX_MAX_CHARS + 1] = "\0"; + sprintf(textValue, "%i", *value); + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = GUI_STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Only allow keys in range [48..57] + if (keyCount < VALUEBOX_MAX_CHARS) + { + if (GetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if ((key >= 48) && (key <= 57)) + { + textValue[keyCount] = (char)key; + keyCount++; + valueHasChanged = true; + } + } + } + + // Delete text + if (keyCount > 0) + { + if (IsKeyPressed(KEY_BACKSPACE)) + { + keyCount--; + textValue[keyCount] = '\0'; + if (keyCount < 0) keyCount = 0; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToInteger(textValue); + + if (IsKeyPressed(KEY_ENTER) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) pressed = true; + } + else + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = GUI_STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) pressed = true; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == GUI_STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == GUI_STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + // WARNING: BLANK color does not work properly with Fade() + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), guiAlpha), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), GUI_TEXT_ALIGN_CENTER, Fade(GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3))), guiAlpha)); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 2, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; + GuiDrawRectangle(cursor, 0, BLANK, Fade(GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED)), guiAlpha)); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_RIGHT)? GUI_TEXT_ALIGN_LEFT : GUI_TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(LABEL, TEXT + (state*3))), guiAlpha)); + //-------------------------------------------------------------------- + + return pressed; +} + +// Text Box control with multiple lines +bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) +{ + GuiControlState state = guiState; + bool pressed = false; + + Rectangle textAreaBounds = { + bounds.x + GuiGetStyle(TEXTBOX, BORDER_WIDTH) + GuiGetStyle(TEXTBOX, TEXT_INNER_PADDING), + bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH) + GuiGetStyle(TEXTBOX, TEXT_INNER_PADDING), + bounds.width - 2*(GuiGetStyle(TEXTBOX, BORDER_WIDTH) + GuiGetStyle(TEXTBOX, TEXT_INNER_PADDING)), + bounds.height - 2*(GuiGetStyle(TEXTBOX, BORDER_WIDTH) + GuiGetStyle(TEXTBOX, TEXT_INNER_PADDING)) + }; + + // Cursor position, [x, y] values should be updated + Rectangle cursor = { 0, -1, 4, (float)GuiGetStyle(DEFAULT, TEXT_SIZE) + 2 }; + + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; // Character rectangle scaling factor + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (editMode) + { + state = GUI_STATE_PRESSED; + + // We get an Unicode codepoint + int codepoint = GetCharPressed(); + int textLength = (int)strlen(text); // Length in bytes (UTF-8 string) + + // Introduce characters + if (textLength < (textSize - 1)) + { + if (IsKeyPressed(KEY_ENTER)) + { + text[textLength] = '\n'; + textLength++; + } + else if (codepoint >= 32) + { + // Supports Unicode inputs -> Encoded to UTF-8 + int charUTF8Length = 0; + const char *charEncoded = CodepointToUTF8(codepoint, &charUTF8Length); + memcpy(text + textLength, charEncoded, charUTF8Length); + textLength += charUTF8Length; + } + } + + // Delete characters + if (textLength > 0) + { + if (IsKeyPressed(KEY_BACKSPACE)) + { + if ((unsigned char)text[textLength - 1] < 127) + { + // Remove ASCII equivalent character (1 byte) + textLength--; + text[textLength] = '\0'; + } + else + { + // Remove latest UTF-8 unicode character introduced (n bytes) + int charUTF8Length = 0; + while (((unsigned char)text[textLength - 1 - charUTF8Length] & 0b01000000) == 0) charUTF8Length++; + + textLength -= (charUTF8Length + 1); + text[textLength] = '\0'; + } + } + } + + // Exit edit mode + if (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) pressed = true; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = GUI_STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) pressed = true; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == GUI_STATE_PRESSED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha), Fade(GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)), guiAlpha)); + } + else if (state == GUI_STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha), Fade(GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED)), guiAlpha)); + } + else GuiDrawRectangle(bounds, 1, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), guiAlpha), BLANK); + + int wrapMode = 1; // 0-No wrap, 1-Char wrap, 2-Word wrap + Vector2 cursorPos = { textAreaBounds.x, textAreaBounds.y }; + + //int lastSpacePos = 0; + //int lastSpaceWidth = 0; + //int lastSpaceCursorPos = 0; + + for (int i = 0, codepointLength = 0; text[i] != '\0'; i += codepointLength) + { + int codepoint = GetCodepoint(text + i, &codepointLength); + int index = GetGlyphIndex(guiFont, codepoint); // If requested codepoint is not found, we get '?' (0x3f) + Rectangle atlasRec = guiFont.recs[index]; + GlyphInfo glyphInfo = guiFont.glyphs[index]; // Glyph measures + + if ((codepointLength == 1) && (codepoint == '\n')) + { + cursorPos.y += (guiFont.baseSize*scaleFactor + GuiGetStyle(TEXTBOX, TEXT_LINES_PADDING)); // Line feed + cursorPos.x = textAreaBounds.x; // Carriage return + } + else + { + if (wrapMode == 1) + { + int glyphWidth = 0; + if (glyphInfo.advanceX != 0) glyphWidth += glyphInfo.advanceX; + else glyphWidth += (atlasRec.width + glyphInfo.offsetX); + + // Jump line if the end of the text box area has been reached + if ((cursorPos.x + (glyphWidth*scaleFactor)) > (textAreaBounds.x + textAreaBounds.width)) + { + cursorPos.y += (guiFont.baseSize*scaleFactor + GuiGetStyle(TEXTBOX, TEXT_LINES_PADDING)); // Line feed + cursorPos.x = textAreaBounds.x; // Carriage return + } + } + else if (wrapMode == 2) + { + /* + if ((codepointLength == 1) && (codepoint == ' ')) + { + lastSpacePos = i; + lastSpaceWidth = 0; + lastSpaceCursorPos = cursorPos.x; + } + + // Jump line if last word reaches end of text box area + if ((lastSpaceCursorPos + lastSpaceWidth) > (textAreaBounds.x + textAreaBounds.width)) + { + cursorPos.y += 12; // Line feed + cursorPos.x = textAreaBounds.x; // Carriage return + } + */ + } + + // Draw current character glyph + DrawTextCodepoint(guiFont, codepoint, cursorPos, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), Fade(GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3))), guiAlpha)); + + int glyphWidth = 0; + if (glyphInfo.advanceX != 0) glyphWidth += glyphInfo.advanceX; + else glyphWidth += (atlasRec.width + glyphInfo.offsetX); + + cursorPos.x += (glyphWidth*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + //if (i > lastSpacePos) lastSpaceWidth += (atlasRec.width + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + cursor.x = cursorPos.x; + cursor.y = cursorPos.y; + + // Draw cursor position considering text glyphs + if (editMode) GuiDrawRectangle(cursor, 0, BLANK, Fade(GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED)), guiAlpha)); + //-------------------------------------------------------------------- + + return pressed; +} + +// Slider control with pro parameters +// NOTE: Other GuiSlider*() controls use this one +float GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue, int sliderWidth) +{ + GuiControlState state = guiState; + + int sliderValue = (int)(((value - minValue)/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); + + Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + if (sliderWidth > 0) // Slider + { + slider.x += (sliderValue - sliderWidth/2); + slider.width = (float)sliderWidth; + } + else if (sliderWidth == 0) // SliderBar + { + slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.width = (float)sliderValue; + } + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = GUI_STATE_PRESSED; + + // Get equivalent value and slider position from mousePoint.x + value = ((maxValue - minValue)*(mousePoint.x - (float)(bounds.x + sliderWidth/2)))/(float)(bounds.width - sliderWidth) + minValue; + + if (sliderWidth > 0) slider.x = mousePoint.x - slider.width/2; // Slider + else if (sliderWidth == 0) slider.width = (float)sliderValue; // SliderBar + } + else state = GUI_STATE_FOCUSED; + } + + if (value > maxValue) value = maxValue; + else if (value < minValue) value = minValue; + } + + // Bar limits check + if (sliderWidth > 0) // Slider + { + if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); + else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); + } + else if (sliderWidth == 0) // SliderBar + { + if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), guiAlpha), Fade(GetColor(GuiGetStyle(SLIDER, (state != GUI_STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED)), guiAlpha)); + + // Draw slider internal bar (depends on state) + if ((state == GUI_STATE_NORMAL) || (state == GUI_STATE_PRESSED)) GuiDrawRectangle(slider, 0, BLANK, Fade(GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED)), guiAlpha)); + else if (state == GUI_STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, Fade(GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED)), guiAlpha)); + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, GUI_TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(SLIDER, TEXT + (state*3))), guiAlpha)); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, GUI_TEXT_ALIGN_LEFT, Fade(GetColor(GuiGetStyle(SLIDER, TEXT + (state*3))), guiAlpha)); + } + //-------------------------------------------------------------------- + + return value; +} + +// Slider control extended, returns selected value and has text +float GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue) +{ + return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); +} + +// Slider Bar control extended, returns selected value +float GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue) +{ + return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, 0); +} + +// Progress Bar control extended, shows current progress value +float GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue) +{ + GuiControlState state = guiState; + + Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), + bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, + bounds.height - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if (state != GUI_STATE_DISABLED) progress.width = ((float)(value/(maxValue - minValue))*(float)(bounds.width - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH))); + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(PROGRESSBAR, BORDER + (state*3))), guiAlpha), BLANK); + + // Draw slider internal progress bar (depends on state) + if ((state == GUI_STATE_NORMAL) || (state == GUI_STATE_PRESSED)) GuiDrawRectangle(progress, 0, BLANK, Fade(GetColor(GuiGetStyle(PROGRESSBAR, BASE_COLOR_PRESSED)), guiAlpha)); + else if (state == GUI_STATE_FOCUSED) GuiDrawRectangle(progress, 0, BLANK, Fade(GetColor(GuiGetStyle(PROGRESSBAR, TEXT_COLOR_FOCUSED)), guiAlpha)); + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, GUI_TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3))), guiAlpha)); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, GUI_TEXT_ALIGN_LEFT, Fade(GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3))), guiAlpha)); + } + //-------------------------------------------------------------------- + + return value; +} + +// Status Bar control +void GuiStatusBar(Rectangle bounds, const char *text) +{ + GuiControlState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(STATUSBAR, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(STATUSBAR, (state != GUI_STATE_DISABLED)? BORDER_COLOR_NORMAL : BORDER_COLOR_DISABLED)), guiAlpha), + Fade(GetColor(GuiGetStyle(STATUSBAR, (state != GUI_STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED)), guiAlpha)); + GuiDrawText(text, GetTextBounds(STATUSBAR, bounds), GuiGetStyle(STATUSBAR, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(STATUSBAR, (state != GUI_STATE_DISABLED)? TEXT_COLOR_NORMAL : TEXT_COLOR_DISABLED)), guiAlpha)); + //-------------------------------------------------------------------- +} + +// Dummy rectangle control, intended for placeholding +void GuiDummyRec(Rectangle bounds, const char *text) +{ + GuiControlState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = GUI_STATE_PRESSED; + else state = GUI_STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, 0, BLANK, Fade(GetColor(GuiGetStyle(DEFAULT, (state != GUI_STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED)), guiAlpha)); + GuiDrawText(text, GetTextBounds(DEFAULT, bounds), GUI_TEXT_ALIGN_CENTER, Fade(GetColor(GuiGetStyle(BUTTON, (state != GUI_STATE_DISABLED)? TEXT_COLOR_NORMAL : TEXT_COLOR_DISABLED)), guiAlpha)); + //------------------------------------------------------------------ +} + +// Scroll Bar control +int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) +{ + GuiControlState state = guiState; + + // Is the scrollbar horizontal or vertical? + bool isVertical = (bounds.width > bounds.height)? false : true; + + // The size (width or height depending on scrollbar type) of the spinner buttons + const int spinnerSize = GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)? (isVertical? (int)bounds.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) : (int)bounds.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH)) : 0; + + // Arrow buttons [<] [>] [∧] [∨] + Rectangle arrowUpLeft = { 0 }; + Rectangle arrowDownRight = { 0 }; + + // Actual area of the scrollbar excluding the arrow buttons + Rectangle scrollbar = { 0 }; + + // Slider bar that moves --[///]----- + Rectangle slider = { 0 }; + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + + const int range = maxValue - minValue; + int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + + // Calculate rectangles for all of the components + arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + + if (isVertical) + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + bounds.height - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize}; + scrollbar = RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), arrowUpLeft.y + arrowUpLeft.height, bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)), bounds.height - arrowUpLeft.height - arrowDownRight.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) }; + sliderSize = (sliderSize >= scrollbar.height)? ((int)scrollbar.height - 2) : sliderSize; // Make sure the slider won't get outside of the scrollbar + slider = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), (float)scrollbar.y + (int)(((float)(value - minValue)/range)*(scrollbar.height - sliderSize)), (float)bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)), (float)sliderSize }; + } + else + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + bounds.width - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize}; + scrollbar = RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x + arrowUpLeft.width, bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), bounds.width - arrowUpLeft.width - arrowDownRight.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH), bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING))}; + sliderSize = (sliderSize >= scrollbar.width)? ((int)scrollbar.width - 2) : sliderSize; // Make sure the slider won't get outside of the scrollbar + slider = RAYGUI_CLITERAL(Rectangle){ (float)scrollbar.x + (int)(((float)(value - minValue)/range)*(scrollbar.width - sliderSize)), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), (float)sliderSize, (float)bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)) }; + } + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = GUI_STATE_FOCUSED; + + // Handle mouse wheel + int wheel = (int)GetMouseWheelMove(); + if (wheel != 0) value += wheel; + + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= range/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (CheckCollisionPointRec(mousePoint, arrowDownRight)) value += range/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + + state = GUI_STATE_PRESSED; + } + else if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (!isVertical) + { + Rectangle scrollArea = { arrowUpLeft.x + arrowUpLeft.width, arrowUpLeft.y, scrollbar.width, bounds.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH)}; + if (CheckCollisionPointRec(mousePoint, scrollArea)) value = (int)(((float)(mousePoint.x - scrollArea.x - slider.width/2)*range)/(scrollArea.width - slider.width) + minValue); + } + else + { + Rectangle scrollArea = { arrowUpLeft.x, arrowUpLeft.y+arrowUpLeft.height, bounds.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH), scrollbar.height}; + if (CheckCollisionPointRec(mousePoint, scrollArea)) value = (int)(((float)(mousePoint.y - scrollArea.y - slider.height/2)*range)/(scrollArea.height - slider.height) + minValue); + } + } + } + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SCROLLBAR, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), guiAlpha), Fade(GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED)), guiAlpha)); // Draw the background + + GuiDrawRectangle(scrollbar, 0, BLANK, Fade(GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL)), guiAlpha)); // Draw the scrollbar active area background + GuiDrawRectangle(slider, 0, BLANK, Fade(GetColor(GuiGetStyle(SLIDER, BORDER + state*3)), guiAlpha)); // Draw the slider bar + + // Draw arrows (using icon if available) + if (GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)) + { +#if defined(RAYGUI_NO_RICONS) + GuiDrawText(isVertical? "^" : "<", RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + GUI_TEXT_ALIGN_CENTER, Fade(GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3))), guiAlpha)); + GuiDrawText(isVertical? "v" : ">", RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + GUI_TEXT_ALIGN_CENTER, Fade(GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3))), guiAlpha)); +#else + GuiDrawText(isVertical? "#121#" : "#118#", RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + GUI_TEXT_ALIGN_CENTER, Fade(GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3)), guiAlpha)); // RICON_ARROW_UP_FILL / RICON_ARROW_LEFT_FILL + GuiDrawText(isVertical? "#120#" : "#119#", RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + GUI_TEXT_ALIGN_CENTER, Fade(GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3)), guiAlpha)); // RICON_ARROW_DOWN_FILL / RICON_ARROW_RIGHT_FILL +#endif + } + //-------------------------------------------------------------------- + + return value; +} + +// List View control +int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int active) +{ + int itemCount = 0; + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, &itemCount, NULL); + + return GuiListViewEx(bounds, items, itemCount, NULL, scrollIndex, active); +} + +// List View control with extended parameters +int GuiListViewEx(Rectangle bounds, const char **text, int count, int *focus, int *scrollIndex, int active) +{ + GuiControlState state = guiState; + int itemFocused = (focus == NULL)? -1 : *focus; + int itemSelected = active; + + // Check if we need a scroll bar + bool useScrollBar = false; + if ((GuiGetStyle(LISTVIEW, LIST_ITERL_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITERL_PADDING))*count > bounds.height) useScrollBar = true; + + // Define base item rectangle [0] + Rectangle itemBounds = { 0 }; + itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITERL_PADDING); + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITERL_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITERL_PADDING) - GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.height = (float)GuiGetStyle(LISTVIEW, LIST_ITERL_HEIGHT); + if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); + + // Get items on the list + int visibleItems = (int)bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITERL_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITERL_PADDING)); + if (visibleItems > count) visibleItems = count; + + int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex; + if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0; + int endIndex = startIndex + visibleItems; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check mouse inside list view + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = GUI_STATE_FOCUSED; + + // Check focused and selected item + for (int i = 0; i < visibleItems; i++) + { + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = startIndex + i; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (itemSelected == (startIndex + i)) itemSelected = -1; + else itemSelected = startIndex + i; + } + break; + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITERL_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITERL_PADDING)); + } + + if (useScrollBar) + { + int wheelMove = (int)GetMouseWheelMove(); + startIndex -= wheelMove; + + if (startIndex < 0) startIndex = 0; + else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems; + + endIndex = startIndex + visibleItems; + if (endIndex > count) endIndex = count; + } + } + else itemFocused = -1; + + // Reset item rectangle y to [0] + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITERL_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), guiAlpha), GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Draw visible items + for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) + { + if (state == GUI_STATE_DISABLED) + { + if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), guiAlpha), Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED)), guiAlpha)); + + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED)), guiAlpha)); + } + else + { + if ((startIndex + i) == itemSelected) + { + // Draw item selected + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), guiAlpha), Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED)), guiAlpha)); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED)), guiAlpha)); + } + else if ((startIndex + i) == itemFocused) + { + // Draw item focused + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), guiAlpha), Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED)), guiAlpha)); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED)), guiAlpha)); + } + else + { + // Draw item normal + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL)), guiAlpha)); + } + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITERL_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITERL_PADDING)); + } + + if (useScrollBar) + { + Rectangle scrollBarBounds = { + bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Calculate percentage of visible items and apply same percentage to scrollbar + float percentVisible = (float)(endIndex - startIndex)/count; + float sliderSize = bounds.height*percentVisible; + + int prevSliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); // Save default slider size + int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)sliderSize); // Change slider size + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed + + startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems); + + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, prevSliderSize); // Reset slider size to default + } + //-------------------------------------------------------------------- + + if (focus != NULL) *focus = itemFocused; + if (scrollIndex != NULL) *scrollIndex = startIndex; + + return itemSelected; +} + +// Color Panel control +Color GuiColorPanel(Rectangle bounds, Color color) +{ + const Color colWhite = { 255, 255, 255, 255 }; + const Color colBlack = { 0, 0, 0, 255 }; + + GuiControlState state = guiState; + Vector2 pickerSelector = { 0 }; + + Vector3 vcolor = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; + Vector3 hsv = ConvertRGBtoHSV(vcolor); + + pickerSelector.x = bounds.x + (float)hsv.y*bounds.width; // HSV: Saturation + pickerSelector.y = bounds.y + (1.0f - (float)hsv.z)*bounds.height; // HSV: Value + + float hue = -1.0f; + Vector3 maxHue = { hue >= 0.0f ? hue : hsv.x, 1.0f, 1.0f }; + Vector3 rgbHue = ConvertHSVtoRGB(maxHue); + Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), + (unsigned char)(255.0f*rgbHue.y), + (unsigned char)(255.0f*rgbHue.z), 255 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = GUI_STATE_PRESSED; + pickerSelector = mousePoint; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + hsv.y = colorPick.x; + hsv.z = 1.0f - colorPick.y; + + Vector3 rgb = ConvertHSVtoRGB(hsv); + + // NOTE: Vector3ToColor() only available on raylib 1.8.1 + color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), + (unsigned char)(255.0f*rgb.y), + (unsigned char)(255.0f*rgb.z), + (unsigned char)(255.0f*(float)color.a/255.0f) }; + + } + else state = GUI_STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != GUI_STATE_DISABLED) + { + DrawRectangleGradientEx(bounds, Fade(colWhite, guiAlpha), Fade(colWhite, guiAlpha), Fade(maxHueCol, guiAlpha), Fade(maxHueCol, guiAlpha)); + DrawRectangleGradientEx(bounds, Fade(colBlack, 0), Fade(colBlack, guiAlpha), Fade(colBlack, guiAlpha), Fade(colBlack, 0)); + + // Draw color picker: selector + Rectangle selector = { pickerSelector.x - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, pickerSelector.y - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE), (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE) }; + GuiDrawRectangle(selector, 0, BLANK, Fade(colWhite, guiAlpha)); + } + else + { + DrawRectangleGradientEx(bounds, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.6f), guiAlpha)); + } + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), guiAlpha), BLANK); + //-------------------------------------------------------------------- + + return color; +} + +// Color Bar Alpha control +// NOTE: Returns alpha value normalized [0..1] +float GuiColorBarAlpha(Rectangle bounds, float alpha) +{ + #define COLORBARALPHA_CHECKED_SIZE 10 + + GuiControlState state = guiState; + Rectangle selector = { (float)bounds.x + alpha*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds) || + CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = GUI_STATE_PRESSED; + + alpha = (mousePoint.x - bounds.x)/bounds.width; + if (alpha <= 0.0f) alpha = 0.0f; + if (alpha >= 1.0f) alpha = 1.0f; + //selector.x = bounds.x + (int)(((alpha - 0)/(100 - 0))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))) - selector.width/2; + } + else state = GUI_STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + + // Draw alpha bar: checked background + if (state != GUI_STATE_DISABLED) + { + int checksX = (int)bounds.width/COLORBARALPHA_CHECKED_SIZE; + int checksY = (int)bounds.height/COLORBARALPHA_CHECKED_SIZE; + + for (int x = 0; x < checksX; x++) + { + for (int y = 0; y < checksY; y++) + { + Rectangle check = { bounds.x + x*COLORBARALPHA_CHECKED_SIZE, bounds.y + y*COLORBARALPHA_CHECKED_SIZE, COLORBARALPHA_CHECKED_SIZE, COLORBARALPHA_CHECKED_SIZE }; + GuiDrawRectangle(check, 0, BLANK, ((x + y)%2)? Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.4f), guiAlpha) : Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.4f), guiAlpha)); + } + } + + DrawRectangleGradientEx(bounds, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientEx(bounds, Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), guiAlpha), BLANK); + + // Draw alpha bar: selector + GuiDrawRectangle(selector, 0, BLANK, Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), guiAlpha)); + //-------------------------------------------------------------------- + + return alpha; +} + +// Color Bar Hue control +// Returns hue value normalized [0..1] +// NOTE: Other similar bars (for reference): +// Color GuiColorBarSat() [WHITE->color] +// Color GuiColorBarValue() [BLACK->color], HSV/HSL +// float GuiColorBarLuminance() [BLACK->WHITE] +float GuiColorBarHue(Rectangle bounds, float hue) +{ + GuiControlState state = guiState; + Rectangle selector = { (float)bounds.x - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)bounds.y + hue/360.0f*bounds.height - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2, (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds) || + CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = GUI_STATE_PRESSED; + + hue = (mousePoint.y - bounds.y)*360/bounds.height; + if (hue <= 0.0f) hue = 0.0f; + if (hue >= 359.0f) hue = 359.0f; + + } + else state = GUI_STATE_FOCUSED; + + /*if (IsKeyDown(KEY_UP)) + { + hue -= 2.0f; + if (hue <= 0.0f) hue = 0.0f; + } + else if (IsKeyDown(KEY_DOWN)) + { + hue += 2.0f; + if (hue >= 360.0f) hue = 360.0f; + }*/ + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != GUI_STATE_DISABLED) + { + // Draw hue bar:color bars + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y), (int)bounds.width, ceil(bounds.height/6), Fade(RAYGUI_CLITERAL(Color) { 255, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color) { 255, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + bounds.height/6), (int)bounds.width, ceil(bounds.height/6), Fade(RAYGUI_CLITERAL(Color) { 255, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color) { 0, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 2*(bounds.height/6)), (int)bounds.width, ceil(bounds.height/6), Fade(RAYGUI_CLITERAL(Color) { 0, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color) { 0, 255, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 3*(bounds.height/6)), (int)bounds.width, ceil(bounds.height/6), Fade(RAYGUI_CLITERAL(Color) { 0, 255, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color) { 0, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 4*(bounds.height/6)), (int)bounds.width, ceil(bounds.height/6), Fade(RAYGUI_CLITERAL(Color) { 0, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color) { 255, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 5*(bounds.height/6)), (int)bounds.width, (int)(bounds.height/6), Fade(RAYGUI_CLITERAL(Color) { 255, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color) { 255, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientV((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), guiAlpha), BLANK); + + // Draw hue bar: selector + GuiDrawRectangle(selector, 0, BLANK, Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), guiAlpha)); + //-------------------------------------------------------------------- + + return hue; +} + +// Color Picker control +// NOTE: It's divided in multiple controls: +// Color GuiColorPanel(Rectangle bounds, Color color) +// float GuiColorBarAlpha(Rectangle bounds, float alpha) +// float GuiColorBarHue(Rectangle bounds, float value) +// NOTE: bounds define GuiColorPanel() size +Color GuiColorPicker(Rectangle bounds, Color color) +{ + color = GuiColorPanel(bounds, color); + + Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; + //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; + + Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ color.r/255.0f, color.g/255.0f, color.b/255.0f }); + hsv.x = GuiColorBarHue(boundsHue, hsv.x); + //color.a = (unsigned char)(GuiColorBarAlpha(boundsAlpha, (float)color.a/255.0f)*255.0f); + Vector3 rgb = ConvertHSVtoRGB(hsv); + + color = RAYGUI_CLITERAL(Color){ (unsigned char)roundf(rgb.x*255.0f), (unsigned char)roundf(rgb.y*255.0f), (unsigned char)roundf(rgb.z*255.0f), color.a }; + + return color; +} + +// Message Box control +int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons) +{ + #define MESSAGEBOX_BUTTON_HEIGHT 24 + #define MESSAGEBOX_BUTTON_PADDING 10 + + int clicked = -1; // Returns clicked button from buttons list, 0 refers to closed window button + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + MESSAGEBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - MESSAGEBOX_BUTTON_HEIGHT - MESSAGEBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = MESSAGEBOX_BUTTON_HEIGHT; + + Vector2 textSize = MeasureTextEx(guiFont, message, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), 1); + + Rectangle textBounds = { 0 }; + textBounds.x = bounds.x + bounds.width/2 - textSize.x/2; + textBounds.y = bounds.y + WINDOW_STATUSBAR_HEIGHT + (bounds.height - WINDOW_STATUSBAR_HEIGHT - MESSAGEBOX_BUTTON_HEIGHT - MESSAGEBOX_BUTTON_PADDING)/2 - textSize.y/2; + textBounds.width = textSize.x; + textBounds.height = textSize.y; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) clicked = 0; + + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + + prevTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) clicked = i + 1; + buttonBounds.x += (buttonBounds.width + MESSAGEBOX_BUTTON_PADDING); + } + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevTextAlignment); + //-------------------------------------------------------------------- + + return clicked; +} + +// Text Input Box control, ask for text +int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text) +{ + #define TEXTINPUTBOX_BUTTON_HEIGHT 24 + #define TEXTINPUTBOX_BUTTON_PADDING 10 + #define TEXTINPUTBOX_HEIGHT 30 + + #define TEXTINPUTBOX_MAX_TEXT_LENGTH 256 + + // Used to enable text edit mode + // WARNING: No more than one GuiTextInputBox() should be open at the same time + static bool textEditMode = false; + + int btnIndex = -1; + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - TEXTINPUTBOX_BUTTON_HEIGHT - TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - TEXTINPUTBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = TEXTINPUTBOX_BUTTON_HEIGHT; + + int messageInputHeight = (int)bounds.height - WINDOW_STATUSBAR_HEIGHT - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - TEXTINPUTBOX_BUTTON_HEIGHT - 2*TEXTINPUTBOX_BUTTON_PADDING; + + Rectangle textBounds = { 0 }; + if (message != NULL) + { + Vector2 textSize = MeasureTextEx(guiFont, message, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), 1); + + textBounds.x = bounds.x + bounds.width/2 - textSize.x/2; + textBounds.y = bounds.y + WINDOW_STATUSBAR_HEIGHT + messageInputHeight/4 - textSize.y/2; + textBounds.width = textSize.x; + textBounds.height = textSize.y; + } + + Rectangle textBoxBounds = { 0 }; + textBoxBounds.x = bounds.x + TEXTINPUTBOX_BUTTON_PADDING; + textBoxBounds.y = bounds.y + WINDOW_STATUSBAR_HEIGHT - TEXTINPUTBOX_HEIGHT/2; + if (message == NULL) textBoxBounds.y += messageInputHeight/2; + else textBoxBounds.y += (messageInputHeight/2 + messageInputHeight/4); + textBoxBounds.width = bounds.width - TEXTINPUTBOX_BUTTON_PADDING*2; + textBoxBounds.height = TEXTINPUTBOX_HEIGHT; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) btnIndex = 0; + + // Draw message if available + if (message != NULL) + { + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + } + + if (GuiTextBox(textBoxBounds, text, TEXTINPUTBOX_MAX_TEXT_LENGTH, textEditMode)) textEditMode = !textEditMode; + + int prevBtnTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) btnIndex = i + 1; + buttonBounds.x += (buttonBounds.width + MESSAGEBOX_BUTTON_PADDING); + } + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevBtnTextAlignment); + //-------------------------------------------------------------------- + + return btnIndex; +} + +// Grid control +// NOTE: Returns grid mouse-hover selected cell +// About drawing lines at subpixel spacing, simple put, not easy solution: +// https://stackoverflow.com/questions/4435450/2d-opengl-drawing-lines-that-dont-exactly-fit-pixel-raster +Vector2 GuiGrid(Rectangle bounds, float spacing, int subdivs) +{ + #if !defined(GRID_COLOR_ALPHA) + #define GRID_COLOR_ALPHA 0.15f // Grid lines alpha amount + #endif + + GuiControlState state = guiState; + Vector2 mousePoint = GetMousePosition(); + Vector2 currentCell = { -1, -1 }; + + int linesV = ((int)(bounds.width/spacing))*subdivs + 1; + int linesH = ((int)(bounds.height/spacing))*subdivs + 1; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + currentCell.x = (mousePoint.x - bounds.x)/spacing; + currentCell.y = (mousePoint.y - bounds.y)/spacing; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + switch (state) + { + case GUI_STATE_NORMAL: + { + if (subdivs > 0) + { + // Draw vertical grid lines + for (int i = 0; i < linesV; i++) + { + Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height }; + GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0) ? Fade(GetColor(GuiGetStyle(DEFAULT, LINE_COLOR)), GRID_COLOR_ALPHA*4) : Fade(GetColor(GuiGetStyle(DEFAULT, LINE_COLOR)), GRID_COLOR_ALPHA)); + } + + // Draw horizontal grid lines + for (int i = 0; i < linesH; i++) + { + Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width, 1 }; + GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0) ? Fade(GetColor(GuiGetStyle(DEFAULT, LINE_COLOR)), GRID_COLOR_ALPHA*4) : Fade(GetColor(GuiGetStyle(DEFAULT, LINE_COLOR)), GRID_COLOR_ALPHA)); + } + } + } break; + default: break; + } + + return currentCell; +} + +//---------------------------------------------------------------------------------- +// Styles loading functions +//---------------------------------------------------------------------------------- + +// Load raygui style file (.rgs) +void GuiLoadStyle(const char *fileName) +{ + bool tryBinary = false; + + // Try reading the files as text file first + FILE *rgsFile = fopen(fileName, "rt"); + + if (rgsFile != NULL) + { + char buffer[256] = { 0 }; + fgets(buffer, 256, rgsFile); + + if (buffer[0] == '#') + { + int controlId = 0; + int propertyId = 0; + unsigned int propertyValue = 0; + + while (!feof(rgsFile)) + { + switch (buffer[0]) + { + case 'p': + { + // Style property: p + + sscanf(buffer, "p %d %d 0x%x", &controlId, &propertyId, &propertyValue); + + GuiSetStyle(controlId, propertyId, (int)propertyValue); + + } break; + case 'f': + { + // Style font: f + + int fontSize = 0; + char charmapFileName[256] = { 0 }; + char fontFileName[256] = { 0 }; + sscanf(buffer, "f %d %s %[^\r\n]s", &fontSize, charmapFileName, fontFileName); + + Font font = { 0 }; + + if (charmapFileName[0] != '0') + { + // Load characters from charmap file, + // expected '\n' separated list of integer values + char *charValues = LoadFileText(charmapFileName); + if (charValues != NULL) + { + int glyphCount = 0; + const char **chars = TextSplit(charValues, '\n', &glyphCount); + + int *values = (int *)RAYGUI_MALLOC(glyphCount*sizeof(int)); + for (int i = 0; i < glyphCount; i++) values[i] = TextToInteger(chars[i]); + + font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, values, glyphCount); + + RAYGUI_FREE(values); + } + } + else font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, NULL, 0); + + if ((font.texture.id > 0) && (font.glyphCount > 0)) GuiSetFont(font); + + } break; + default: break; + } + + fgets(buffer, 256, rgsFile); + } + } + else tryBinary = true; + + fclose(rgsFile); + } + + if (tryBinary) + { + rgsFile = fopen(fileName, "rb"); + + if (rgsFile == NULL) return; + + char signature[5] = ""; + short version = 0; + short reserved = 0; + int propertyCount = 0; + + fread(signature, 1, 4, rgsFile); + fread(&version, 1, sizeof(short), rgsFile); + fread(&reserved, 1, sizeof(short), rgsFile); + fread(&propertyCount, 1, sizeof(int), rgsFile); + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'S') && + (signature[3] == ' ')) + { + short controlId = 0; + short propertyId = 0; + int propertyValue = 0; + + for (int i = 0; i < propertyCount; i++) + { + fread(&controlId, 1, sizeof(short), rgsFile); + fread(&propertyId, 1, sizeof(short), rgsFile); + fread(&propertyValue, 1, sizeof(int), rgsFile); + + if (controlId == 0) // DEFAULT control + { + // If a DEFAULT property is loaded, it is propagated to all controls + // NOTE: All DEFAULT properties should be defined first in the file + GuiSetStyle(0, (int)propertyId, propertyValue); + + if (propertyId < RAYGUI_MAX_PROPS_BASE) for (int i = 1; i < RAYGUI_MAX_CONTROLS; i++) GuiSetStyle(i, (int)propertyId, propertyValue); + } + else GuiSetStyle((int)controlId, (int)propertyId, propertyValue); + } + + // Font loading is highly dependant on raylib API to load font data and image +#if !defined(RAYGUI_STANDALONE) + // Load custom font if available + int fontDataSize = 0; + fread(&fontDataSize, 1, sizeof(int), rgsFile); + + if (fontDataSize > 0) + { + Font font = { 0 }; + int fontType = 0; // 0-Normal, 1-SDF + Rectangle whiteRec = { 0 }; + + fread(&font.baseSize, 1, sizeof(int), rgsFile); + fread(&font.glyphCount, 1, sizeof(int), rgsFile); + fread(&fontType, 1, sizeof(int), rgsFile); + + // Load font white rectangle + fread(&whiteRec, 1, sizeof(Rectangle), rgsFile); + + // Load font image parameters + int fontImageSize = 0; + fread(&fontImageSize, 1, sizeof(int), rgsFile); + + if (fontImageSize > 0) + { + Image imFont = { 0 }; + imFont.mipmaps = 1; + fread(&imFont.width, 1, sizeof(int), rgsFile); + fread(&imFont.height, 1, sizeof(int), rgsFile); + fread(&imFont.format, 1, sizeof(int), rgsFile); + + imFont.data = (unsigned char *)RAYGUI_MALLOC(fontImageSize); + fread(imFont.data, 1, fontImageSize, rgsFile); + + font.texture = LoadTextureFromImage(imFont); + + RAYGUI_FREE(imFont.data); + } + + // Load font recs data + font.recs = (Rectangle *)RAYGUI_CALLOC(font.glyphCount, sizeof(Rectangle)); + for (int i = 0; i < font.glyphCount; i++) fread(&font.recs[i], 1, sizeof(Rectangle), rgsFile); + + // Load font chars info data + font.glyphs = (GlyphInfo *)RAYGUI_CALLOC(font.glyphCount, sizeof(GlyphInfo)); + for (int i = 0; i < font.glyphCount; i++) + { + fread(&font.glyphs[i].value, 1, sizeof(int), rgsFile); + fread(&font.glyphs[i].offsetX, 1, sizeof(int), rgsFile); + fread(&font.glyphs[i].offsetY, 1, sizeof(int), rgsFile); + fread(&font.glyphs[i].advanceX, 1, sizeof(int), rgsFile); + } + + GuiSetFont(font); + + // Set font texture source rectangle to be used as white texture to draw shapes + // NOTE: This way, all gui can be draw using a single draw call + if ((whiteRec.width != 0) && (whiteRec.height != 0)) SetShapesTexture(font.texture, whiteRec); + } +#endif + } + + fclose(rgsFile); + } +} + +// Load style default over global style +void GuiLoadStyleDefault(void) +{ + // We set this variable first to avoid cyclic function calls + // when calling GuiSetStyle() and GuiGetStyle() + guiStyleLoaded = true; + + // Initialize default LIGHT style property values + GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, 0x838383ff); + GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, 0xc9c9c9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, 0x686868ff); + GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, 0x5bb2d9ff); + GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, 0xc9effeff); + GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, 0x6c9bbcff); + GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, 0x0492c7ff); + GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, 0x97e8ffff); + GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, 0x368bafff); + GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, 0xb5c1c2ff); + GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, 0xe6e9e9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, 0xaeb7b8ff); + GuiSetStyle(DEFAULT, BORDER_WIDTH, 1); // WARNING: Some controls use other values + GuiSetStyle(DEFAULT, TEXT_PADDING, 0); // WARNING: Some controls use other values + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); // WARNING: Some controls use other values + + // Initialize control-specific property values + // NOTE: Those properties are in default list but require specific values by control type + GuiSetStyle(LABEL, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_LEFT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 2); + GuiSetStyle(SLIDER, TEXT_PADDING, 5); + GuiSetStyle(CHECKBOX, TEXT_PADDING, 5); + GuiSetStyle(CHECKBOX, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_RIGHT); + GuiSetStyle(TEXTBOX, TEXT_PADDING, 5); + GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_LEFT); + GuiSetStyle(VALUEBOX, TEXT_PADDING, 4); + GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_LEFT); + GuiSetStyle(SPINNER, TEXT_PADDING, 4); + GuiSetStyle(SPINNER, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_LEFT); + GuiSetStyle(STATUSBAR, TEXT_PADDING, 6); + GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_LEFT); + + // Initialize extended property values + // NOTE: By default, extended property values are initialized to 0 + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, LINE_COLOR, 0x90abb5ff); // DEFAULT specific property + GuiSetStyle(DEFAULT, BACKGROUND_COLOR, 0xf5f5f5ff); // DEFAULT specific property + GuiSetStyle(TOGGLE, GROUP_PADDING, 2); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 15); + GuiSetStyle(SLIDER, SLIDER_PADDING, 1); + GuiSetStyle(PROGRESSBAR, PROGRESS_PADDING, 1); + GuiSetStyle(CHECKBOX, CHECK_PADDING, 1); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_WIDTH, 30); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_PADDING, 2); + GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); + GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITERL_PADDING, 2); + GuiSetStyle(TEXTBOX, TEXT_LINES_PADDING, 5); + GuiSetStyle(TEXTBOX, TEXT_INNER_PADDING, 4); + GuiSetStyle(TEXTBOX, COLOR_SELECTED_FG, 0xf0fffeff); + GuiSetStyle(TEXTBOX, COLOR_SELECTED_BG, 0x839affe0); + GuiSetStyle(SPINNER, SPIN_BUTTON_WIDTH, 20); + GuiSetStyle(SPINNER, SPIN_BUTTON_PADDING, 2); + GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); + GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); + GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, 16); + GuiSetStyle(SCROLLBAR, SCROLL_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 10); + GuiSetStyle(LISTVIEW, LIST_ITERL_HEIGHT, 0x1e); + GuiSetStyle(LISTVIEW, LIST_ITERL_PADDING, 2); + GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 10); + GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); + GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 6); + GuiSetStyle(COLORPICKER, HUEBAR_WIDTH, 0x14); + GuiSetStyle(COLORPICKER, HUEBAR_PADDING, 0xa); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT, 6); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW, 2); + + guiFont = GetFontDefault(); // Initialize default font +} + +// Get text with icon id prepended +// NOTE: Useful to add icons by name id (enum) instead of +// a number that can change between ricon versions +const char *GuiIconText(int iconId, const char *text) +{ +#if defined(RAYGUI_NO_RICONS) + return NULL; +#else + static char buffer[1024] = { 0 }; + memset(buffer, 0, 1024); + + sprintf(buffer, "#%03i#", iconId); + + if (text != NULL) + { + for (int i = 5; i < 1024; i++) + { + buffer[i] = text[i - 5]; + if (text[i - 5] == '\0') break; + } + } + + return buffer; +#endif +} + +#if !defined(RAYGUI_NO_RICONS) + +// Get full icons data pointer +unsigned int *GuiGetIcons(void) { return guiIcons; } + +// Load raygui icons file (.rgi) +// NOTE: In case nameIds are required, they can be requested with loadIconsName, +// they are returned as a guiIconsName[iconCount][RICON_MAX_NAME_LENGTH], +// WARNING: guiIconsName[]][] memory should be manually freed! +char **GuiLoadIcons(const char *fileName, bool loadIconsName) +{ + // Style File Structure (.rgi) + // ------------------------------------------------------ + // Offset | Size | Type | Description + // ------------------------------------------------------ + // 0 | 4 | char | Signature: "rGI " + // 4 | 2 | short | Version: 100 + // 6 | 2 | short | reserved + + // 8 | 2 | short | Num icons (N) + // 10 | 2 | short | Icons size (Options: 16, 32, 64) (S) + + // Icons name id (32 bytes per name id) + // foreach (icon) + // { + // 12+32*i | 32 | char | Icon NameId + // } + + // Icons data: One bit per pixel, stored as unsigned int array (depends on icon size) + // S*S pixels/32bit per unsigned int = K unsigned int per icon + // foreach (icon) + // { + // ... | K | unsigned int | Icon Data + // } + + FILE *rgiFile = fopen(fileName, "rb"); + + char **guiIconsName = NULL; + + if (rgiFile != NULL) + { + char signature[5] = ""; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + fread(signature, 1, 4, rgiFile); + fread(&version, 1, sizeof(short), rgiFile); + fread(&reserved, 1, sizeof(short), rgiFile); + fread(&iconCount, 1, sizeof(short), rgiFile); + fread(&iconSize, 1, sizeof(short), rgiFile); + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char **)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_MALLOC(RICON_MAX_NAME_LENGTH); + fread(guiIconsName[i], RICON_MAX_NAME_LENGTH, 1, rgiFile); + } + } + else fseek(rgiFile, iconCount*RICON_MAX_NAME_LENGTH, SEEK_CUR); + + // Read icons data directly over guiIcons data array + fread(guiIcons, iconCount*(iconSize*iconSize/32), sizeof(unsigned int), rgiFile); + } + + fclose(rgiFile); + } + + return guiIconsName; +} + +// Draw selected icon using rectangles pixel-by-pixel +void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) +{ + #define BIT_CHECK(a,b) ((a) & (1<<(b))) + + for (int i = 0, y = 0; i < RICON_SIZE*RICON_SIZE/32; i++) + { + for (int k = 0; k < 32; k++) + { + if (BIT_CHECK(guiIcons[iconId*RICON_DATA_ELEMENTS + i], k)) + { + #if !defined(RAYGUI_STANDALONE) + DrawRectangle(posX + (k%RICON_SIZE)*pixelSize, posY + y*pixelSize, pixelSize, pixelSize, color); + #endif + } + + if ((k == 15) || (k == 31)) y++; + } + } +} + +// Get icon bit data +// NOTE: Bit data array grouped as unsigned int (ICON_SIZE*ICON_SIZE/32 elements) +unsigned int *GuiGetIconData(int iconId) +{ + static unsigned int iconData[RICON_DATA_ELEMENTS] = { 0 }; + memset(iconData, 0, RICON_DATA_ELEMENTS*sizeof(unsigned int)); + + if (iconId < RICON_MAX_ICONS) memcpy(iconData, &guiIcons[iconId*RICON_DATA_ELEMENTS], RICON_DATA_ELEMENTS*sizeof(unsigned int)); + + return iconData; +} + +// Set icon bit data +// NOTE: Data must be provided as unsigned int array (ICON_SIZE*ICON_SIZE/32 elements) +void GuiSetIconData(int iconId, unsigned int *data) +{ + if (iconId < RICON_MAX_ICONS) memcpy(&guiIcons[iconId*RICON_DATA_ELEMENTS], data, RICON_DATA_ELEMENTS*sizeof(unsigned int)); +} + +// Set icon pixel value +void GuiSetIconPixel(int iconId, int x, int y) +{ + #define BIT_SET(a,b) ((a) |= (1<<(b))) + + // This logic works for any RICON_SIZE pixels icons, + // For example, in case of 16x16 pixels, every 2 lines fit in one unsigned int data element + BIT_SET(guiIcons[iconId*RICON_DATA_ELEMENTS + y/(sizeof(unsigned int)*8/RICON_SIZE)], x + (y%(sizeof(unsigned int)*8/RICON_SIZE)*RICON_SIZE)); +} + +// Clear icon pixel value +void GuiClearIconPixel(int iconId, int x, int y) +{ + #define BIT_CLEAR(a,b) ((a) &= ~((1)<<(b))) + + // This logic works for any RICON_SIZE pixels icons, + // For example, in case of 16x16 pixels, every 2 lines fit in one unsigned int data element + BIT_CLEAR(guiIcons[iconId*RICON_DATA_ELEMENTS + y/(sizeof(unsigned int)*8/RICON_SIZE)], x + (y%(sizeof(unsigned int)*8/RICON_SIZE)*RICON_SIZE)); +} + +// Check icon pixel value +bool GuiCheckIconPixel(int iconId, int x, int y) +{ + #define BIT_CHECK(a,b) ((a) & (1<<(b))) + + return (BIT_CHECK(guiIcons[iconId*8 + y/2], x + (y%2*16))); +} +#endif // !RAYGUI_NO_RICONS + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +// Gui get text width using default font +// NOTE: Icon is not considered here +static int GetTextWidth(const char *text) +{ + Vector2 size = { 0 }; + + if ((text != NULL) && (text[0] != '\0')) + { + size = MeasureTextEx(guiFont, text, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + + return (int)size.x; +} + +// Get text bounds considering control bounds +static Rectangle GetTextBounds(int control, Rectangle bounds) +{ + Rectangle textBounds = bounds; + + textBounds.x = bounds.x + GuiGetStyle(control, BORDER_WIDTH); + textBounds.y = bounds.y + GuiGetStyle(control, BORDER_WIDTH); + textBounds.width = bounds.width - 2*GuiGetStyle(control, BORDER_WIDTH); + textBounds.height = bounds.height - 2*GuiGetStyle(control, BORDER_WIDTH); + + // Consider TEXT_PADDING properly, depends on control type and TEXT_ALIGNMENT + switch (control) + { + case COMBOBOX: bounds.width -= (GuiGetStyle(control, COMBO_BUTTON_WIDTH) + GuiGetStyle(control, COMBO_BUTTON_PADDING)); break; + case VALUEBOX: break; // NOTE: ValueBox text value always centered, text padding applies to label + default: + { + if (GuiGetStyle(control, TEXT_ALIGNMENT) == GUI_TEXT_ALIGN_RIGHT) textBounds.x -= GuiGetStyle(control, TEXT_PADDING); + else textBounds.x += GuiGetStyle(control, TEXT_PADDING); + } break; + } + + // TODO: Special cases (no label): COMBOBOX, DROPDOWNBOX, LISTVIEW (scrollbar?) + // More special cases (label on side): CHECKBOX, SLIDER, VALUEBOX, SPINNER + + return textBounds; +} + +// Get text icon if provided and move text cursor +// NOTE: We support up to 999 values for iconId +static const char *GetTextIcon(const char *text, int *iconId) +{ +#if !defined(RAYGUI_NO_RICONS) + *iconId = -1; + if (text[0] == '#') // Maybe we have an icon! + { + char iconValue[4] = { 0 }; // Maximum length for icon value: 3 digits + '\0' + + int pos = 1; + while ((pos < 4) && (text[pos] >= '0') && (text[pos] <= '9')) + { + iconValue[pos - 1] = text[pos]; + pos++; + } + + if (text[pos] == '#') + { + *iconId = TextToInteger(iconValue); + + // Move text pointer after icon + // WARNING: If only icon provided, it could point to EOL character: '\0' + if (*iconId >= 0) text += (pos + 1); + } + } +#endif + + return text; +} + +// Gui draw text using default font +static void GuiDrawText(const char *text, Rectangle bounds, int alignment, Color tint) +{ + #define TEXT_VALIGN_PIXEL_OFFSET(h) ((int)h%2) // Vertical alignment for pixel perfect + + if ((text != NULL) && (text[0] != '\0')) + { + int iconId = 0; + text = GetTextIcon(text, &iconId); // Check text for icon and move cursor + + // Get text position depending on alignment and iconId + //--------------------------------------------------------------------------------- + #define RICON_TEXT_PADDING 4 + + Vector2 position = { bounds.x, bounds.y }; + + // NOTE: We get text size after icon has been processed + int textWidth = GetTextWidth(text); + int textHeight = GuiGetStyle(DEFAULT, TEXT_SIZE); + + // If text requires an icon, add size to measure + if (iconId >= 0) + { + textWidth += RICON_SIZE; + + // WARNING: If only icon provided, text could be pointing to EOF character: '\0' + if ((text != NULL) && (text[0] != '\0')) textWidth += RICON_TEXT_PADDING; + } + + // Check guiTextAlign global variables + switch (alignment) + { + case GUI_TEXT_ALIGN_LEFT: + { + position.x = bounds.x; + position.y = bounds.y + bounds.height/2 - textHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(bounds.height); + } break; + case GUI_TEXT_ALIGN_CENTER: + { + position.x = bounds.x + bounds.width/2 - textWidth/2; + position.y = bounds.y + bounds.height/2 - textHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(bounds.height); + } break; + case GUI_TEXT_ALIGN_RIGHT: + { + position.x = bounds.x + bounds.width - textWidth; + position.y = bounds.y + bounds.height/2 - textHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(bounds.height); + } break; + default: break; + } + + // NOTE: Make sure we get pixel-perfect coordinates, + // In case of decimals we got weird text positioning + position.x = (float)((int)position.x); + position.y = (float)((int)position.y); + //--------------------------------------------------------------------------------- + + // Draw text (with icon if available) + //--------------------------------------------------------------------------------- +#if !defined(RAYGUI_NO_RICONS) + if (iconId >= 0) + { + // NOTE: We consider icon height, probably different than text size + GuiDrawIcon(iconId, (int)position.x, (int)(bounds.y + bounds.height/2 - RICON_SIZE/2 + TEXT_VALIGN_PIXEL_OFFSET(bounds.height)), 1, tint); + position.x += (RICON_SIZE + RICON_TEXT_PADDING); + } +#endif + DrawTextEx(guiFont, text, position, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING), tint); + //--------------------------------------------------------------------------------- + } +} + +// Gui draw rectangle using default raygui plain style with borders +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color) +{ + if (color.a > 0) + { + // Draw rectangle filled with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, color); + } + + if (borderWidth > 0) + { + // Draw rectangle border lines with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, borderWidth, borderColor); + DrawRectangle((int)rec.x, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, borderColor); + DrawRectangle((int)rec.x + (int)rec.width - borderWidth, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, borderColor); + DrawRectangle((int)rec.x, (int)rec.y + (int)rec.height - borderWidth, (int)rec.width, borderWidth, borderColor); + } +} + +// Split controls text into multiple strings +// Also check for multiple columns (required by GuiToggleGroup()) +static const char **GuiTextSplit(const char *text, int *count, int *textRow) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by TEXTSPLIT_MAX_TEXT_ELEMENTS + // 2. Maximum size of text to split is TEXTSPLIT_MAX_TEXT_LENGTH + // NOTE: Those definitions could be externally provided if required + + #if !defined(TEXTSPLIT_MAX_TEXT_LENGTH) + #define TEXTSPLIT_MAX_TEXT_LENGTH 1024 + #endif + + #if !defined(TEXTSPLIT_MAX_TEXT_ELEMENTS) + #define TEXTSPLIT_MAX_TEXT_ELEMENTS 128 + #endif + + static const char *result[TEXTSPLIT_MAX_TEXT_ELEMENTS] = { NULL }; + static char buffer[TEXTSPLIT_MAX_TEXT_LENGTH] = { 0 }; + memset(buffer, 0, TEXTSPLIT_MAX_TEXT_LENGTH); + + result[0] = buffer; + int counter = 1; + + if (textRow != NULL) textRow[0] = 0; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < TEXTSPLIT_MAX_TEXT_LENGTH; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if ((buffer[i] == ';') || (buffer[i] == '\n')) + { + result[counter] = buffer + i + 1; + + if (textRow != NULL) + { + if (buffer[i] == '\n') textRow[counter] = textRow[counter - 1] + 1; + else textRow[counter] = textRow[counter - 1]; + } + + buffer[i] = '\0'; // Set an end of string at this point + + counter++; + if (counter == TEXTSPLIT_MAX_TEXT_ELEMENTS) break; + } + } + + *count = counter; + + return result; +} + +// Convert color data from RGB to HSV +// NOTE: Color data should be passed normalized +static Vector3 ConvertRGBtoHSV(Vector3 rgb) +{ + Vector3 hsv = { 0 }; + float min = 0.0f; + float max = 0.0f; + float delta = 0.0f; + + min = (rgb.x < rgb.y)? rgb.x : rgb.y; + min = (min < rgb.z)? min : rgb.z; + + max = (rgb.x > rgb.y)? rgb.x : rgb.y; + max = (max > rgb.z)? max : rgb.z; + + hsv.z = max; // Value + delta = max - min; + + if (delta < 0.00001f) + { + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + if (max > 0.0f) + { + // NOTE: If max is 0, this divide would cause a crash + hsv.y = (delta/max); // Saturation + } + else + { + // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + // NOTE: Comparing float values could not work properly + if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta + else + { + if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow + else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan + } + + hsv.x *= 60.0f; // Convert to degrees + + if (hsv.x < 0.0f) hsv.x += 360.0f; + + return hsv; +} + +// Convert color data from HSV to RGB +// NOTE: Color data should be passed normalized +static Vector3 ConvertHSVtoRGB(Vector3 hsv) +{ + Vector3 rgb = { 0 }; + float hh = 0.0f, p = 0.0f, q = 0.0f, t = 0.0f, ff = 0.0f; + long i = 0; + + // NOTE: Comparing float values could not work properly + if (hsv.y <= 0.0f) + { + rgb.x = hsv.z; + rgb.y = hsv.z; + rgb.z = hsv.z; + return rgb; + } + + hh = hsv.x; + if (hh >= 360.0f) hh = 0.0f; + hh /= 60.0f; + + i = (long)hh; + ff = hh - i; + p = hsv.z*(1.0f - hsv.y); + q = hsv.z*(1.0f - (hsv.y*ff)); + t = hsv.z*(1.0f - (hsv.y*(1.0f - ff))); + + switch (i) + { + case 0: + { + rgb.x = hsv.z; + rgb.y = t; + rgb.z = p; + } break; + case 1: + { + rgb.x = q; + rgb.y = hsv.z; + rgb.z = p; + } break; + case 2: + { + rgb.x = p; + rgb.y = hsv.z; + rgb.z = t; + } break; + case 3: + { + rgb.x = p; + rgb.y = q; + rgb.z = hsv.z; + } break; + case 4: + { + rgb.x = t; + rgb.y = p; + rgb.z = hsv.z; + } break; + case 5: + default: + { + rgb.x = hsv.z; + rgb.y = p; + rgb.z = q; + } break; + } + + return rgb; +} + +#if defined(RAYGUI_STANDALONE) +// Returns a Color struct from hexadecimal value +static Color GetColor(int hexValue) +{ + Color color; + + color.r = (unsigned char)(hexValue >> 24) & 0xFF; + color.g = (unsigned char)(hexValue >> 16) & 0xFF; + color.b = (unsigned char)(hexValue >> 8) & 0xFF; + color.a = (unsigned char)hexValue & 0xFF; + + return color; +} + +// Returns hexadecimal value for a Color +static int ColorToInt(Color color) +{ + return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); +} + +// Check if point is inside rectangle +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec) +{ + bool collision = false; + + if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && + (point.y >= rec.y) && (point.y <= (rec.y + rec.height))) collision = true; + + return collision; +} + +// Color fade-in or fade-out, alpha goes from 0.0f to 1.0f +static Color Fade(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + Color result = { color.r, color.g, color.b, (unsigned char)(255.0f*alpha) }; + + return result; +} + +// Formatting of text with variables to 'embed' +static const char *TextFormat(const char *text, ...) +{ + #define MAX_FORMATTEXT_LENGTH 64 + + static char buffer[MAX_FORMATTEXT_LENGTH]; + + va_list args; + va_start(args, text); + vsprintf(buffer, text, args); + va_end(args); + + return buffer; +} + +// Draw rectangle with vertical gradient fill color +// NOTE: This function is only used by GuiColorPicker() +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) +{ + Rectangle bounds = { (float)posX, (float)posY, (float)width, (float)height }; + DrawRectangleGradientEx(bounds, color1, color2, color2, color1); +} + +#define TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH 1024 // Size of static buffer: TextSplit() +#define TEXTSPLIT_MAX_SUBSTRINGS_COUNT 128 // Size of static pointers array: TextSplit() + +// Split string into multiple strings +const char **TextSplit(const char *text, char delimiter, int *count) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by TEXTSPLIT_MAX_SUBSTRINGS_COUNT + // 2. Maximum size of text to split is TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH + + static const char *result[TEXTSPLIT_MAX_SUBSTRINGS_COUNT] = { NULL }; + static char buffer[TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH] = { 0 }; + memset(buffer, 0, TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH); + + result[0] = buffer; + int counter = 0; + + if (text != NULL) + { + counter = 1; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if (buffer[i] == delimiter) + { + buffer[i] = '\0'; // Set an end of string at this point + result[counter] = buffer + i + 1; + counter++; + + if (counter == TEXTSPLIT_MAX_SUBSTRINGS_COUNT) break; + } + } + } + + *count = counter; + return result; +} + +// Get integer value from text +// NOTE: This function replaces atoi() [stdlib.h] +static int TextToInteger(const char *text) +{ + int value = 0; + int sign = 1; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1; + text++; + } + + for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); ++i) value = value*10 + (int)(text[i] - '0'); + + return value*sign; +} + +// Encode codepoint into UTF-8 text (char array size returned as parameter) +static const char *CodepointToUTF8(int codepoint, int *byteSize) +{ + static char utf8[6] = { 0 }; + int size = 0; + + if (codepoint <= 0x7f) + { + utf8[0] = (char)codepoint; + size = 1; + } + else if (codepoint <= 0x7ff) + { + utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); + utf8[1] = (char)((codepoint & 0x3f) | 0x80); + size = 2; + } + else if (codepoint <= 0xffff) + { + utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); + utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[2] = (char)((codepoint & 0x3f) | 0x80); + size = 3; + } + else if (codepoint <= 0x10ffff) + { + utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); + utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); + utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[3] = (char)((codepoint & 0x3f) | 0x80); + size = 4; + } + + *byteSize = size; + + return utf8; +} + +// Get next codepoint in a UTF-8 encoded text, scanning until '\0' is found +// When a invalid UTF-8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned +// Total number of bytes processed are returned as a parameter +// NOTE: the standard says U+FFFD should be returned in case of errors +// but that character is not supported by the default font in raylib +static int GetCodepoint(const char *text, int *bytesProcessed) +{ +/* + UTF-8 specs from https://www.ietf.org/rfc/rfc3629.txt + + Char. number range | UTF-8 octet sequence + (hexadecimal) | (binary) + --------------------+--------------------------------------------- + 0000 0000-0000 007F | 0xxxxxxx + 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx +*/ + // NOTE: on decode errors we return as soon as possible + + int code = 0x3f; // Codepoint (defaults to '?') + int octet = (unsigned char)(text[0]); // The first UTF8 octet + *bytesProcessed = 1; + + if (octet <= 0x7f) + { + // Only one octet (ASCII range x00-7F) + code = text[0]; + } + else if ((octet & 0xe0) == 0xc0) + { + // Two octets + + // [0]xC2-DF [1]UTF8-tail(x80-BF) + unsigned char octet1 = text[1]; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + if ((octet >= 0xc2) && (octet <= 0xdf)) + { + code = ((octet & 0x1f) << 6) | (octet1 & 0x3f); + *bytesProcessed = 2; + } + } + else if ((octet & 0xf0) == 0xe0) + { + // Three octets + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + octet2 = text[2]; + + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence + + // [0]xE0 [1]xA0-BF [2]UTF8-tail(x80-BF) + // [0]xE1-EC [1]UTF8-tail [2]UTF8-tail(x80-BF) + // [0]xED [1]x80-9F [2]UTF8-tail(x80-BF) + // [0]xEE-EF [1]UTF8-tail [2]UTF8-tail(x80-BF) + + if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) || + ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f)))) { *bytesProcessed = 2; return code; } + + if ((octet >= 0xe0) && (0 <= 0xef)) + { + code = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f); + *bytesProcessed = 3; + } + } + else if ((octet & 0xf8) == 0xf0) + { + // Four octets + if (octet > 0xf4) return code; + + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; + unsigned char octet3 = '\0'; + + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence + + octet2 = text[2]; + + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence + + octet3 = text[3]; + + if ((octet3 == '\0') || ((octet3 >> 6) != 2)) { *bytesProcessed = 4; return code; } // Unexpected sequence + + // [0]xF0 [1]x90-BF [2]UTF8-tail [3]UTF8-tail + // [0]xF1-F3 [1]UTF8-tail [2]UTF8-tail [3]UTF8-tail + // [0]xF4 [1]x80-8F [2]UTF8-tail [3]UTF8-tail + + if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) || + ((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f)))) { *bytesProcessed = 2; return code; } // Unexpected sequence + + if (octet >= 0xf0) + { + code = ((octet & 0x7) << 18) | ((octet1 & 0x3f) << 12) | ((octet2 & 0x3f) << 6) | (octet3 & 0x3f); + *bytesProcessed = 4; + } + } + + if (code > 0x10ffff) code = 0x3f; // Codepoints after U+10ffff are invalid + + return code; +} +#endif // RAYGUI_STANDALONE + +#endif // RAYGUI_IMPLEMENTATION diff --git a/include/raylib.h b/include/raylib.h new file mode 100644 index 0000000..1a9ecce --- /dev/null +++ b/include/raylib.h @@ -0,0 +1,1538 @@ +/********************************************************************************************** +* +* raylib v4.0 - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) +* +* FEATURES: +* - NO external dependencies, all required libraries included with raylib +* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, +* MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5. +* - Written in plain C code (C99) in PascalCase/camelCase notation +* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3 or ES2 - choose at compile) +* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] +* - Multiple Fonts formats supported (TTF, XNA fonts, AngelCode fonts) +* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) +* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! +* - Flexible Materials system, supporting classic maps and PBR maps +* - Animated 3D models supported (skeletal bones animation) (IQM) +* - Shaders support, including Model shaders and Postprocessing shaders +* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] +* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, XM, MOD) +* - VR stereo rendering with configurable HMD device parameters +* - Bindings to multiple programming languages available! +* +* NOTES: +* - One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] +* - One default Texture2D is loaded on rlglInit(), 1x1 white pixel R8G8B8A8 [rlgl] (OpenGL 3.3 or ES2) +* - One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) +* - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) +* +* DEPENDENCIES (included): +* [rcore] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input (PLATFORM_DESKTOP) +* [rlgl] glad (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading (PLATFORM_DESKTOP) +* [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management +* +* OPTIONAL DEPENDENCIES (included): +* [rcore] msf_gif (Miles Fogle) for GIF recording +* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorythm +* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorythm +* [rtextures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) +* [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG) +* [rtextures] stb_image_resize (Sean Barret) for image resizing algorithms +* [rtext] stb_truetype (Sean Barret) for ttf fonts loading +* [rtext] stb_rect_pack (Sean Barret) for rectangles packing +* [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation +* [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) +* [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF) +* [raudio] dr_wav (David Reid) for WAV audio file loading +* [raudio] dr_flac (David Reid) for FLAC audio file loading +* [raudio] dr_mp3 (David Reid) for MP3 audio file loading +* [raudio] stb_vorbis (Sean Barret) for OGG audio loading +* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading +* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading +* +* +* LICENSE: zlib/libpng +* +* raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software: +* +* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYLIB_H +#define RAYLIB_H + +#include // Required for: va_list - Only used by TraceLogCallback + +#define RAYLIB_VERSION "4.0" + +// Function specifiers in case library is build/used as a shared library (Windows) +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +#if defined(_WIN32) + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif +#endif + +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Some basic Defines +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) +#endif + +// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) +// Plain structures in C++ (without constructors) can be initialized with { } +#if defined(__cplusplus) + #define CLITERAL(type) type +#else + #define CLITERAL(type) (type) +#endif + +// NOTE: We set some defines with some data types declared by raylib +// Other modules (raymath, rlgl) also require some of those types, so, +// to be able to use those other modules as standalone (not depending on raylib) +// this defines are very useful for internal check and avoid type (re)definitions +#define RL_COLOR_TYPE +#define RL_RECTANGLE_TYPE +#define RL_VECTOR2_TYPE +#define RL_VECTOR3_TYPE +#define RL_VECTOR4_TYPE +#define RL_QUATERNION_TYPE +#define RL_MATRIX_TYPE + +// Some Basic Colors +// NOTE: Custom raylib color palette for amazing visuals on WHITE background +#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray +#define GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray +#define DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray +#define YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow +#define GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold +#define ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange +#define PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink +#define RED CLITERAL(Color){ 230, 41, 55, 255 } // Red +#define MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon +#define GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green +#define LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime +#define DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green +#define SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue +#define BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue +#define DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue +#define PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple +#define VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet +#define DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple +#define BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige +#define BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown +#define DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown + +#define WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White +#define BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black +#define BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) +#define MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta +#define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) + +//---------------------------------------------------------------------------------- +// Structures Definition +//---------------------------------------------------------------------------------- +// Boolean type +#if defined(__STDC__) && __STDC_VERSION__ >= 199901L + #include +#elif !defined(__cplusplus) && !defined(bool) + typedef enum bool { false, true } bool; + #define RL_BOOL_TYPE +#endif + +// Vector2, 2 components +typedef struct Vector2 { + float x; // Vector x component + float y; // Vector y component +} Vector2; + +// Vector3, 3 components +typedef struct Vector3 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component +} Vector3; + +// Vector4, 4 components +typedef struct Vector4 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component + float w; // Vector w component +} Vector4; + +// Quaternion, 4 components (Vector4 alias) +typedef Vector4 Quaternion; + +// Matrix, 4x4 components, column major, OpenGL style, right handed +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; + +// Color, 4 components, R8G8B8A8 (32bit) +typedef struct Color { + unsigned char r; // Color red value + unsigned char g; // Color green value + unsigned char b; // Color blue value + unsigned char a; // Color alpha value +} Color; + +// Rectangle, 4 components +typedef struct Rectangle { + float x; // Rectangle top-left corner position x + float y; // Rectangle top-left corner position y + float width; // Rectangle width + float height; // Rectangle height +} Rectangle; + +// Image, pixel data stored in CPU memory (RAM) +typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Image; + +// Texture, tex data stored in GPU memory (VRAM) +typedef struct Texture { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Texture; + +// Texture2D, same as Texture +typedef Texture Texture2D; + +// TextureCubemap, same as Texture +typedef Texture TextureCubemap; + +// RenderTexture, fbo for texture rendering +typedef struct RenderTexture { + unsigned int id; // OpenGL framebuffer object id + Texture texture; // Color buffer attachment texture + Texture depth; // Depth buffer attachment texture +} RenderTexture; + +// RenderTexture2D, same as RenderTexture +typedef RenderTexture RenderTexture2D; + +// NPatchInfo, n-patch layout info +typedef struct NPatchInfo { + Rectangle source; // Texture source rectangle + int left; // Left border offset + int top; // Top border offset + int right; // Right border offset + int bottom; // Bottom border offset + int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 +} NPatchInfo; + +// GlyphInfo, font characters glyphs info +typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data +} GlyphInfo; + +// Font, font texture and GlyphInfo array data +typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of glyph characters + int glyphPadding; // Padding around the glyph characters + Texture2D texture; // Texture atlas containing the glyphs + Rectangle *recs; // Rectangles in texture for the glyphs + GlyphInfo *glyphs; // Glyphs info data +} Font; + +// Camera, defines position/orientation in 3d space +typedef struct Camera3D { + Vector3 position; // Camera position + Vector3 target; // Camera target it looks-at + Vector3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view apperture in Y (degrees) in perspective, used as near plane width in orthographic + int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC +} Camera3D; + +typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + +// Camera2D, defines position/orientation in 2d space +typedef struct Camera2D { + Vector2 offset; // Camera offset (displacement from target) + Vector2 target; // Camera target (rotation and zoom origin) + float rotation; // Camera rotation in degrees + float zoom; // Camera zoom (scaling), should be 1.0f by default +} Camera2D; + +// Mesh, vertex data and vao/vbo +typedef struct Mesh { + int vertexCount; // Number of vertices stored in arrays + int triangleCount; // Number of triangles stored (indexed or not) + + // Vertex attributes data + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *texcoords2; // Vertex second texture coordinates (useful for lightmaps) (shader-location = 5) + float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) + float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) + + // Animation vertex data + float *animVertices; // Animated vertex positions (after bones transformations) + float *animNormals; // Animated normals (after bones transformations) + unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) + float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) + + // OpenGL identifiers + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) +} Mesh; + +// Shader +typedef struct Shader { + unsigned int id; // Shader program id + int *locs; // Shader locations array (RL_MAX_SHADER_LOCATIONS) +} Shader; + +// MaterialMap +typedef struct MaterialMap { + Texture2D texture; // Material map texture + Color color; // Material map color + float value; // Material map value +} MaterialMap; + +// Material, includes shader and maps +typedef struct Material { + Shader shader; // Material shader + MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) + float params[4]; // Material generic parameters (if required) +} Material; + +// Transform, vectex transformation data +typedef struct Transform { + Vector3 translation; // Translation + Quaternion rotation; // Rotation + Vector3 scale; // Scale +} Transform; + +// Bone, skeletal animation bone +typedef struct BoneInfo { + char name[32]; // Bone name + int parent; // Bone parent +} BoneInfo; + +// Model, meshes, materials and animation data +typedef struct Model { + Matrix transform; // Local transform matrix + + int meshCount; // Number of meshes + int materialCount; // Number of materials + Mesh *meshes; // Meshes array + Material *materials; // Materials array + int *meshMaterial; // Mesh material number + + // Animation data + int boneCount; // Number of bones + BoneInfo *bones; // Bones information (skeleton) + Transform *bindPose; // Bones base transformation (pose) +} Model; + +// ModelAnimation +typedef struct ModelAnimation { + int boneCount; // Number of bones + int frameCount; // Number of animation frames + BoneInfo *bones; // Bones information (skeleton) + Transform **framePoses; // Poses array by frame +} ModelAnimation; + +// Ray, ray for raycasting +typedef struct Ray { + Vector3 position; // Ray position (origin) + Vector3 direction; // Ray direction +} Ray; + +// RayCollision, ray hit information +typedef struct RayCollision { + bool hit; // Did the ray hit something? + float distance; // Distance to nearest hit + Vector3 point; // Point of nearest hit + Vector3 normal; // Surface normal of hit +} RayCollision; + +// BoundingBox +typedef struct BoundingBox { + Vector3 min; // Minimum vertex box-corner + Vector3 max; // Maximum vertex box-corner +} BoundingBox; + +// Wave, audio wave data +typedef struct Wave { + unsigned int frameCount; // Total number of frames (considering channels) + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) + void *data; // Buffer data pointer +} Wave; + +typedef struct rAudioBuffer rAudioBuffer; + +// AudioStream, custom audio stream +typedef struct AudioStream { + rAudioBuffer *buffer; // Pointer to internal data used by the audio system + + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) +} AudioStream; + +// Sound +typedef struct Sound { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) +} Sound; + +// Music, audio stream, anything longer than ~10 seconds should be streamed +typedef struct Music { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) + bool looping; // Music looping enable + + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type +} Music; + +// VrDeviceInfo, Head-Mounted-Display device parameters +typedef struct VrDeviceInfo { + int hResolution; // Horizontal resolution in pixels + int vResolution; // Vertical resolution in pixels + float hScreenSize; // Horizontal size in meters + float vScreenSize; // Vertical size in meters + float vScreenCenter; // Screen center in meters + float eyeToScreenDistance; // Distance between eye and display in meters + float lensSeparationDistance; // Lens separation distance in meters + float interpupillaryDistance; // IPD (distance between pupils) in meters + float lensDistortionValues[4]; // Lens distortion constant parameters + float chromaAbCorrection[4]; // Chromatic aberration correction parameters +} VrDeviceInfo; + +// VrStereoConfig, VR stereo rendering configuration for simulator +typedef struct VrStereoConfig { + Matrix projection[2]; // VR projection matrices (per eye) + Matrix viewOffset[2]; // VR view offset matrices (per eye) + float leftLensCenter[2]; // VR left lens center + float rightLensCenter[2]; // VR right lens center + float leftScreenCenter[2]; // VR left screen center + float rightScreenCenter[2]; // VR right screen center + float scale[2]; // VR distortion scale + float scaleIn[2]; // VR distortion scale in +} VrStereoConfig; + +//---------------------------------------------------------------------------------- +// Enumerators Definition +//---------------------------------------------------------------------------------- +// System/Window config flags +// NOTE: Every bit registers one state (use it with bit masks) +// By default all flags are set to 0 +typedef enum { + FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU + FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen + FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window + FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) + FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window + FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) + FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) + FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused + FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top + FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized + FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer + FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI + FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X + FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) +} ConfigFlags; + +// Trace log level +// NOTE: Organized by priority level +typedef enum { + LOG_ALL = 0, // Display all logs + LOG_TRACE, // Trace logging, intended for internal use only + LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + LOG_INFO, // Info logging, used for program execution info + LOG_WARNING, // Warning logging, used on recoverable failures + LOG_ERROR, // Error logging, used on unrecoverable failures + LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + LOG_NONE // Disable logging +} TraceLogLevel; + +// Keyboard keys (US keyboard layout) +// NOTE: Use GetKeyPressed() to allow redefining +// required keys for alternative layouts +typedef enum { + KEY_NULL = 0, // Key: NULL, used for no key pressed + // Alphanumeric keys + KEY_APOSTROPHE = 39, // Key: ' + KEY_COMMA = 44, // Key: , + KEY_MINUS = 45, // Key: - + KEY_PERIOD = 46, // Key: . + KEY_SLASH = 47, // Key: / + KEY_ZERO = 48, // Key: 0 + KEY_ONE = 49, // Key: 1 + KEY_TWO = 50, // Key: 2 + KEY_THREE = 51, // Key: 3 + KEY_FOUR = 52, // Key: 4 + KEY_FIVE = 53, // Key: 5 + KEY_SIX = 54, // Key: 6 + KEY_SEVEN = 55, // Key: 7 + KEY_EIGHT = 56, // Key: 8 + KEY_NINE = 57, // Key: 9 + KEY_SEMICOLON = 59, // Key: ; + KEY_EQUAL = 61, // Key: = + KEY_A = 65, // Key: A | a + KEY_B = 66, // Key: B | b + KEY_C = 67, // Key: C | c + KEY_D = 68, // Key: D | d + KEY_E = 69, // Key: E | e + KEY_F = 70, // Key: F | f + KEY_G = 71, // Key: G | g + KEY_H = 72, // Key: H | h + KEY_I = 73, // Key: I | i + KEY_J = 74, // Key: J | j + KEY_K = 75, // Key: K | k + KEY_L = 76, // Key: L | l + KEY_M = 77, // Key: M | m + KEY_N = 78, // Key: N | n + KEY_O = 79, // Key: O | o + KEY_P = 80, // Key: P | p + KEY_Q = 81, // Key: Q | q + KEY_R = 82, // Key: R | r + KEY_S = 83, // Key: S | s + KEY_T = 84, // Key: T | t + KEY_U = 85, // Key: U | u + KEY_V = 86, // Key: V | v + KEY_W = 87, // Key: W | w + KEY_X = 88, // Key: X | x + KEY_Y = 89, // Key: Y | y + KEY_Z = 90, // Key: Z | z + KEY_LEFT_BRACKET = 91, // Key: [ + KEY_BACKSLASH = 92, // Key: '\' + KEY_RIGHT_BRACKET = 93, // Key: ] + KEY_GRAVE = 96, // Key: ` + // Function keys + KEY_SPACE = 32, // Key: Space + KEY_ESCAPE = 256, // Key: Esc + KEY_ENTER = 257, // Key: Enter + KEY_TAB = 258, // Key: Tab + KEY_BACKSPACE = 259, // Key: Backspace + KEY_INSERT = 260, // Key: Ins + KEY_DELETE = 261, // Key: Del + KEY_RIGHT = 262, // Key: Cursor right + KEY_LEFT = 263, // Key: Cursor left + KEY_DOWN = 264, // Key: Cursor down + KEY_UP = 265, // Key: Cursor up + KEY_PAGE_UP = 266, // Key: Page up + KEY_PAGE_DOWN = 267, // Key: Page down + KEY_HOME = 268, // Key: Home + KEY_END = 269, // Key: End + KEY_CAPS_LOCK = 280, // Key: Caps lock + KEY_SCROLL_LOCK = 281, // Key: Scroll down + KEY_NUM_LOCK = 282, // Key: Num lock + KEY_PRINT_SCREEN = 283, // Key: Print screen + KEY_PAUSE = 284, // Key: Pause + KEY_F1 = 290, // Key: F1 + KEY_F2 = 291, // Key: F2 + KEY_F3 = 292, // Key: F3 + KEY_F4 = 293, // Key: F4 + KEY_F5 = 294, // Key: F5 + KEY_F6 = 295, // Key: F6 + KEY_F7 = 296, // Key: F7 + KEY_F8 = 297, // Key: F8 + KEY_F9 = 298, // Key: F9 + KEY_F10 = 299, // Key: F10 + KEY_F11 = 300, // Key: F11 + KEY_F12 = 301, // Key: F12 + KEY_LEFT_SHIFT = 340, // Key: Shift left + KEY_LEFT_CONTROL = 341, // Key: Control left + KEY_LEFT_ALT = 342, // Key: Alt left + KEY_LEFT_SUPER = 343, // Key: Super left + KEY_RIGHT_SHIFT = 344, // Key: Shift right + KEY_RIGHT_CONTROL = 345, // Key: Control right + KEY_RIGHT_ALT = 346, // Key: Alt right + KEY_RIGHT_SUPER = 347, // Key: Super right + KEY_KB_MENU = 348, // Key: KB menu + // Keypad keys + KEY_KP_0 = 320, // Key: Keypad 0 + KEY_KP_1 = 321, // Key: Keypad 1 + KEY_KP_2 = 322, // Key: Keypad 2 + KEY_KP_3 = 323, // Key: Keypad 3 + KEY_KP_4 = 324, // Key: Keypad 4 + KEY_KP_5 = 325, // Key: Keypad 5 + KEY_KP_6 = 326, // Key: Keypad 6 + KEY_KP_7 = 327, // Key: Keypad 7 + KEY_KP_8 = 328, // Key: Keypad 8 + KEY_KP_9 = 329, // Key: Keypad 9 + KEY_KP_DECIMAL = 330, // Key: Keypad . + KEY_KP_DIVIDE = 331, // Key: Keypad / + KEY_KP_MULTIPLY = 332, // Key: Keypad * + KEY_KP_SUBTRACT = 333, // Key: Keypad - + KEY_KP_ADD = 334, // Key: Keypad + + KEY_KP_ENTER = 335, // Key: Keypad Enter + KEY_KP_EQUAL = 336, // Key: Keypad = + // Android key buttons + KEY_BACK = 4, // Key: Android back button + KEY_MENU = 82, // Key: Android menu button + KEY_VOLUME_UP = 24, // Key: Android volume up button + KEY_VOLUME_DOWN = 25 // Key: Android volume down button +} KeyboardKey; + +// Add backwards compatibility support for deprecated names +#define MOUSE_LEFT_BUTTON MOUSE_BUTTON_LEFT +#define MOUSE_RIGHT_BUTTON MOUSE_BUTTON_RIGHT +#define MOUSE_MIDDLE_BUTTON MOUSE_BUTTON_MIDDLE + +// Mouse buttons +typedef enum { + MOUSE_BUTTON_LEFT = 0, // Mouse button left + MOUSE_BUTTON_RIGHT = 1, // Mouse button right + MOUSE_BUTTON_MIDDLE = 2, // Mouse button middle (pressed wheel) + MOUSE_BUTTON_SIDE = 3, // Mouse button side (advanced mouse device) + MOUSE_BUTTON_EXTRA = 4, // Mouse button extra (advanced mouse device) + MOUSE_BUTTON_FORWARD = 5, // Mouse button fordward (advanced mouse device) + MOUSE_BUTTON_BACK = 6, // Mouse button back (advanced mouse device) +} MouseButton; + +// Mouse cursor +typedef enum { + MOUSE_CURSOR_DEFAULT = 0, // Default pointer shape + MOUSE_CURSOR_ARROW = 1, // Arrow shape + MOUSE_CURSOR_IBEAM = 2, // Text writing cursor shape + MOUSE_CURSOR_CROSSHAIR = 3, // Cross shape + MOUSE_CURSOR_POINTING_HAND = 4, // Pointing hand cursor + MOUSE_CURSOR_RESIZE_EW = 5, // Horizontal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NS = 6, // Vertical resize/move arrow shape + MOUSE_CURSOR_RESIZE_NWSE = 7, // Top-left to bottom-right diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_ALL = 9, // The omni-directional resize/move cursor shape + MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape +} MouseCursor; + +// Gamepad buttons +typedef enum { + GAMEPAD_BUTTON_UNKNOWN = 0, // Unknown button, just for error checking + GAMEPAD_BUTTON_LEFT_FACE_UP, // Gamepad left DPAD up button + GAMEPAD_BUTTON_LEFT_FACE_RIGHT, // Gamepad left DPAD right button + GAMEPAD_BUTTON_LEFT_FACE_DOWN, // Gamepad left DPAD down button + GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Gamepad left DPAD left button + GAMEPAD_BUTTON_RIGHT_FACE_UP, // Gamepad right button up (i.e. PS3: Triangle, Xbox: Y) + GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, // Gamepad right button right (i.e. PS3: Square, Xbox: X) + GAMEPAD_BUTTON_RIGHT_FACE_DOWN, // Gamepad right button down (i.e. PS3: Cross, Xbox: A) + GAMEPAD_BUTTON_RIGHT_FACE_LEFT, // Gamepad right button left (i.e. PS3: Circle, Xbox: B) + GAMEPAD_BUTTON_LEFT_TRIGGER_1, // Gamepad top/back trigger left (first), it could be a trailing button + GAMEPAD_BUTTON_LEFT_TRIGGER_2, // Gamepad top/back trigger left (second), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_1, // Gamepad top/back trigger right (one), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_2, // Gamepad top/back trigger right (second), it could be a trailing button + GAMEPAD_BUTTON_MIDDLE_LEFT, // Gamepad center buttons, left one (i.e. PS3: Select) + GAMEPAD_BUTTON_MIDDLE, // Gamepad center buttons, middle one (i.e. PS3: PS, Xbox: XBOX) + GAMEPAD_BUTTON_MIDDLE_RIGHT, // Gamepad center buttons, right one (i.e. PS3: Start) + GAMEPAD_BUTTON_LEFT_THUMB, // Gamepad joystick pressed button left + GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right +} GamepadButton; + +// Gamepad axis +typedef enum { + GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis + GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis + GAMEPAD_AXIS_RIGHT_X = 2, // Gamepad right stick X axis + GAMEPAD_AXIS_RIGHT_Y = 3, // Gamepad right stick Y axis + GAMEPAD_AXIS_LEFT_TRIGGER = 4, // Gamepad back trigger left, pressure level: [1..-1] + GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // Gamepad back trigger right, pressure level: [1..-1] +} GamepadAxis; + +// Material map index +typedef enum { + MATERIAL_MAP_ALBEDO = 0, // Albedo material (same as: MATERIAL_MAP_DIFFUSE) + MATERIAL_MAP_METALNESS, // Metalness material (same as: MATERIAL_MAP_SPECULAR) + MATERIAL_MAP_NORMAL, // Normal material + MATERIAL_MAP_ROUGHNESS, // Roughness material + MATERIAL_MAP_OCCLUSION, // Ambient occlusion material + MATERIAL_MAP_EMISSION, // Emission material + MATERIAL_MAP_HEIGHT, // Heightmap material + MATERIAL_MAP_CUBEMAP, // Cubemap material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_IRRADIANCE, // Irradiance material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_PREFILTER, // Prefilter material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_BRDF // Brdf material +} MaterialMapIndex; + +#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO +#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS + +// Shader location index +typedef enum { + SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position + SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 + SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 + SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal + SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent + SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color + SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection + SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) + SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection + SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) + SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal + SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view + SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color + SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color + SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color + SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: SHADER_LOC_MAP_DIFFUSE) + SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: SHADER_LOC_MAP_SPECULAR) + SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal + SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness + SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion + SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission + SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height + SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap + SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance + SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter + SHADER_LOC_MAP_BRDF // Shader location: sampler2d texture: brdf +} ShaderLocationIndex; + +#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO +#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float + SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) + SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) + SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) + SHADER_UNIFORM_INT, // Shader uniform type: int + SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) + SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) + SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d +} ShaderUniformDataType; + +// Shader attribute data types +typedef enum { + SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float + SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) + SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) + SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) +} ShaderAttributeDataType; + +// Pixel formats +// NOTE: Support depends on OpenGL version and platform +typedef enum { + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} PixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} TextureFilter; + +// Texture parameters: wrap mode +typedef enum { + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode +} TextureWrap; + +// Cubemap layouts +typedef enum { + CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type + CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces + CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by an horizontal line with faces + CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces + CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE, // Layout is defined by a 4x3 cross with cubemap faces + CUBEMAP_LAYOUT_PANORAMA // Layout is defined by a panorama image (equirectangular map) +} CubemapLayout; + +// Font type, defines generation method +typedef enum { + FONT_DEFAULT = 0, // Default font generation, anti-aliased + FONT_BITMAP, // Bitmap font generation, no anti-aliasing + FONT_SDF // SDF font generation, requires external shader +} FontType; + +// Color blending modes (pre-defined) +typedef enum { + BLEND_ALPHA = 0, // Blend textures considering alpha (default) + BLEND_ADDITIVE, // Blend textures adding colors + BLEND_MULTIPLIED, // Blend textures multiplying colors + BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + BLEND_CUSTOM // Belnd textures using custom src/dst factors (use rlSetBlendMode()) +} BlendMode; + +// Gesture +// NOTE: It could be used as flags to enable only some gestures +typedef enum { + GESTURE_NONE = 0, // No gesture + GESTURE_TAP = 1, // Tap gesture + GESTURE_DOUBLETAP = 2, // Double tap gesture + GESTURE_HOLD = 4, // Hold gesture + GESTURE_DRAG = 8, // Drag gesture + GESTURE_SWIPE_RIGHT = 16, // Swipe right gesture + GESTURE_SWIPE_LEFT = 32, // Swipe left gesture + GESTURE_SWIPE_UP = 64, // Swipe up gesture + GESTURE_SWIPE_DOWN = 128, // Swipe down gesture + GESTURE_PINCH_IN = 256, // Pinch in gesture + GESTURE_PINCH_OUT = 512 // Pinch out gesture +} Gesture; + +// Camera system modes +typedef enum { + CAMERA_CUSTOM = 0, // Custom camera + CAMERA_FREE, // Free camera + CAMERA_ORBITAL, // Orbital camera + CAMERA_FIRST_PERSON, // First person camera + CAMERA_THIRD_PERSON // Third person camera +} CameraMode; + +// Camera projection +typedef enum { + CAMERA_PERSPECTIVE = 0, // Perspective projection + CAMERA_ORTHOGRAPHIC // Orthographic projection +} CameraProjection; + +// N-patch layout +typedef enum { + NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles + NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles + NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles +} NPatchLayout; + +// Callbacks to hook some internal functions +// WARNING: This callbacks are intended for advance users +typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages +typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, unsigned int *bytesRead); // FileIO: Load binary data +typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, unsigned int bytesToWrite); // FileIO: Save binary data +typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data +typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data + +//------------------------------------------------------------------------------------ +// Global Variables Definition +//------------------------------------------------------------------------------------ +// It's lonely here... + +//------------------------------------------------------------------------------------ +// Window and Graphics Device Functions (Module: core) +//------------------------------------------------------------------------------------ + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Window-related functions +RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context +RLAPI bool WindowShouldClose(void); // Check if KEY_ESCAPE pressed or Close icon pressed +RLAPI void CloseWindow(void); // Close window and unload OpenGL context +RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully +RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen +RLAPI bool IsWindowHidden(void); // Check if window is currently hidden (only PLATFORM_DESKTOP) +RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized (only PLATFORM_DESKTOP) +RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized (only PLATFORM_DESKTOP) +RLAPI bool IsWindowFocused(void); // Check if window is currently focused (only PLATFORM_DESKTOP) +RLAPI bool IsWindowResized(void); // Check if window has been resized last frame +RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled +RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags +RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP) +RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable (only PLATFORM_DESKTOP) +RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable (only PLATFORM_DESKTOP) +RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized (only PLATFORM_DESKTOP) +RLAPI void SetWindowIcon(Image image); // Set icon for window (only PLATFORM_DESKTOP) +RLAPI void SetWindowTitle(const char *title); // Set title for window (only PLATFORM_DESKTOP) +RLAPI void SetWindowPosition(int x, int y); // Set window position on screen (only PLATFORM_DESKTOP) +RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window (fullscreen mode) +RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowSize(int width, int height); // Set window dimensions +RLAPI void *GetWindowHandle(void); // Get native window handle +RLAPI int GetScreenWidth(void); // Get current screen width +RLAPI int GetScreenHeight(void); // Get current screen height +RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI) +RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI) +RLAPI int GetMonitorCount(void); // Get number of connected monitors +RLAPI int GetCurrentMonitor(void); // Get current connected monitor +RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position +RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (max available by monitor) +RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (max available by monitor) +RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres +RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres +RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate +RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor +RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor +RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the primary monitor +RLAPI void SetClipboardText(const char *text); // Set clipboard text content +RLAPI const char *GetClipboardText(void); // Get clipboard text content + +// Custom frame control functions +// NOTE: Those functions are intended for advance users that want full control over the frame processing +// By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timming + PollInputEvents() +// To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL +RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) +RLAPI void PollInputEvents(void); // Register all input events +RLAPI void WaitTime(float ms); // Wait for some milliseconds (halt program execution) + +// Cursor-related functions +RLAPI void ShowCursor(void); // Shows cursor +RLAPI void HideCursor(void); // Hides cursor +RLAPI bool IsCursorHidden(void); // Check if cursor is not visible +RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) +RLAPI void DisableCursor(void); // Disables cursor (lock cursor) +RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the screen + +// Drawing-related functions +RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) +RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing +RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) +RLAPI void BeginMode2D(Camera2D camera); // Begin 2D mode with custom camera (2D) +RLAPI void EndMode2D(void); // Ends 2D mode with custom camera +RLAPI void BeginMode3D(Camera3D camera); // Begin 3D mode with custom camera (3D) +RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode +RLAPI void BeginTextureMode(RenderTexture2D target); // Begin drawing to render texture +RLAPI void EndTextureMode(void); // Ends drawing to render texture +RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing +RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) +RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied, subtract, custom) +RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) +RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) +RLAPI void EndScissorMode(void); // End scissor mode +RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) +RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) + +// VR stereo config functions for VR simulator +RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters +RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config + +// Shader management functions +// NOTE: Shader functionality is not available on OpenGL 1.1 +RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations +RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location +RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location +RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value +RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector +RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) +RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) +RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) + +// Screen-space-related functions +RLAPI Ray GetMouseRay(Vector2 mousePosition, Camera camera); // Get a ray trace from mouse position +RLAPI Matrix GetCameraMatrix(Camera camera); // Get camera transform matrix (view matrix) +RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix +RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Get the screen space position for a 3d world space position +RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Get size position for a 3d world space position +RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Get the screen space position for a 2d camera world space position +RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Get the world space position for a 2d camera screen space position + +// Timing-related functions +RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) +RLAPI int GetFPS(void); // Get current FPS +RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) +RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() + +// Misc. functions +RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) +RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator +RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) +RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) + +RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) +RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level +RLAPI void *MemAlloc(int size); // Internal memory allocator +RLAPI void *MemRealloc(void *ptr, int size); // Internal memory reallocator +RLAPI void MemFree(void *ptr); // Internal memory free + +// Set custom callbacks +// WARNING: Callbacks setup is intended for advance users +RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log +RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader +RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver +RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader +RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver + +// Files management functions +RLAPI unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead); // Load file data as byte array (read) +RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() +RLAPI bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite); // Save data to file from byte array (write), returns true on success +RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string +RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() +RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success +RLAPI bool FileExists(const char *fileName); // Check if file exists +RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists +RLAPI bool IsFileExtension(const char *fileName, const char *ext); // Check file extension (including point: .png, .wav) +RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: '.png') +RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string +RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) +RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) +RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) +RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) +RLAPI char **GetDirectoryFiles(const char *dirPath, int *count); // Get filenames in a directory path (memory should be freed) +RLAPI void ClearDirectoryFiles(void); // Clear directory files paths buffers (free memory) +RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success +RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window +RLAPI char **GetDroppedFiles(int *count); // Get dropped files names (memory should be freed) +RLAPI void ClearDroppedFiles(void); // Clear dropped files paths buffer (free memory) +RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) + +// Compression/Encoding functionality +RLAPI unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength); // Compress data (DEFLATE algorithm) +RLAPI unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength); // Decompress data (DEFLATE algorithm) +RLAPI char *EncodeDataBase64(const unsigned char *data, int dataLength, int *outputLength); // Encode data to Base64 string +RLAPI unsigned char *DecodeDataBase64(unsigned char *data, int *outputLength); // Decode Base64 string data + +// Persistent storage management +RLAPI bool SaveStorageValue(unsigned int position, int value); // Save integer value to storage file (to defined position), returns true on success +RLAPI int LoadStorageValue(unsigned int position); // Load integer value from storage file (from defined position) + +RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) + +//------------------------------------------------------------------------------------ +// Input Handling Functions (Module: core) +//------------------------------------------------------------------------------------ + +// Input-related functions: keyboard +RLAPI bool IsKeyPressed(int key); // Check if a key has been pressed once +RLAPI bool IsKeyDown(int key); // Check if a key is being pressed +RLAPI bool IsKeyReleased(int key); // Check if a key has been released once +RLAPI bool IsKeyUp(int key); // Check if a key is NOT being pressed +RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) +RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty +RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty + +// Input-related functions: gamepads +RLAPI bool IsGamepadAvailable(int gamepad); // Check if a gamepad is available +RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id +RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if a gamepad button has been pressed once +RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a gamepad button is being pressed +RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once +RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed +RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed +RLAPI int GetGamepadAxisCount(int gamepad); // Get gamepad axis count for a gamepad +RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get axis movement value for a gamepad axis +RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) + +// Input-related functions: mouse +RLAPI bool IsMouseButtonPressed(int button); // Check if a mouse button has been pressed once +RLAPI bool IsMouseButtonDown(int button); // Check if a mouse button is being pressed +RLAPI bool IsMouseButtonReleased(int button); // Check if a mouse button has been released once +RLAPI bool IsMouseButtonUp(int button); // Check if a mouse button is NOT being pressed +RLAPI int GetMouseX(void); // Get mouse position X +RLAPI int GetMouseY(void); // Get mouse position Y +RLAPI Vector2 GetMousePosition(void); // Get mouse position XY +RLAPI Vector2 GetMouseDelta(void); // Get mouse delta between frames +RLAPI void SetMousePosition(int x, int y); // Set mouse position XY +RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset +RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling +RLAPI float GetMouseWheelMove(void); // Get mouse wheel movement Y +RLAPI void SetMouseCursor(int cursor); // Set mouse cursor + +// Input-related functions: touch +RLAPI int GetTouchX(void); // Get touch position X for touch point 0 (relative to screen size) +RLAPI int GetTouchY(void); // Get touch position Y for touch point 0 (relative to screen size) +RLAPI Vector2 GetTouchPosition(int index); // Get touch position XY for a touch point index (relative to screen size) +RLAPI int GetTouchPointId(int index); // Get touch point identifier for given index +RLAPI int GetTouchPointCount(void); // Get number of touch points + +//------------------------------------------------------------------------------------ +// Gestures and Touch Handling Functions (Module: rgestures) +//------------------------------------------------------------------------------------ +RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +RLAPI bool IsGestureDetected(int gesture); // Check if a gesture have been detected +RLAPI int GetGestureDetected(void); // Get latest detected gesture +RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in milliseconds +RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector +RLAPI float GetGestureDragAngle(void); // Get gesture drag angle +RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle + +//------------------------------------------------------------------------------------ +// Camera System Functions (Module: rcamera) +//------------------------------------------------------------------------------------ +RLAPI void SetCameraMode(Camera camera, int mode); // Set camera mode (multiple camera modes available) +RLAPI void UpdateCamera(Camera *camera); // Update camera position for selected mode + +RLAPI void SetCameraPanControl(int keyPan); // Set camera pan key to combine with mouse movement (free camera) +RLAPI void SetCameraAltControl(int keyAlt); // Set camera alt key to combine with mouse movement (free camera) +RLAPI void SetCameraSmoothZoomControl(int keySmoothZoom); // Set camera smooth zoom key to combine with mouse (free camera) +RLAPI void SetCameraMoveControls(int keyFront, int keyBack, int keyRight, int keyLeft, int keyUp, int keyDown); // Set camera move controls (1st person and 3rd person cameras) + +//------------------------------------------------------------------------------------ +// Basic Shapes Drawing Functions (Module: shapes) +//------------------------------------------------------------------------------------ +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing + +// Basic shapes drawing functions +RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel +RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel (Vector version) +RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line +RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (Vector version) +RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line defining thickness +RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line using cubic-bezier curves in-out +RLAPI void DrawLineBezierQuad(Vector2 startPos, Vector2 endPos, Vector2 controlPos, float thick, Color color); // Draw line using quadratic bezier curves with a control point +RLAPI void DrawLineBezierCubic(Vector2 startPos, Vector2 endPos, Vector2 startControlPos, Vector2 endControlPos, float thick, Color color); // Draw line using cubic bezier curves with 2 control points +RLAPI void DrawLineStrip(Vector2 *points, int pointCount, Color color); // Draw lines sequence +RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle +RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle +RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline +RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2); // Draw a gradient-filled circle +RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) +RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline +RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse +RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline +RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring +RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline +RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) +RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters +RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline +RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters +RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges +RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, float lineThick, Color color); // Draw rectangle with rounded edges outline +RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) +RLAPI void DrawTriangleFan(Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) +RLAPI void DrawTriangleStrip(Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) +RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides +RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters + +// Basic shapes collision detection functions +RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles +RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles +RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle +RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle +RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle +RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference +RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] +RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision + +//------------------------------------------------------------------------------------ +// Texture Loading and Drawing Functions (Module: textures) +//------------------------------------------------------------------------------------ + +// Image loading functions +// NOTE: This functions do not require GPU access +RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) +RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data +RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) +RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png' +RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data +RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer and (screenshot) +RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) +RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success +RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success + +// Image generation functions +RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color +RLAPI Image GenImageGradientV(int width, int height, Color top, Color bottom); // Generate image: vertical gradient +RLAPI Image GenImageGradientH(int width, int height, Color left, Color right); // Generate image: horizontal gradient +RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient +RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked +RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise +RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm, bigger tileSize means bigger cells + +// Image manipulation functions +RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) +RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece +RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) +RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) +RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format +RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) +RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle +RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value +RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image +RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel +RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) +RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) +RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color +RLAPI void ImageMipmaps(Image *image); // Compute all mipmap levels for a provided image +RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +RLAPI void ImageFlipVertical(Image *image); // Flip image vertically +RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally +RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg +RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg +RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint +RLAPI void ImageColorInvert(Image *image); // Modify image color: invert +RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale +RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) +RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) +RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color +RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) +RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount); // Load colors palette from image as a Color array (RGBA - 32bit) +RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() +RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() +RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle +RLAPI Color GetImageColor(Image image, int x, int y); // Get image pixel color at (x, y) position + +// Image drawing functions +// NOTE: Image software-rendering functions (CPU) +RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with given color +RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image +RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) +RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image +RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) +RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle within an image +RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw circle within an image (Vector version) +RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) +RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image +RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) +RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) +RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) + +// Texture loading functions +// NOTE: These functions require GPU access +RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) +RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data +RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported +RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) +RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) +RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) +RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data +RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data + +// Texture configuration functions +RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture +RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode +RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode + +// Texture drawing functions +RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D +RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 +RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters +RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle +RLAPI void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint); // Draw texture quad with tiling and offset parameters +RLAPI void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint); // Draw part of a texture (defined by a rectangle) with rotation and scale tiled into dest. +RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters +RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely +RLAPI void DrawTexturePoly(Texture2D texture, Vector2 center, Vector2 *points, Vector2 *texcoords, int pointCount, Color tint); // Draw a textured polygon + +// Color/pixel related functions +RLAPI Color Fade(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI int ColorToInt(Color color); // Get hexadecimal value for a Color +RLAPI Vector4 ColorNormalize(Color color); // Get Color normalized as float [0..1] +RLAPI Color ColorFromNormalized(Vector4 normalized); // Get Color from normalized values [0..1] +RLAPI Vector3 ColorToHSV(Color color); // Get HSV values for a Color, hue [0..360], saturation/value [0..1] +RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Get a Color from HSV values, hue [0..360], saturation/value [0..1] +RLAPI Color 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 +RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format +RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer +RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format + +//------------------------------------------------------------------------------------ +// Font Loading and Text Drawing Functions (Module: text) +//------------------------------------------------------------------------------------ + +// Font loading/unloading functions +RLAPI Font GetFontDefault(void); // Get the default Font +RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) +RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int glyphCount); // Load font from file with extended parameters, use NULL for fontChars and 0 for glyphCount to load the default character set +RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) +RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int glyphCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' +RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int glyphCount, int type); // Load font data for further use +RLAPI Image GenImageFontAtlas(const GlyphInfo *chars, Rectangle **recs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI void UnloadFontData(GlyphInfo *chars, int glyphCount); // Unload font chars info data (RAM) +RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM) + +// Text drawing functions +RLAPI void DrawFPS(int posX, int posY); // Draw current FPS +RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) +RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation) +RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) + +// Text font info functions +RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font +RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font +RLAPI int GetGlyphIndex(Font font, int codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found +RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found +RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found + +// Text codepoints management functions (unicode characters) +RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter +RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory +RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string +RLAPI int GetCodepoint(const char *text, int *bytesProcessed); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) +RLAPI char *TextCodepointsToUTF8(int *codepoints, int length); // Encode text as codepoints array into UTF-8 text string (WARNING: memory must be freed!) + +// Text strings management functions (no UTF-8 strings, only byte chars) +// NOTE: Some strings allocate memory internally for returned strings, just be careful! +RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied +RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal +RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending +RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf() style) +RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string +RLAPI char *TextReplace(char *text, const char *replace, const char *by); // Replace text string (WARNING: memory must be freed!) +RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (WARNING: memory must be freed!) +RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter +RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! +RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string +RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string +RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string +RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string +RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) + +//------------------------------------------------------------------------------------ +// Basic 3d Shapes Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Basic geometric 3D shapes drawing functions +RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space +RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line +RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space +RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleStrip3D(Vector3 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube +RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) +RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires +RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) +RLAPI void 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 +RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone +RLAPI void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder with base at startPos and top at endPos +RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires +RLAPI void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder wires with base at startPos and top at endPos +RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ +RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line +RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) + +//------------------------------------------------------------------------------------ +// Model 3d Loading and Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Model management functions +RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) +RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) +RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) +RLAPI void UnloadModelKeepMeshes(Model model); // Unload model (but not meshes) from memory (RAM and/or VRAM) +RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes) + +// Model drawing functions +RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) +RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters +RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) +RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters +RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) +RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float size, Color tint); // Draw a billboard texture +RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source +RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source and rotation + +// Mesh management functions +RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids +RLAPI void UpdateMeshBuffer(Mesh mesh, int index, void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index +RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU +RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform +RLAPI void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms +RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success +RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits +RLAPI void GenMeshTangents(Mesh *mesh); // Compute mesh tangents +RLAPI void GenMeshBinormals(Mesh *mesh); // Compute mesh binormals + +// Mesh generation functions +RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh +RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) +RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh +RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) +RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) +RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh +RLAPI Mesh GenMeshCone(float radius, float height, int slices); // Generate cone/pyramid mesh +RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh +RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh +RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data +RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data + +// Material loading/unloading functions +RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file +RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) +RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh + +// Model animations loading/unloading functions +RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, unsigned int *animCount); // Load model animations from file +RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose +RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data +RLAPI void UnloadModelAnimations(ModelAnimation* animations, unsigned int count); // Unload animation array data +RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match + +// Collision detection functions +RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres +RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes +RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere +RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere +RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box +RLAPI RayCollision GetRayCollisionModel(Ray ray, Model model); // Get collision info between ray and model +RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh +RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle +RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad + +//------------------------------------------------------------------------------------ +// Audio Loading and Playing Functions (Module: audio) +//------------------------------------------------------------------------------------ + +// Audio device management functions +RLAPI void InitAudioDevice(void); // Initialize audio device and context +RLAPI void CloseAudioDevice(void); // Close the audio device and context +RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +RLAPI void SetMasterVolume(float volume); // Set master volume (listener) + +// Wave/Sound loading/unloading functions +RLAPI Wave LoadWave(const char *fileName); // Load wave data from file +RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' +RLAPI Sound LoadSound(const char *fileName); // Load sound from file +RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data +RLAPI void UnloadWave(Wave wave); // Unload wave data +RLAPI void UnloadSound(Sound sound); // Unload sound +RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success +RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success + +// Wave/Sound management functions +RLAPI void PlaySound(Sound sound); // Play a sound +RLAPI void StopSound(Sound sound); // Stop playing a sound +RLAPI void PauseSound(Sound sound); // Pause a sound +RLAPI void ResumeSound(Sound sound); // Resume a paused sound +RLAPI void PlaySoundMulti(Sound sound); // Play a sound (using multichannel buffer pool) +RLAPI void StopSoundMulti(void); // Stop any sound playing (using multichannel buffer pool) +RLAPI int GetSoundsPlaying(void); // Get number of sounds playing in the multichannel +RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing +RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave +RLAPI void WaveCrop(Wave *wave, int initSample, int finalSample); // Crop a wave to defined samples range +RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a floats array +RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() + +// Music management functions +RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file +RLAPI Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int dataSize); // Load music stream from data +RLAPI void UnloadMusicStream(Music music); // Unload music stream +RLAPI void PlayMusicStream(Music music); // Start music playing +RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing +RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming +RLAPI void StopMusicStream(Music music); // Stop music playing +RLAPI void PauseMusicStream(Music music); // Pause music playing +RLAPI void ResumeMusicStream(Music music); // Resume playing paused music +RLAPI void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) +RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) +RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) +RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory +RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data +RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream +RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream +RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream +RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream +RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams + +#if defined(__cplusplus) +} +#endif + +#endif // RAYLIB_H diff --git a/include/raymath.h b/include/raymath.h new file mode 100644 index 0000000..9714962 --- /dev/null +++ b/include/raymath.h @@ -0,0 +1,1850 @@ +/********************************************************************************************** +* +* raymath v1.5 - Math functions to work with Vector2, Vector3, Matrix and Quaternions +* +* CONFIGURATION: +* +* #define RAYMATH_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RAYMATH_STATIC_INLINE +* Define static inline functions code, so #include header suffices for use. +* This may use up lots of memory. +* +* CONVENTIONS: +* +* - Functions are always self-contained, no function use another raymath function inside, +* required code is directly re-implemented inside +* - Functions input parameters are always received by value (2 unavoidable exceptions) +* - Functions use always a "result" anmed variable for return +* - Functions are always defined inline +* - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2015-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYMATH_H +#define RAYMATH_H + +#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_STATIC_INLINE) + #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_STATIC_INLINE is contradictory" +#endif + +// Function specifiers definition +#if defined(RAYMATH_IMPLEMENTATION) + #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RMAPI __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll). + #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RMAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #else + #define RMAPI extern inline // Provide external definition + #endif +#elif defined(RAYMATH_STATIC_INLINE) + #define RMAPI static inline // Functions may be inlined, no external out-of-line definition +#else + #if defined(__TINYC__) + #define RMAPI static inline // plain inline not supported by tinycc (See issue #435) + #else + #define RMAPI inline // Functions may be inlined or external definition used + #endif +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif + +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif + +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Get float vector for Matrix +#ifndef MatrixToFloat + #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) +#endif + +// Get float vector for Vector3 +#ifndef Vector3ToFloat + #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if !defined(RL_VECTOR2_TYPE) +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; +#define RL_VECTOR2_TYPE +#endif + +#if !defined(RL_VECTOR3_TYPE) +// Vector3 type +typedef struct Vector3 { + float x; + float y; + float z; +} Vector3; +#define RL_VECTOR3_TYPE +#endif + +#if !defined(RL_VECTOR4_TYPE) +// Vector4 type +typedef struct Vector4 { + float x; + float y; + float z; + float w; +} Vector4; +#define RL_VECTOR4_TYPE +#endif + +#if !defined(RL_QUATERNION_TYPE) +// Quaternion type +typedef Vector4 Quaternion; +#define RL_QUATERNION_TYPE +#endif + +#if !defined(RL_MATRIX_TYPE) +// Matrix type (OpenGL style 4x4 - right handed, column major) +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; +#define RL_MATRIX_TYPE +#endif + +// NOTE: Helper types to be used instead of array return types for *ToFloat functions +typedef struct float3 { + float v[3]; +} float3; + +typedef struct float16 { + float v[16]; +} float16; + +#include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), fminf(), fmaxf(), fabs() + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Utils math +//---------------------------------------------------------------------------------- + +// Clamp float value +RMAPI float Clamp(float value, float min, float max) +{ + float result = (value < min)? min : value; + + if (result > max) result = max; + + return result; +} + +// Calculate linear interpolation between two floats +RMAPI float Lerp(float start, float end, float amount) +{ + float result = start + amount*(end - start); + + return result; +} + +// Normalize input value within input range +RMAPI float Normalize(float value, float start, float end) +{ + float result = (value - start)/(end - start); + + return result; +} + +// Remap input value within input range to output range +RMAPI float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) +{ + float result =(value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector2 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector2 Vector2Zero(void) +{ + Vector2 result = { 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector2 Vector2One(void) +{ + Vector2 result = { 1.0f, 1.0f }; + + return result; +} + +// Add two vectors (v1 + v2) +RMAPI Vector2 Vector2Add(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x + v2.x, v1.y + v2.y }; + + return result; +} + +// Add vector and float value +RMAPI Vector2 Vector2AddValue(Vector2 v, float add) +{ + Vector2 result = { v.x + add, v.y + add }; + + return result; +} + +// Subtract two vectors (v1 - v2) +RMAPI Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x - v2.x, v1.y - v2.y }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector2 Vector2SubtractValue(Vector2 v, float sub) +{ + Vector2 result = { v.x - sub, v.y - sub }; + + return result; +} + +// Calculate vector length +RMAPI float Vector2Length(Vector2 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y)); + + return result; +} + +// Calculate vector square length +RMAPI float Vector2LengthSqr(Vector2 v) +{ + float result = (v.x*v.x) + (v.y*v.y); + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector2DotProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) +{ + float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + + return result; +} + +// Calculate angle from two vectors +RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) +{ + float result = atan2f(v2.y, v2.x) - atan2f(v1.y, v1.x); + + return result; +} + +// Scale vector (multiply by value) +RMAPI Vector2 Vector2Scale(Vector2 v, float scale) +{ + Vector2 result = { v.x*scale, v.y*scale }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x*v2.x, v1.y*v2.y }; + + return result; +} + +// Negate vector +RMAPI Vector2 Vector2Negate(Vector2 v) +{ + Vector2 result = { -v.x, -v.y }; + + return result; +} + +// Divide vector by vector +RMAPI Vector2 Vector2Divide(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x/v2.x, v1.y/v2.y }; + + return result; +} + +// Normalize provided vector +RMAPI Vector2 Vector2Normalize(Vector2 v) +{ + Vector2 result = { 0 }; + float length = sqrtf((v.x*v.x) + (v.y*v.y)); + + if (length > 0) + { + float ilength = 1.0f/length; + result.x = v.x*ilength; + result.y = v.y*ilength; + } + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) +{ + Vector2 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector2 Vector2Reflect(Vector2 v, Vector2 normal) +{ + Vector2 result = { 0 }; + + float dotProduct = (v.x*normal.x + v.y*normal.y); // Dot product + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + + return result; +} + +// Rotate vector by angle +RMAPI Vector2 Vector2Rotate(Vector2 v, float angle) +{ + Vector2 result = { 0 }; + + result.x = v.x*cosf(angle) - v.y*sinf(angle); + result.y = v.x*sinf(angle) + v.y*cosf(angle); + + return result; +} + +// Move Vector towards target +RMAPI Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) +{ + Vector2 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float value = (dx*dx) + (dy*dy); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector3 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector3 Vector3Zero(void) +{ + Vector3 result = { 0.0f, 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector3 Vector3One(void) +{ + Vector3 result = { 1.0f, 1.0f, 1.0f }; + + return result; +} + +// Add two vectors +RMAPI Vector3 Vector3Add(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + + return result; +} + +// Add vector and float value +RMAPI Vector3 Vector3AddValue(Vector3 v, float add) +{ + Vector3 result = { v.x + add, v.y + add, v.z + add }; + + return result; +} + +// Subtract two vectors +RMAPI Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector3 Vector3SubtractValue(Vector3 v, float sub) +{ + Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; + + return result; +} + +// Multiply vector by scalar +RMAPI Vector3 Vector3Scale(Vector3 v, float scalar) +{ + Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; + + return result; +} + +// Calculate two vectors cross product +RMAPI Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + + return result; +} + +// Calculate one vector perpendicular vector +RMAPI Vector3 Vector3Perpendicular(Vector3 v) +{ + Vector3 result = { 0 }; + + float min = (float) fabs(v.x); + Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; + + if (fabs(v.y) < min) + { + min = (float) fabs(v.y); + Vector3 tmp = {0.0f, 1.0f, 0.0f}; + cardinalAxis = tmp; + } + + if (fabs(v.z) < min) + { + Vector3 tmp = {0.0f, 0.0f, 1.0f}; + cardinalAxis = tmp; + } + + // Cross product between vectors + result.x = v.y*cardinalAxis.z - v.z*cardinalAxis.y; + result.y = v.z*cardinalAxis.x - v.x*cardinalAxis.z; + result.z = v.x*cardinalAxis.y - v.y*cardinalAxis.x; + + return result; +} + +// Calculate vector length +RMAPI float Vector3Length(const Vector3 v) +{ + float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + + return result; +} + +// Calculate vector square length +RMAPI float Vector3LengthSqr(const Vector3 v) +{ + float result = v.x*v.x + v.y*v.y + v.z*v.z; + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector3DotProduct(Vector3 v1, Vector3 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector3Distance(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + result = sqrtf(dx*dx + dy*dy + dz*dz); + + return result; +} + +// Calculate angle between two vectors +RMAPI float Vector3Angle(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + Vector3 cross = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + float len = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z); + float dot = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + result = atan2f(len, dot); + + return result; +} + +// Negate provided vector (invert direction) +RMAPI Vector3 Vector3Negate(Vector3 v) +{ + Vector3 result = { -v.x, -v.y, -v.z }; + + return result; +} + +// Divide vector by vector +RMAPI Vector3 Vector3Divide(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; + + return result; +} + +// Normalize provided vector +RMAPI Vector3 Vector3Normalize(Vector3 v) +{ + Vector3 result = v; + + float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x *= ilength; + result.y *= ilength; + result.z *= ilength; + + return result; +} + +// Orthonormalize provided vectors +// Makes vectors normalized and orthogonal to each other +// Gram-Schmidt function implementation +RMAPI void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) +{ + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Normalize(*v1); + Vector3 v = *v1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + v1->x *= ilength; + v1->y *= ilength; + v1->z *= ilength; + + // Vector3CrossProduct(*v1, *v2) + Vector3 vn1 = { v1->y*v2->z - v1->z*v2->y, v1->z*v2->x - v1->x*v2->z, v1->x*v2->y - v1->y*v2->x }; + + // Vector3Normalize(vn1); + v = vn1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vn1.x *= ilength; + vn1.y *= ilength; + vn1.z *= ilength; + + // Vector3CrossProduct(vn1, *v1) + Vector3 vn2 = { vn1.y*v1->z - vn1.z*v1->y, vn1.z*v1->x - vn1.x*v1->z, vn1.x*v1->y - vn1.y*v1->x }; + + *v2 = vn2; +} + +// Transforms a Vector3 by a given Matrix +RMAPI Vector3 Vector3Transform(Vector3 v, Matrix mat) +{ + Vector3 result = { 0 }; + + float x = v.x; + float y = v.y; + float z = v.z; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; + + return result; +} + +// Transform a vector by quaternion rotation +RMAPI Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) +{ + Vector3 result = { 0 }; + + result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); + result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); + result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) +{ + Vector3 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal) +{ + Vector3 result = { 0 }; + + // I is the original vector + // N is the normal of the incident plane + // R = I - (2*N*(DotProduct[I, N])) + + float dotProduct = (v.x*normal.x + v.y*normal.y + v.z*normal.z); + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + result.z = v.z - (2.0f*normal.z)*dotProduct; + + return result; +} + +// Get min value for each pair of components +RMAPI Vector3 Vector3Min(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector3 Vector3Max(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + + return result; +} + +// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) +// NOTE: Assumes P is on the plane of the triangle +RMAPI Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) +{ + Vector3 result = { 0 }; + + Vector3 v0 = { b.x - a.x, b.y - a.y, b.z - a.z }; // Vector3Subtract(b, a) + Vector3 v1 = { c.x - a.x, c.y - a.y, c.z - a.z }; // Vector3Subtract(c, a) + Vector3 v2 = { p.x - a.x, p.y - a.y, p.z - a.z }; // Vector3Subtract(p, a) + float d00 = (v0.x*v0.x + v0.y*v0.y + v0.z*v0.z); // Vector3DotProduct(v0, v0) + float d01 = (v0.x*v1.x + v0.y*v1.y + v0.z*v1.z); // Vector3DotProduct(v0, v1) + float d11 = (v1.x*v1.x + v1.y*v1.y + v1.z*v1.z); // Vector3DotProduct(v1, v1) + float d20 = (v2.x*v0.x + v2.y*v0.y + v2.z*v0.z); // Vector3DotProduct(v2, v0) + float d21 = (v2.x*v1.x + v2.y*v1.y + v2.z*v1.z); // Vector3DotProduct(v2, v1) + + float denom = d00*d11 - d01*d01; + + result.y = (d11*d20 - d01*d21)/denom; + result.z = (d00*d21 - d01*d20)/denom; + result.x = 1.0f - (result.z + result.y); + + return result; +} + +// Projects a Vector3 from screen space into object space +// NOTE: We are avoiding calling other raymath functions despite available +RMAPI Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) +{ + Vector3 result = { 0 }; + + // Calculate unproject matrix (multiply view patrix by projection matrix) and invert it + Matrix matViewProj = { // MatrixMultiply(view, projection); + view.m0*projection.m0 + view.m1*projection.m4 + view.m2*projection.m8 + view.m3*projection.m12, + view.m0*projection.m1 + view.m1*projection.m5 + view.m2*projection.m9 + view.m3*projection.m13, + view.m0*projection.m2 + view.m1*projection.m6 + view.m2*projection.m10 + view.m3*projection.m14, + view.m0*projection.m3 + view.m1*projection.m7 + view.m2*projection.m11 + view.m3*projection.m15, + view.m4*projection.m0 + view.m5*projection.m4 + view.m6*projection.m8 + view.m7*projection.m12, + view.m4*projection.m1 + view.m5*projection.m5 + view.m6*projection.m9 + view.m7*projection.m13, + view.m4*projection.m2 + view.m5*projection.m6 + view.m6*projection.m10 + view.m7*projection.m14, + view.m4*projection.m3 + view.m5*projection.m7 + view.m6*projection.m11 + view.m7*projection.m15, + view.m8*projection.m0 + view.m9*projection.m4 + view.m10*projection.m8 + view.m11*projection.m12, + view.m8*projection.m1 + view.m9*projection.m5 + view.m10*projection.m9 + view.m11*projection.m13, + view.m8*projection.m2 + view.m9*projection.m6 + view.m10*projection.m10 + view.m11*projection.m14, + view.m8*projection.m3 + view.m9*projection.m7 + view.m10*projection.m11 + view.m11*projection.m15, + view.m12*projection.m0 + view.m13*projection.m4 + view.m14*projection.m8 + view.m15*projection.m12, + view.m12*projection.m1 + view.m13*projection.m5 + view.m14*projection.m9 + view.m15*projection.m13, + view.m12*projection.m2 + view.m13*projection.m6 + view.m14*projection.m10 + view.m15*projection.m14, + view.m12*projection.m3 + view.m13*projection.m7 + view.m14*projection.m11 + view.m15*projection.m15 }; + + // Calculate inverted matrix -> MatrixInvert(matViewProj); + // Cache the matrix values (speed optimization) + float a00 = matViewProj.m0, a01 = matViewProj.m1, a02 = matViewProj.m2, a03 = matViewProj.m3; + float a10 = matViewProj.m4, a11 = matViewProj.m5, a12 = matViewProj.m6, a13 = matViewProj.m7; + float a20 = matViewProj.m8, a21 = matViewProj.m9, a22 = matViewProj.m10, a23 = matViewProj.m11; + float a30 = matViewProj.m12, a31 = matViewProj.m13, a32 = matViewProj.m14, a33 = matViewProj.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + Matrix matViewProjInv = { + (a11*b11 - a12*b10 + a13*b09)*invDet, + (-a01*b11 + a02*b10 - a03*b09)*invDet, + (a31*b05 - a32*b04 + a33*b03)*invDet, + (-a21*b05 + a22*b04 - a23*b03)*invDet, + (-a10*b11 + a12*b08 - a13*b07)*invDet, + (a00*b11 - a02*b08 + a03*b07)*invDet, + (-a30*b05 + a32*b02 - a33*b01)*invDet, + (a20*b05 - a22*b02 + a23*b01)*invDet, + (a10*b10 - a11*b08 + a13*b06)*invDet, + (-a00*b10 + a01*b08 - a03*b06)*invDet, + (a30*b04 - a31*b02 + a33*b00)*invDet, + (-a20*b04 + a21*b02 - a23*b00)*invDet, + (-a10*b09 + a11*b07 - a12*b06)*invDet, + (a00*b09 - a01*b07 + a02*b06)*invDet, + (-a30*b03 + a31*b01 - a32*b00)*invDet, + (a20*b03 - a21*b01 + a22*b00)*invDet }; + + // Create quaternion from source point + Quaternion quat = { source.x, source.y, source.z, 1.0f }; + + // Multiply quat point by unproject matrix + Quaternion qtransformed = { // QuaternionTransform(quat, matViewProjInv) + matViewProjInv.m0*quat.x + matViewProjInv.m4*quat.y + matViewProjInv.m8*quat.z + matViewProjInv.m12*quat.w, + matViewProjInv.m1*quat.x + matViewProjInv.m5*quat.y + matViewProjInv.m9*quat.z + matViewProjInv.m13*quat.w, + matViewProjInv.m2*quat.x + matViewProjInv.m6*quat.y + matViewProjInv.m10*quat.z + matViewProjInv.m14*quat.w, + matViewProjInv.m3*quat.x + matViewProjInv.m7*quat.y + matViewProjInv.m11*quat.z + matViewProjInv.m15*quat.w }; + + // Normalized world points in vectors + result.x = qtransformed.x/qtransformed.w; + result.y = qtransformed.y/qtransformed.w; + result.z = qtransformed.z/qtransformed.w; + + return result; +} + +// Get Vector3 as float array +RMAPI float3 Vector3ToFloatV(Vector3 v) +{ + float3 buffer = { 0 }; + + buffer.v[0] = v.x; + buffer.v[1] = v.y; + buffer.v[2] = v.z; + + return buffer; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix math +//---------------------------------------------------------------------------------- + +// Compute matrix determinant +RMAPI float MatrixDeterminant(Matrix mat) +{ + float result = 0.0f; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + + a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + + a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + + a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + + a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + + a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; + + return result; +} + +// Get the trace of the matrix (sum of the values along the diagonal) +RMAPI float MatrixTrace(Matrix mat) +{ + float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); + + return result; +} + +// Transposes provided matrix +RMAPI Matrix MatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +RMAPI Matrix MatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + +// Normalize provided matrix +RMAPI Matrix MatrixNormalize(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + // MatrixDeterminant(mat) + float det = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + + a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + + a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + + a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + + a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + + a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; + + result.m0 = mat.m0/det; + result.m1 = mat.m1/det; + result.m2 = mat.m2/det; + result.m3 = mat.m3/det; + result.m4 = mat.m4/det; + result.m5 = mat.m5/det; + result.m6 = mat.m6/det; + result.m7 = mat.m7/det; + result.m8 = mat.m8/det; + result.m9 = mat.m9/det; + result.m10 = mat.m10/det; + result.m11 = mat.m11/det; + result.m12 = mat.m12/det; + result.m13 = mat.m13/det; + result.m14 = mat.m14/det; + result.m15 = mat.m15/det; + + return result; +} + +// Get identity matrix +RMAPI Matrix MatrixIdentity(void) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Add two matrices +RMAPI Matrix MatrixAdd(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 + right.m0; + result.m1 = left.m1 + right.m1; + result.m2 = left.m2 + right.m2; + result.m3 = left.m3 + right.m3; + result.m4 = left.m4 + right.m4; + result.m5 = left.m5 + right.m5; + result.m6 = left.m6 + right.m6; + result.m7 = left.m7 + right.m7; + result.m8 = left.m8 + right.m8; + result.m9 = left.m9 + right.m9; + result.m10 = left.m10 + right.m10; + result.m11 = left.m11 + right.m11; + result.m12 = left.m12 + right.m12; + result.m13 = left.m13 + right.m13; + result.m14 = left.m14 + right.m14; + result.m15 = left.m15 + right.m15; + + return result; +} + +// Subtract two matrices (left - right) +RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 - right.m0; + result.m1 = left.m1 - right.m1; + result.m2 = left.m2 - right.m2; + result.m3 = left.m3 - right.m3; + result.m4 = left.m4 - right.m4; + result.m5 = left.m5 - right.m5; + result.m6 = left.m6 - right.m6; + result.m7 = left.m7 - right.m7; + result.m8 = left.m8 - right.m8; + result.m9 = left.m9 - right.m9; + result.m10 = left.m10 - right.m10; + result.m11 = left.m11 - right.m11; + result.m12 = left.m12 - right.m12; + result.m13 = left.m13 - right.m13; + result.m14 = left.m14 - right.m14; + result.m15 = left.m15 - right.m15; + + return result; +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +// Get translation matrix +RMAPI Matrix MatrixTranslate(float x, float y, float z) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Create rotation matrix from axis and angle +// NOTE: Angle should be provided in radians +RMAPI Matrix MatrixRotate(Vector3 axis, float angle) +{ + Matrix result = { 0 }; + + float x = axis.x, y = axis.y, z = axis.z; + + float lengthSquared = x*x + y*y + z*z; + + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float ilength = 1.0f/sqrtf(lengthSquared); + x *= ilength; + y *= ilength; + z *= ilength; + } + + float sinres = sinf(angle); + float cosres = cosf(angle); + float t = 1.0f - cosres; + + result.m0 = x*x*t + cosres; + result.m1 = y*x*t + z*sinres; + result.m2 = z*x*t - y*sinres; + result.m3 = 0.0f; + + result.m4 = x*y*t - z*sinres; + result.m5 = y*y*t + cosres; + result.m6 = z*y*t + x*sinres; + result.m7 = 0.0f; + + result.m8 = x*z*t + y*sinres; + result.m9 = y*z*t - x*sinres; + result.m10 = z*z*t + cosres; + result.m11 = 0.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = 0.0f; + result.m15 = 1.0f; + + return result; +} + +// Get x-rotation matrix (angle in radians) +RMAPI Matrix MatrixRotateX(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m5 = cosres; + result.m6 = -sinres; + result.m9 = sinres; + result.m10 = cosres; + + return result; +} + +// Get y-rotation matrix (angle in radians) +RMAPI Matrix MatrixRotateY(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m2 = sinres; + result.m8 = -sinres; + result.m10 = cosres; + + return result; +} + +// Get z-rotation matrix (angle in radians) +RMAPI Matrix MatrixRotateZ(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m1 = -sinres; + result.m4 = sinres; + result.m5 = cosres; + + return result; +} + + +// Get xyz-rotation matrix (angles in radians) +RMAPI Matrix MatrixRotateXYZ(Vector3 ang) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosz = cosf(-ang.z); + float sinz = sinf(-ang.z); + float cosy = cosf(-ang.y); + float siny = sinf(-ang.y); + float cosx = cosf(-ang.x); + float sinx = sinf(-ang.x); + + result.m0 = cosz*cosy; + result.m4 = (cosz*siny*sinx) - (sinz*cosx); + result.m8 = (cosz*siny*cosx) + (sinz*sinx); + + result.m1 = sinz*cosy; + result.m5 = (sinz*siny*sinx) + (cosz*cosx); + result.m9 = (sinz*siny*cosx) - (cosz*sinx); + + result.m2 = -siny; + result.m6 = cosy*sinx; + result.m10= cosy*cosx; + + return result; +} + +// Get zyx-rotation matrix (angles in radians) +RMAPI Matrix MatrixRotateZYX(Vector3 ang) +{ + Matrix result = { 0 }; + + float cz = cosf(ang.z); + float sz = sinf(ang.z); + float cy = cosf(ang.y); + float sy = sinf(ang.y); + float cx = cosf(ang.x); + float sx = sinf(ang.x); + + result.m0 = cz*cy; + result.m1 = cz*sy*sx - cx*sz; + result.m2 = sz*sx + cz*cx*sy; + result.m3 = 0; + + result.m4 = cy*sz; + result.m5 = cz*cx + sz*sy*sx; + result.m6 = cx*sz*sy - cz*sx; + result.m7 = 0; + + result.m8 = -sy; + result.m9 = cy*sx; + result.m10 = cy*cx; + result.m11 = 0; + + result.m12 = 0; + result.m13 = 0; + result.m14 = 0; + result.m15 = 1; + + return result; +} + +// Get scaling matrix +RMAPI Matrix MatrixScale(float x, float y, float z) +{ + Matrix result = { x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Get perspective projection matrix +RMAPI Matrix MatrixFrustum(double left, double right, double bottom, double top, double near, double far) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(far - near); + + result.m0 = ((float)near*2.0f)/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + + result.m4 = 0.0f; + result.m5 = ((float)near*2.0f)/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)far + (float)near)/fn; + result.m11 = -1.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = -((float)far*(float)near*2.0f)/fn; + result.m15 = 0.0f; + + return result; +} + +// Get perspective projection matrix +// NOTE: Angle should be provided in radians +RMAPI Matrix MatrixPerspective(double fovy, double aspect, double near, double far) +{ + Matrix result = { 0 }; + + double top = near*tan(fovy*0.5); + double bottom = -top; + double right = top*aspect; + double left = -right; + + // MatrixFrustum(-right, right, -top, top, near, far); + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(far - near); + + result.m0 = ((float)near*2.0f)/rl; + result.m5 = ((float)near*2.0f)/tb; + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)far + (float)near)/fn; + result.m11 = -1.0f; + result.m14 = -((float)far*(float)near*2.0f)/fn; + + return result; +} + +// Get orthographic projection matrix +RMAPI Matrix MatrixOrtho(double left, double right, double bottom, double top, double near, double far) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(far - near); + + result.m0 = 2.0f/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + result.m4 = 0.0f; + result.m5 = 2.0f/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + result.m8 = 0.0f; + result.m9 = 0.0f; + result.m10 = -2.0f/fn; + result.m11 = 0.0f; + result.m12 = -((float)left + (float)right)/rl; + result.m13 = -((float)top + (float)bottom)/tb; + result.m14 = -((float)far + (float)near)/fn; + result.m15 = 1.0f; + + return result; +} + +// Get camera look-at matrix (view matrix) +RMAPI Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) +{ + Matrix result = { 0 }; + + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Subtract(eye, target) + Vector3 vz = { eye.x - target.x, eye.y - target.y, eye.z - target.z }; + + // Vector3Normalize(vz) + Vector3 v = vz; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vz.x *= ilength; + vz.y *= ilength; + vz.z *= ilength; + + // Vector3CrossProduct(up, vz) + Vector3 vx = { up.y*vz.z - up.z*vz.y, up.z*vz.x - up.x*vz.z, up.x*vz.y - up.y*vz.x }; + + // Vector3Normalize(x) + v = vx; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vx.x *= ilength; + vx.y *= ilength; + vx.z *= ilength; + + // Vector3CrossProduct(vz, vx) + Vector3 vy = { vz.y*vx.z - vz.z*vx.y, vz.z*vx.x - vz.x*vx.z, vz.x*vx.y - vz.y*vx.x }; + + result.m0 = vx.x; + result.m1 = vy.x; + result.m2 = vz.x; + result.m3 = 0.0f; + result.m4 = vx.y; + result.m5 = vy.y; + result.m6 = vz.y; + result.m7 = 0.0f; + result.m8 = vx.z; + result.m9 = vy.z; + result.m10 = vz.z; + result.m11 = 0.0f; + result.m12 = -(vx.x*eye.x + vx.y*eye.y + vx.z*eye.z); // Vector3DotProduct(vx, eye) + result.m13 = -(vy.x*eye.x + vy.y*eye.y + vy.z*eye.z); // Vector3DotProduct(vy, eye) + result.m14 = -(vz.x*eye.x + vz.y*eye.y + vz.z*eye.z); // Vector3DotProduct(vz, eye) + result.m15 = 1.0f; + + return result; +} + +// Get float array of matrix data +RMAPI float16 MatrixToFloatV(Matrix mat) +{ + float16 result = { 0 }; + + result.v[0] = mat.m0; + result.v[1] = mat.m1; + result.v[2] = mat.m2; + result.v[3] = mat.m3; + result.v[4] = mat.m4; + result.v[5] = mat.m5; + result.v[6] = mat.m6; + result.v[7] = mat.m7; + result.v[8] = mat.m8; + result.v[9] = mat.m9; + result.v[10] = mat.m10; + result.v[11] = mat.m11; + result.v[12] = mat.m12; + result.v[13] = mat.m13; + result.v[14] = mat.m14; + result.v[15] = mat.m15; + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Quaternion math +//---------------------------------------------------------------------------------- + +// Add two quaternions +RMAPI Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; + + return result; +} + +// Add quaternion and float value +RMAPI Quaternion QuaternionAddValue(Quaternion q, float add) +{ + Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; + + return result; +} + +// Subtract two quaternions +RMAPI Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; + + return result; +} + +// Subtract quaternion and float value +RMAPI Quaternion QuaternionSubtractValue(Quaternion q, float sub) +{ + Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; + + return result; +} + +// Get identity quaternion +RMAPI Quaternion QuaternionIdentity(void) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Computes the length of a quaternion +RMAPI float QuaternionLength(Quaternion q) +{ + float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + + return result; +} + +// Normalize provided quaternion +RMAPI Quaternion QuaternionNormalize(Quaternion q) +{ + Quaternion result = { 0 }; + + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Invert provided quaternion +RMAPI Quaternion QuaternionInvert(Quaternion q) +{ + Quaternion result = q; + + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + float lengthSq = length*length; + + if (lengthSq != 0.0) + { + float invLength = 1.0f/lengthSq; + + result.x *= -invLength; + result.y *= -invLength; + result.z *= -invLength; + result.w *= invLength; + } + + return result; +} + +// Calculate two quaternion multiplication +RMAPI Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) +{ + Quaternion result = { 0 }; + + float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; + float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; + + result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; + result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; + result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; + result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; + + return result; +} + +// Scale quaternion by float value +RMAPI Quaternion QuaternionScale(Quaternion q, float mul) +{ + Quaternion result = { 0 }; + + float qax = q.x, qay = q.y, qaz = q.z, qaw = q.w; + + result.x = qax*mul + qaw*mul + qay*mul - qaz*mul; + result.y = qay*mul + qaw*mul + qaz*mul - qax*mul; + result.z = qaz*mul + qaw*mul + qax*mul - qay*mul; + result.w = qaw*mul - qax*mul - qay*mul - qaz*mul; + + return result; +} + +// Divide two quaternions +RMAPI Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) +{ + Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; + + return result; +} + +// Calculate linear interpolation between two quaternions +RMAPI Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + return result; +} + +// Calculate slerp-optimized interpolation between two quaternions +RMAPI Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + // QuaternionLerp(q1, q2, amount) + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + // QuaternionNormalize(q); + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Calculates spherical linear interpolation between two quaternions +RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; + + if (cosHalfTheta < 0) + { + q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; + cosHalfTheta = -cosHalfTheta; + } + + if (fabs(cosHalfTheta) >= 1.0f) result = q1; + else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); + else + { + float halfTheta = acosf(cosHalfTheta); + float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); + + if (fabs(sinHalfTheta) < 0.001f) + { + result.x = (q1.x*0.5f + q2.x*0.5f); + result.y = (q1.y*0.5f + q2.y*0.5f); + result.z = (q1.z*0.5f + q2.z*0.5f); + result.w = (q1.w*0.5f + q2.w*0.5f); + } + else + { + float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; + float ratioB = sinf(amount*halfTheta)/sinHalfTheta; + + result.x = (q1.x*ratioA + q2.x*ratioB); + result.y = (q1.y*ratioA + q2.y*ratioB); + result.z = (q1.z*ratioA + q2.z*ratioB); + result.w = (q1.w*ratioA + q2.w*ratioB); + } + } + + return result; +} + +// Calculate quaternion based on the rotation from one vector to another +RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) +{ + Quaternion result = { 0 }; + + float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) + Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) + + result.x = cross.x; + result.y = cross.y; + result.z = cross.z; + result.w = 1.0f + cos2Theta; + + // QuaternionNormalize(q); + // NOTE: Normalize to essentially nlerp the original and identity to 0.5 + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Get a quaternion for a given rotation matrix +RMAPI Quaternion QuaternionFromMatrix(Matrix mat) +{ + Quaternion result = { 0 }; + + if ((mat.m0 > mat.m5) && (mat.m0 > mat.m10)) + { + float s = sqrtf(1.0f + mat.m0 - mat.m5 - mat.m10)*2; + + result.x = 0.25f*s; + result.y = (mat.m4 + mat.m1)/s; + result.z = (mat.m2 + mat.m8)/s; + result.w = (mat.m9 - mat.m6)/s; + } + else if (mat.m5 > mat.m10) + { + float s = sqrtf(1.0f + mat.m5 - mat.m0 - mat.m10)*2; + result.x = (mat.m4 + mat.m1)/s; + result.y = 0.25f*s; + result.z = (mat.m9 + mat.m6)/s; + result.w = (mat.m2 - mat.m8)/s; + } + else + { + float s = sqrtf(1.0f + mat.m10 - mat.m0 - mat.m5)*2; + result.x = (mat.m2 + mat.m8)/s; + result.y = (mat.m9 + mat.m6)/s; + result.z = 0.25f*s; + result.w = (mat.m4 - mat.m1)/s; + } + + return result; +} + +// Get a matrix for a given quaternion +RMAPI Matrix QuaternionToMatrix(Quaternion q) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float a2 = q.x*q.x; + float b2 = q.y*q.y; + float c2 = q.z*q.z; + float ac = q.x*q.z; + float ab = q.x*q.y; + float bc = q.y*q.z; + float ad = q.w*q.x; + float bd = q.w*q.y; + float cd = q.w*q.z; + + result.m0 = 1 - 2*(b2 + c2); + result.m1 = 2*(ab + cd); + result.m2 = 2*(ac - bd); + + result.m4 = 2*(ab - cd); + result.m5 = 1 - 2*(a2 + c2); + result.m6 = 2*(bc + ad); + + result.m8 = 2*(ac + bd); + result.m9 = 2*(bc - ad); + result.m10 = 1 - 2*(a2 + b2); + + return result; +} + +// Get rotation quaternion for an angle and axis +// NOTE: angle must be provided in radians +RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + float axisLength = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + + if (axisLength != 0.0f) + { + angle *= 0.5f; + + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Normalize(axis) + Vector3 v = axis; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + float sinres = sinf(angle); + float cosres = cosf(angle); + + result.x = axis.x*sinres; + result.y = axis.y*sinres; + result.z = axis.z*sinres; + result.w = cosres; + + // QuaternionNormalize(q); + Quaternion q = result; + length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + } + + return result; +} + +// Get the rotation angle and axis for a given quaternion +RMAPI void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) +{ + if (fabs(q.w) > 1.0f) + { + // QuaternionNormalize(q); + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + q.x = q.x*ilength; + q.y = q.y*ilength; + q.z = q.z*ilength; + q.w = q.w*ilength; + } + + Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; + float resAngle = 2.0f*acosf(q.w); + float den = sqrtf(1.0f - q.w*q.w); + + if (den > 0.0001f) + { + resAxis.x = q.x/den; + resAxis.y = q.y/den; + resAxis.z = q.z/den; + } + else + { + // This occurs when the angle is zero. + // Not a problem: just set an arbitrary normalized axis. + resAxis.x = 1.0f; + } + + *outAxis = resAxis; + *outAngle = resAngle; +} + +// Get the quaternion equivalent to Euler angles +// NOTE: Rotation order is ZYX +RMAPI Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) +{ + Quaternion result = { 0 }; + + float x0 = cosf(pitch*0.5f); + float x1 = sinf(pitch*0.5f); + float y0 = cosf(yaw*0.5f); + float y1 = sinf(yaw*0.5f); + float z0 = cosf(roll*0.5f); + float z1 = sinf(roll*0.5f); + + result.x = x1*y0*z0 - x0*y1*z1; + result.y = x0*y1*z0 + x1*y0*z1; + result.z = x0*y0*z1 - x1*y1*z0; + result.w = x0*y0*z0 + x1*y1*z1; + + return result; +} + +// Get the Euler angles equivalent to quaternion (roll, pitch, yaw) +// NOTE: Angles are returned in a Vector3 struct in radians +RMAPI Vector3 QuaternionToEuler(Quaternion q) +{ + Vector3 result = { 0 }; + + // Roll (x-axis rotation) + float x0 = 2.0f*(q.w*q.x + q.y*q.z); + float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); + result.x = atan2f(x0, x1); + + // Pitch (y-axis rotation) + float y0 = 2.0f*(q.w*q.y - q.z*q.x); + y0 = y0 > 1.0f ? 1.0f : y0; + y0 = y0 < -1.0f ? -1.0f : y0; + result.y = asinf(y0); + + // Yaw (z-axis rotation) + float z0 = 2.0f*(q.w*q.z + q.x*q.y); + float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); + result.z = atan2f(z0, z1); + + return result; +} + +// Transform a quaternion given a transformation matrix +RMAPI Quaternion QuaternionTransform(Quaternion q, Matrix mat) +{ + Quaternion result = { 0 }; + + result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; + result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; + result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; + result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; + + return result; +} + +#endif // RAYMATH_H diff --git a/include/rgui.h b/include/rgui.h new file mode 100644 index 0000000..c91c505 --- /dev/null +++ b/include/rgui.h @@ -0,0 +1,27 @@ +#pragma once + +/* Global. */ +int lguiGuiEnable( lua_State *L ); +int lguiGuiDisable( lua_State *L ); +int lguiGuiLock( lua_State *L ); +int lguiGuiUnlock( lua_State *L ); +/* Font. */ +int lguiGuiSetFont( lua_State *L ); +/* Container. */ +int lguiGuiWindowBox( lua_State *L ); +int lguiGuiPanel( lua_State *L ); +int lguiGuiScrollPanel( lua_State *L ); +/* Basic. */ +int lguiGuiLabel( lua_State *L ); +int lguiGuiButton( lua_State *L ); +int lguiGuiToggle( lua_State *L ); +int lguiGuiCheckBox( lua_State *L ); +int lguiGuiTextBox( lua_State *L ); +int lguiGuiTextBoxMulti( lua_State *L ); +int lguiGuiSpinner( lua_State *L ); +int lguiGuiValueBox( lua_State *L ); +int lguiGuiSlider( lua_State *L ); +int lguiGuiSliderBar( lua_State *L ); +int lguiGuiProgressBar( lua_State *L ); +int lguiGuiScrollBar( lua_State *L ); +int lguiGuiDropdownBox( lua_State *L ); diff --git a/include/rlgl.h b/include/rlgl.h new file mode 100644 index 0000000..0b74014 --- /dev/null +++ b/include/rlgl.h @@ -0,0 +1,4673 @@ +/********************************************************************************************** +* +* rlgl v4.0 - A multi-OpenGL abstraction layer with an immediate-mode style API +* +* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0) +* that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) +* +* When chosing an OpenGL backend different than OpenGL 1.1, some internal buffer are +* initialized on rlglInit() to accumulate vertex data. +* +* When an internal state change is required all the stored vertex data is renderer in batch, +* additioanlly, rlDrawRenderBatchActive() could be called to force flushing of the batch. +* +* Some additional resources are also loaded for convenience, here the complete list: +* - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data +* - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 +* - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) +* +* Internal buffer (and additional resources) must be manually unloaded calling rlglClose(). +* +* +* CONFIGURATION: +* +* #define GRAPHICS_API_OPENGL_11 +* #define GRAPHICS_API_OPENGL_21 +* #define GRAPHICS_API_OPENGL_33 +* #define GRAPHICS_API_OPENGL_43 +* #define GRAPHICS_API_OPENGL_ES2 +* Use selected OpenGL graphics backend, should be supported by platform +* Those preprocessor defines are only used on rlgl module, if OpenGL version is +* required by any other module, use rlGetVersion() to check it +* +* #define RLGL_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RLGL_RENDER_TEXTURES_HINT +* Enable framebuffer objects (fbo) support (enabled by default) +* Some GPUs could not support them despite the OpenGL version +* +* #define RLGL_SHOW_GL_DETAILS_INFO +* Show OpenGL extensions and capabilities detailed logs on init +* +* #define RLGL_ENABLE_OPENGL_DEBUG_CONTEXT +* Enable debug context (only available on OpenGL 4.3) +* +* rlgl capabilities could be customized just defining some internal +* values before library inclusion (default values listed): +* +* #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch elements limits +* #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +* #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +* #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) +* +* #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack +* #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +* #define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance +* #define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance +* +* When loading a shader, the following vertex attribute and uniform +* location names are tried to be set automatically: +* +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Binded by default to shader location: 0 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Binded by default to shader location: 1 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Binded by default to shader location: 2 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Binded by default to shader location: 3 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Binded by default to shader location: 4 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Binded by default to shader location: 5 +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) +* +* DEPENDENCIES: +* +* - OpenGL libraries (depending on platform and OpenGL version selected) +* - GLAD OpenGL extensions loading library (only for OpenGL 3.3 Core, 4.3 Core) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RLGL_H +#define RLGL_H + +#define RLGL_VERSION "4.0" + +// Function specifiers in case library is build/used as a shared library (Windows) +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +#if defined(_WIN32) + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif +#endif + +// Function specifiers definition +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +// Support TRACELOG macros +#ifndef TRACELOG + #define TRACELOG(level, ...) (void)0 + #define TRACELOGD(...) (void)0 +#endif + +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(n,sz) realloc(n,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(p) free(p) +#endif + +// Security check in case no GRAPHICS_API_OPENGL_* defined +#if !defined(GRAPHICS_API_OPENGL_11) && \ + !defined(GRAPHICS_API_OPENGL_21) && \ + !defined(GRAPHICS_API_OPENGL_33) && \ + !defined(GRAPHICS_API_OPENGL_43) && \ + !defined(GRAPHICS_API_OPENGL_ES2) + #define GRAPHICS_API_OPENGL_33 +#endif + +// Security check in case multiple GRAPHICS_API_OPENGL_* defined +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(GRAPHICS_API_OPENGL_21) + #undef GRAPHICS_API_OPENGL_21 + #endif + #if defined(GRAPHICS_API_OPENGL_33) + #undef GRAPHICS_API_OPENGL_33 + #endif + #if defined(GRAPHICS_API_OPENGL_43) + #undef GRAPHICS_API_OPENGL_43 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + #undef GRAPHICS_API_OPENGL_ES2 + #endif +#endif + +// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality +// WARNING: Specific parts are checked with #if defines +#if defined(GRAPHICS_API_OPENGL_21) + #define GRAPHICS_API_OPENGL_33 +#endif + +// OpenGL 4.3 uses OpenGL 3.3 Core functionality +#if defined(GRAPHICS_API_OPENGL_43) + #define GRAPHICS_API_OPENGL_33 +#endif + +// Support framebuffer objects by default +// NOTE: Some driver implementation do not support it, despite they should +#define RLGL_RENDER_TEXTURES_HINT + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Default internal render batch elements limits +#ifndef RL_DEFAULT_BATCH_BUFFER_ELEMENTS + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // This is the maximum amount of elements (quads) per batch + // NOTE: Be careful with text, every letter maps to a quad + #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + // We reduce memory sizes for embedded systems (RPI and HTML5) + // NOTE: On HTML5 (emscripten) this is allocated on heap, + // by default it's only 16MB!...just take care... + #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 2048 + #endif +#endif +#ifndef RL_DEFAULT_BATCH_BUFFERS + #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +#endif +#ifndef RL_DEFAULT_BATCH_DRAWCALLS + #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +#endif +#ifndef RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS + #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) +#endif + +// Internal Matrix stack +#ifndef RL_MAX_MATRIX_STACK_SIZE + #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of Matrix stack +#endif + +// Shader limits +#ifndef RL_MAX_SHADER_LOCATIONS + #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +#endif + +// Projection matrix culling +#ifndef RL_CULL_DISTANCE_NEAR + #define RL_CULL_DISTANCE_NEAR 0.01 // Default near cull distance +#endif +#ifndef RL_CULL_DISTANCE_FAR + #define RL_CULL_DISTANCE_FAR 1000.0 // Default far cull distance +#endif + +// Texture parameters (equivalent to OpenGL defines) +#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S +#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T +#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER +#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER + +#define RL_TEXTURE_FILTER_NEAREST 0x2600 // GL_NEAREST +#define RL_TEXTURE_FILTER_LINEAR 0x2601 // GL_LINEAR +#define RL_TEXTURE_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_ANISOTROPIC 0x3000 // Anisotropic filter (custom identifier) + +#define RL_TEXTURE_WRAP_REPEAT 0x2901 // GL_REPEAT +#define RL_TEXTURE_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE +#define RL_TEXTURE_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT +#define RL_TEXTURE_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT + +// Matrix modes (equivalent to OpenGL) +#define RL_MODELVIEW 0x1700 // GL_MODELVIEW +#define RL_PROJECTION 0x1701 // GL_PROJECTION +#define RL_TEXTURE 0x1702 // GL_TEXTURE + +// Primitive assembly draw modes +#define RL_LINES 0x0001 // GL_LINES +#define RL_TRIANGLES 0x0004 // GL_TRIANGLES +#define RL_QUADS 0x0007 // GL_QUADS + +// GL equivalent data types +#define RL_UNSIGNED_BYTE 0x1401 // GL_UNSIGNED_BYTE +#define RL_FLOAT 0x1406 // GL_FLOAT + +// Buffer usage hint +#define RL_STREAM_DRAW 0x88E0 // GL_STREAM_DRAW +#define RL_STREAM_READ 0x88E1 // GL_STREAM_READ +#define RL_STREAM_COPY 0x88E2 // GL_STREAM_COPY +#define RL_STATIC_DRAW 0x88E4 // GL_STATIC_DRAW +#define RL_STATIC_READ 0x88E5 // GL_STATIC_READ +#define RL_STATIC_COPY 0x88E6 // GL_STATIC_COPY +#define RL_DYNAMIC_DRAW 0x88E8 // GL_DYNAMIC_DRAW +#define RL_DYNAMIC_READ 0x88E9 // GL_DYNAMIC_READ +#define RL_DYNAMIC_COPY 0x88EA // GL_DYNAMIC_COPY + +// GL Shader type +#define RL_FRAGMENT_SHADER 0x8B30 // GL_FRAGMENT_SHADER +#define RL_VERTEX_SHADER 0x8B31 // GL_VERTEX_SHADER +#define RL_COMPUTE_SHADER 0x91B9 // GL_COMPUTE_SHADER + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef enum { + OPENGL_11 = 1, + OPENGL_21, + OPENGL_33, + OPENGL_43, + OPENGL_ES_20 +} rlGlVersion; + +typedef enum { + RL_ATTACHMENT_COLOR_CHANNEL0 = 0, + RL_ATTACHMENT_COLOR_CHANNEL1, + RL_ATTACHMENT_COLOR_CHANNEL2, + RL_ATTACHMENT_COLOR_CHANNEL3, + RL_ATTACHMENT_COLOR_CHANNEL4, + RL_ATTACHMENT_COLOR_CHANNEL5, + RL_ATTACHMENT_COLOR_CHANNEL6, + RL_ATTACHMENT_COLOR_CHANNEL7, + RL_ATTACHMENT_DEPTH = 100, + RL_ATTACHMENT_STENCIL = 200, +} rlFramebufferAttachType; + +typedef enum { + RL_ATTACHMENT_CUBEMAP_POSITIVE_X = 0, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_X, + RL_ATTACHMENT_CUBEMAP_POSITIVE_Y, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Y, + RL_ATTACHMENT_CUBEMAP_POSITIVE_Z, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Z, + RL_ATTACHMENT_TEXTURE2D = 100, + RL_ATTACHMENT_RENDERBUFFER = 200, +} rlFramebufferAttachTextureType; + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +typedef struct rlVertexBuffer { + int elementCount; // Number of elements in the buffer (QUADS) + + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int vboId[4]; // OpenGL Vertex Buffer Objects id (4 types of vertex data) +} rlVertexBuffer; + +// Draw call type +// NOTE: Only texture changes register a new draw, other state-change-related elements are not +// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any +// of those state-change happens (this is done in core module) +typedef struct rlDrawCall { + int mode; // Drawing mode: LINES, TRIANGLES, QUADS + int vertexCount; // Number of vertex of the draw + int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) + //unsigned int vaoId; // Vertex array id to be used on the draw -> Using RLGL.currentBatch->vertexBuffer.vaoId + //unsigned int shaderId; // Shader id to be used on the draw -> Using RLGL.currentShaderId + unsigned int textureId; // Texture id to be used on the draw -> Use to create new draw call if changes + + //Matrix projection; // Projection matrix for this draw -> Using RLGL.projection by default + //Matrix modelview; // Modelview matrix for this draw -> Using RLGL.modelview by default +} rlDrawCall; + +// rlRenderBatch type +typedef struct rlRenderBatch { + int bufferCount; // Number of vertex buffers (multi-buffering support) + int currentBuffer; // Current buffer tracking in case of multi-buffering + rlVertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data + + rlDrawCall *draws; // Draw calls array, depends on textureId + int drawCounter; // Draw calls counter + float currentDepth; // Current depth value for next draw +} rlRenderBatch; + +#if defined(__STDC__) && __STDC_VERSION__ >= 199901L + #include +#elif !defined(__cplusplus) && !defined(bool) && !defined(RL_BOOL_TYPE) + // Boolean type + typedef enum bool { false, true } bool; +#endif + +#if !defined(RL_MATRIX_TYPE) +// Matrix, 4x4 components, column major, OpenGL style, right handed +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; +#define RL_MATRIX_TYPE +#endif + +// Trace log level +// NOTE: Organized by priority level +typedef enum { + RL_LOG_ALL = 0, // Display all logs + RL_LOG_TRACE, // Trace logging, intended for internal use only + RL_LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + RL_LOG_INFO, // Info logging, used for program execution info + RL_LOG_WARNING, // Warning logging, used on recoverable failures + RL_LOG_ERROR, // Error logging, used on unrecoverable failures + RL_LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + RL_LOG_NONE // Disable logging +} rlTraceLogLevel; + +// Texture formats (support depends on OpenGL version) +typedef enum { + RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + RL_PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} rlPixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + RL_TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation + RL_TEXTURE_FILTER_BILINEAR, // Linear filtering + RL_TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + RL_TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + RL_TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + RL_TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} rlTextureFilter; + +// Color blending modes (pre-defined) +typedef enum { + RL_BLEND_ALPHA = 0, // Blend textures considering alpha (default) + RL_BLEND_ADDITIVE, // Blend textures adding colors + RL_BLEND_MULTIPLIED, // Blend textures multiplying colors + RL_BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + RL_BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + RL_BLEND_CUSTOM // Belnd textures using custom src/dst factors (use SetBlendModeCustom()) +} rlBlendMode; + +// Shader location point type +typedef enum { + RL_SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position + RL_SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 + RL_SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 + RL_SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal + RL_SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent + RL_SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color + RL_SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection + RL_SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) + RL_SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection + RL_SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) + RL_SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal + RL_SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view + RL_SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color + RL_SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color + RL_SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color + RL_SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: RL_SHADER_LOC_MAP_DIFFUSE) + RL_SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: RL_SHADER_LOC_MAP_SPECULAR) + RL_SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal + RL_SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness + RL_SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion + RL_SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission + RL_SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height + RL_SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap + RL_SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance + RL_SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter + RL_SHADER_LOC_MAP_BRDF // Shader location: sampler2d texture: brdf +} rlShaderLocationIndex; + +#define RL_SHADER_LOC_MAP_DIFFUSE RL_SHADER_LOC_MAP_ALBEDO +#define RL_SHADER_LOC_MAP_SPECULAR RL_SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + RL_SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float + RL_SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) + RL_SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) + RL_SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) + RL_SHADER_UNIFORM_INT, // Shader uniform type: int + RL_SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) + RL_SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) + RL_SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + RL_SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d +} rlShaderUniformDataType; + +// Shader attribute data types +typedef enum { + RL_SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float + RL_SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) + RL_SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) + RL_SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) +} rlShaderAttributeDataType; + +//------------------------------------------------------------------------------------ +// Functions Declaration - Matrix operations +//------------------------------------------------------------------------------------ + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed +RLAPI void rlPushMatrix(void); // Push the current matrix to stack +RLAPI void rlPopMatrix(void); // Pop lattest inserted matrix from stack +RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix +RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix +RLAPI void rlRotatef(float angle, float x, float y, float z); // Multiply the current matrix by a rotation matrix +RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix +RLAPI void rlMultMatrixf(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 + +//------------------------------------------------------------------------------------ +// Functions Declaration - Vertex level operations +//------------------------------------------------------------------------------------ +RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) +RLAPI void rlEnd(void); // Finish vertex providing +RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int +RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float +RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float +RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float +RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float +RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte +RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float +RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float + +//------------------------------------------------------------------------------------ +// Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) +// NOTE: This functions are used to completely abstract raylib code from OpenGL layer, +// some of them are direct wrappers over OpenGL calls, some others are custom +//------------------------------------------------------------------------------------ + +// Vertex buffers state +RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array (VAO, if supported) +RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) +RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) +RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) +RLAPI void rlEnableVertexBufferElement(unsigned int id);// Enable vertex buffer element (VBO element) +RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) +RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index +RLAPI void rlDisableVertexAttribute(unsigned int index);// Disable vertex attribute index +#if defined(GRAPHICS_API_OPENGL_11) +RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); // Enable attribute state pointer +RLAPI void rlDisableStatePointer(int vertexAttribType); // Disable attribute state pointer +#endif + +// Textures state +RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot +RLAPI void rlEnableTexture(unsigned int id); // Enable texture +RLAPI void rlDisableTexture(void); // Disable texture +RLAPI void rlEnableTextureCubemap(unsigned int id); // Enable texture cubemap +RLAPI void rlDisableTextureCubemap(void); // Disable texture cubemap +RLAPI void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) + +// Shader state +RLAPI void rlEnableShader(unsigned int id); // Enable shader program +RLAPI void rlDisableShader(void); // Disable shader program + +// Framebuffer state +RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) +RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer +RLAPI void rlActiveDrawBuffers(int count); // Activate multiple draw color buffers + +// General render state +RLAPI void rlEnableColorBlend(void); // Enable color blending +RLAPI void rlDisableColorBlend(void); // Disable color blending +RLAPI void rlEnableDepthTest(void); // Enable depth test +RLAPI void rlDisableDepthTest(void); // Disable depth test +RLAPI void rlEnableDepthMask(void); // Enable depth write +RLAPI void rlDisableDepthMask(void); // Disable depth write +RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling +RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling +RLAPI void rlEnableScissorTest(void); // Enable scissor test +RLAPI void rlDisableScissorTest(void); // Disable scissor test +RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test +RLAPI void rlEnableWireMode(void); // Enable wire mode +RLAPI void rlDisableWireMode(void); // Disable wire mode +RLAPI void rlSetLineWidth(float width); // Set the line drawing width +RLAPI float rlGetLineWidth(void); // Get the line drawing width +RLAPI void rlEnableSmoothLines(void); // Enable line aliasing +RLAPI void rlDisableSmoothLines(void); // Disable line aliasing +RLAPI void rlEnableStereoRender(void); // Enable stereo rendering +RLAPI void rlDisableStereoRender(void); // Disable stereo rendering +RLAPI bool rlIsStereoRenderEnabled(void); // Check if stereo render is enabled + +RLAPI void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color +RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) +RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes +RLAPI void rlSetBlendMode(int mode); // Set blending mode +RLAPI void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation); // Set blending mode factor and equation (using OpenGL factors) + +//------------------------------------------------------------------------------------ +// Functions Declaration - rlgl functionality +//------------------------------------------------------------------------------------ +// rlgl initialization functions +RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) +RLAPI void rlglClose(void); // De-inititialize rlgl (buffers, shaders, textures) +RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions (loader function required) +RLAPI int rlGetVersion(void); // Get current OpenGL version +RLAPI int rlGetFramebufferWidth(void); // Get default framebuffer width +RLAPI int rlGetFramebufferHeight(void); // Get default framebuffer height + +RLAPI unsigned int rlGetTextureIdDefault(void); // Get default texture id +RLAPI unsigned int rlGetShaderIdDefault(void); // Get default shader id +RLAPI int *rlGetShaderLocsDefault(void); // Get default shader locations + +// Render batch management +// NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode +// but this render batch API is exposed in case of custom batches are required +RLAPI rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system +RLAPI void rlUnloadRenderBatch(rlRenderBatch batch); // Unload render batch system +RLAPI void rlDrawRenderBatch(rlRenderBatch *batch); // Draw render batch data (Update->Draw->Reset) +RLAPI void rlSetRenderBatchActive(rlRenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) +RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch +RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex +RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits + +//------------------------------------------------------------------------------------------------------------------------ + +// Vertex buffers management +RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported +RLAPI unsigned int rlLoadVertexBuffer(void *buffer, int size, bool dynamic); // Load a vertex buffer attribute +RLAPI unsigned int rlLoadVertexBufferElement(void *buffer, int size, bool dynamic); // Load a new attributes element buffer +RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, void *data, int dataSize, int offset); // Update GPU buffer with new data +RLAPI void rlUnloadVertexArray(unsigned int vaoId); +RLAPI void rlUnloadVertexBuffer(unsigned int vboId); +RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, void *pointer); +RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); +RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value +RLAPI void rlDrawVertexArray(int offset, int count); +RLAPI void rlDrawVertexArrayElements(int offset, int count, void *buffer); +RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); +RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, void *buffer, int instances); + +// Textures management +RLAPI unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount); // Load texture in GPU +RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) +RLAPI unsigned int rlLoadTextureCubemap(void *data, int size, int format); // Load texture cubemap +RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update GPU texture with new data +RLAPI void rlGetGlTextureFormats(int format, int *glInternalFormat, int *glFormat, int *glType); // Get OpenGL internal formats +RLAPI const char *rlGetPixelFormatName(unsigned int format); // Get name string for pixel format +RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory +RLAPI void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps); // Generate mipmap data for selected texture +RLAPI void *rlReadTexturePixels(unsigned int id, int width, int height, int format); // Read texture pixel data +RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) + +// Framebuffer management (fbo) +RLAPI unsigned int rlLoadFramebuffer(int width, int height); // Load an empty framebuffer +RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer +RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete +RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU + +// Shaders management +RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings +RLAPI unsigned int rlCompileShader(const char *shaderCode, int type); // Compile custom shader and return shader id (type: RL_VERTEX_SHADER, RL_FRAGMENT_SHADER, RL_COMPUTE_SHADER) +RLAPI unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program +RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program +RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); // Get shader location uniform +RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute +RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform +RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix +RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler +RLAPI void rlSetShader(unsigned int id, int *locs); // Set shader currently active (id and locations) + +// Compute shader management +RLAPI unsigned int rlLoadComputeShaderProgram(unsigned int shaderId); // Load compute shader program +RLAPI void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ); // Dispatch compute shader (equivalent to *draw* for graphics pilepine) + +// Shader buffer storage object management (ssbo) +RLAPI unsigned int rlLoadShaderBuffer(unsigned long long size, const void *data, int usageHint); // Load shader storage buffer object (SSBO) +RLAPI void rlUnloadShaderBuffer(unsigned int ssboId); // Unload shader storage buffer object (SSBO) +RLAPI void rlUpdateShaderBufferElements(unsigned int id, const void *data, unsigned long long dataSize, unsigned long long offset); // Update SSBO buffer data +RLAPI unsigned long long rlGetShaderBufferSize(unsigned int id); // Get SSBO buffer size +RLAPI void rlReadShaderBufferElements(unsigned int id, void *dest, unsigned long long count, unsigned long long offset); // Bind SSBO buffer +RLAPI void rlBindShaderBuffer(unsigned int id, unsigned int index); // Copy SSBO buffer data + +// Buffer management +RLAPI void rlCopyBuffersElements(unsigned int destId, unsigned int srcId, unsigned long long destOffset, unsigned long long srcOffset, unsigned long long count); // Copy SSBO buffer data +RLAPI void rlBindImageTexture(unsigned int id, unsigned int index, unsigned int format, int readonly); // Bind image texture + +// Matrix state management +RLAPI Matrix rlGetMatrixModelview(void); // Get internal modelview matrix +RLAPI Matrix rlGetMatrixProjection(void); // Get internal projection matrix +RLAPI Matrix rlGetMatrixTransform(void); // Get internal accumulated transform matrix +RLAPI Matrix rlGetMatrixProjectionStereo(int eye); // Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye); // Get internal view offset matrix for stereo render (selected eye) +RLAPI void rlSetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) +RLAPI void rlSetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) +RLAPI void rlSetMatrixProjectionStereo(Matrix right, Matrix left); // Set eyes projection matrices for stereo rendering +RLAPI void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left); // Set eyes view offsets matrices for stereo rendering + +// Quick and dirty cube/quad buffers load->draw->unload +RLAPI void rlLoadDrawCube(void); // Load and draw a cube +RLAPI void rlLoadDrawQuad(void); // Load and draw a quad + +#if defined(__cplusplus) +} +#endif + +#endif // RLGL_H + +/*********************************************************************************** +* +* RLGL IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RLGL_IMPLEMENTATION) + +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(__APPLE__) + #include // OpenGL 1.1 library for OSX + #include // OpenGL extensions library + #else + // APIENTRY for OpenGL function pointer declarations is required + #ifndef APIENTRY + #if defined(_WIN32) + #define APIENTRY __stdcall + #else + #define APIENTRY + #endif + #endif + // WINGDIAPI definition. Some Windows OpenGL headers need it + #if !defined(WINGDIAPI) && defined(_WIN32) + #define WINGDIAPI __declspec(dllimport) + #endif + + #include // OpenGL 1.1 library + #endif +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + #if defined(__APPLE__) + #include // OpenGL 3 library for OSX + #include // OpenGL 3 extensions library for OSX + #else + #define GLAD_MALLOC RL_MALLOC + #define GLAD_FREE RL_FREE + + #define GLAD_GL_IMPLEMENTATION + #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers + #endif +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + #define GL_GLEXT_PROTOTYPES + //#include // EGL library -> not required, platform layer + #include // OpenGL ES 2.0 library + #include // OpenGL ES 2.0 extensions library + + // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi + // provided headers (despite being defined in official Khronos GLES2 headers) + #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); + #endif +#endif + +#include // Required for: malloc(), free() +#include // Required for: strcmp(), strlen() [Used in rlglInit(), on extensions loading] +#include // Required for: sqrtf(), sinf(), cosf(), floor(), log() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +#ifndef GL_SHADING_LANGUAGE_VERSION + #define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#endif + +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif +#ifndef GL_ETC1_RGB8_OES + #define GL_ETC1_RGB8_OES 0x8D64 +#endif +#ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 +#endif +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#endif +#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#endif +#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR + #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR + #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 +#endif + +#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + #define GL_LUMINANCE 0x1909 + #define GL_LUMINANCE_ALPHA 0x190A +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + #define glClearDepth glClearDepthf + #define GL_READ_FRAMEBUFFER GL_FRAMEBUFFER + #define GL_DRAW_FRAMEBUFFER GL_FRAMEBUFFER +#endif + +// Default shader vertex attribute names to set location points +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION + #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Binded by default to shader location: 0 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Binded by default to shader location: 1 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL + #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Binded by default to shader location: 2 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR + #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Binded by default to shader location: 3 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Binded by default to shader location: 4 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Binded by default to shader location: 5 +#endif + +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MVP + #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW + #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION + #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL + #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL + #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR + #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +typedef struct rlglData { + rlRenderBatch *currentBatch; // Current render batch + rlRenderBatch defaultBatch; // Default internal render batch + + struct { + int vertexCounter; // Current active render batch vertex counter (generic, used for all batches) + float texcoordx, texcoordy; // Current active texture coordinate (added on glVertex*()) + float normalx, normaly, normalz; // Current active normal (added on glVertex*()) + unsigned char colorr, colorg, colorb, colora; // Current active color (added on glVertex*()) + + int currentMatrixMode; // Current matrix mode + Matrix *currentMatrix; // Current matrix pointer + Matrix modelview; // Default modelview matrix + Matrix projection; // Default projection matrix + Matrix transform; // Transform matrix to be used with rlTranslate, rlRotate, rlScale + bool transformRequired; // Require transform matrix application to current draw-call vertex (if required) + Matrix stack[RL_MAX_MATRIX_STACK_SIZE];// Matrix stack for push/pop + int stackCounter; // Matrix stack counter + + unsigned int defaultTextureId; // Default texture used on shapes/poly drawing (required by shader) + unsigned int activeTextureId[RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS]; // Active texture ids to be enabled on batch drawing (0 active by default) + unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) + unsigned int defaultFShaderId; // Default fragment shader id (used by default shader program) + unsigned int defaultShaderId; // Default shader program id, supports vertex color and diffuse texture + int *defaultShaderLocs; // Default shader locations pointer to be used on rendering + unsigned int currentShaderId; // Current shader id to be used on rendering (by default, defaultShaderId) + int *currentShaderLocs; // Current shader locations pointer to be used on rendering (by default, defaultShaderLocs) + + bool stereoRender; // Stereo rendering flag + Matrix projectionStereo[2]; // VR stereo rendering eyes projection matrices + Matrix viewOffsetStereo[2]; // VR stereo rendering eyes view offset matrices + + int currentBlendMode; // Blending mode active + int glBlendSrcFactor; // Blending source factor + int glBlendDstFactor; // Blending destination factor + int glBlendEquation; // Blending equation + + int framebufferWidth; // Default framebuffer width + int framebufferHeight; // Default framebuffer height + + } State; // Renderer state + struct { + bool vao; // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object) + bool instancing; // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays) + bool texNPOT; // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot) + bool texDepth; // Depth textures supported (GL_ARB_depth_texture, GL_WEBGL_depth_texture, GL_OES_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) + bool texCompETC2; // ETC2/EAC texture compression support (GL_ARB_ES3_compatibility) + bool texCompPVRT; // PVR texture compression support (GL_IMG_texture_compression_pvrtc) + bool texCompASTC; // ASTC texture compression support (GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr) + bool texMirrorClamp; // Clamp mirror wrap mode supported (GL_EXT_texture_mirror_clamp) + bool texAnisoFilter; // Anisotropic texture filtering support (GL_EXT_texture_filter_anisotropic) + bool computeShader; // Compute shaders support (GL_ARB_compute_shader) + bool ssbo; // Shader storage buffer object support (GL_ARB_shader_storage_buffer_object) + + float maxAnisotropyLevel; // Maximum anisotropy level supported (minimum is 2.0f) + int maxDepthBits; // Maximum bits for depth component + + } ExtSupported; // Extensions supported flags +} rlglData; + +typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions loader signature (same as GLADloadproc) + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static rlglData RLGL = { 0 }; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +#if defined(GRAPHICS_API_OPENGL_ES2) +// NOTE: VAO functionality is exposed through extensions (OES) +static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays = NULL; +static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL; +static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL; + +// NOTE: Instancing functionality could also be available through extension +static PFNGLDRAWARRAYSINSTANCEDEXTPROC glDrawArraysInstanced = NULL; +static PFNGLDRAWELEMENTSINSTANCEDEXTPROC glDrawElementsInstanced = NULL; +static PFNGLVERTEXATTRIBDIVISOREXTPROC glVertexAttribDivisor = NULL; +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static void rlLoadShaderDefault(void); // Load default shader +static void rlUnloadShaderDefault(void); // Unload default shader +#if defined(RLGL_SHOW_GL_DETAILS_INFO) +static char *rlGetCompressedFormatName(int format); // Get compressed format official GL identifier name +#endif // RLGL_SHOW_GL_DETAILS_INFO +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +#if defined(GRAPHICS_API_OPENGL_11) +static int rlGenTextureMipmapsData(unsigned char *data, int baseWidth, int baseHeight); // Generate mipmaps data on CPU side +static unsigned char *rlGenNextMipmapData(unsigned char *srcData, int srcWidth, int srcHeight); // Generate next mipmap level on CPU side +#endif +static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) +// Auxiliar matrix math functions +static Matrix rlMatrixIdentity(void); // Get identity matrix +static Matrix rlMatrixMultiply(Matrix left, Matrix right); // Multiply two matrices + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix operations +//---------------------------------------------------------------------------------- + +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlMatrixMode(int mode) +{ + switch (mode) + { + case RL_PROJECTION: glMatrixMode(GL_PROJECTION); break; + case RL_MODELVIEW: glMatrixMode(GL_MODELVIEW); break; + case RL_TEXTURE: glMatrixMode(GL_TEXTURE); break; + default: break; + } +} + +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + glFrustum(left, right, bottom, top, znear, zfar); +} + +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + glOrtho(left, right, bottom, top, znear, zfar); +} + +void rlPushMatrix(void) { glPushMatrix(); } +void rlPopMatrix(void) { glPopMatrix(); } +void rlLoadIdentity(void) { glLoadIdentity(); } +void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); } +void rlRotatef(float angle, float x, float y, float z) { glRotatef(angle, x, y, z); } +void rlScalef(float x, float y, float z) { glScalef(x, y, z); } +void rlMultMatrixf(float *matf) { glMultMatrixf(matf); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Choose the current matrix to be transformed +void rlMatrixMode(int mode) +{ + if (mode == RL_PROJECTION) RLGL.State.currentMatrix = &RLGL.State.projection; + else if (mode == RL_MODELVIEW) RLGL.State.currentMatrix = &RLGL.State.modelview; + //else if (mode == RL_TEXTURE) // Not supported + + RLGL.State.currentMatrixMode = mode; +} + +// Push the current matrix into RLGL.State.stack +void rlPushMatrix(void) +{ + if (RLGL.State.stackCounter >= RL_MAX_MATRIX_STACK_SIZE) TRACELOG(RL_LOG_ERROR, "RLGL: Matrix stack overflow (RL_MAX_MATRIX_STACK_SIZE)"); + + if (RLGL.State.currentMatrixMode == RL_MODELVIEW) + { + RLGL.State.transformRequired = true; + RLGL.State.currentMatrix = &RLGL.State.transform; + } + + RLGL.State.stack[RLGL.State.stackCounter] = *RLGL.State.currentMatrix; + RLGL.State.stackCounter++; +} + +// Pop lattest inserted matrix from RLGL.State.stack +void rlPopMatrix(void) +{ + if (RLGL.State.stackCounter > 0) + { + Matrix mat = RLGL.State.stack[RLGL.State.stackCounter - 1]; + *RLGL.State.currentMatrix = mat; + RLGL.State.stackCounter--; + } + + if ((RLGL.State.stackCounter == 0) && (RLGL.State.currentMatrixMode == RL_MODELVIEW)) + { + RLGL.State.currentMatrix = &RLGL.State.modelview; + RLGL.State.transformRequired = false; + } +} + +// Reset current matrix to identity matrix +void rlLoadIdentity(void) +{ + *RLGL.State.currentMatrix = rlMatrixIdentity(); +} + +// Multiply the current matrix by a translation matrix +void rlTranslatef(float x, float y, float z) +{ + Matrix matTranslation = { + 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matTranslation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a rotation matrix +// NOTE: The provided angle must be in degrees +void rlRotatef(float angle, float x, float y, float z) +{ + Matrix matRotation = rlMatrixIdentity(); + + // Axis vector (x, y, z) normalization + float lengthSquared = x*x + y*y + z*z; + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float inverseLength = 1.0f/sqrtf(lengthSquared); + x *= inverseLength; + y *= inverseLength; + z *= inverseLength; + } + + // Rotation matrix generation + float sinres = sinf(DEG2RAD*angle); + float cosres = cosf(DEG2RAD*angle); + float t = 1.0f - cosres; + + matRotation.m0 = x*x*t + cosres; + matRotation.m1 = y*x*t + z*sinres; + matRotation.m2 = z*x*t - y*sinres; + matRotation.m3 = 0.0f; + + matRotation.m4 = x*y*t - z*sinres; + matRotation.m5 = y*y*t + cosres; + matRotation.m6 = z*y*t + x*sinres; + matRotation.m7 = 0.0f; + + matRotation.m8 = x*z*t + y*sinres; + matRotation.m9 = y*z*t - x*sinres; + matRotation.m10 = z*z*t + cosres; + matRotation.m11 = 0.0f; + + matRotation.m12 = 0.0f; + matRotation.m13 = 0.0f; + matRotation.m14 = 0.0f; + matRotation.m15 = 1.0f; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matRotation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a scaling matrix +void rlScalef(float x, float y, float z) +{ + Matrix matScale = { + x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matScale, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by another matrix +void rlMultMatrixf(float *matf) +{ + // Matrix creation from array + Matrix mat = { matf[0], matf[4], matf[8], matf[12], + matf[1], matf[5], matf[9], matf[13], + matf[2], matf[6], matf[10], matf[14], + matf[3], matf[7], matf[11], matf[15] }; + + *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, mat); +} + +// Multiply the current matrix by a perspective matrix generated by parameters +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + Matrix matFrustum = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(zfar - znear); + + matFrustum.m0 = ((float) znear*2.0f)/rl; + matFrustum.m1 = 0.0f; + matFrustum.m2 = 0.0f; + matFrustum.m3 = 0.0f; + + matFrustum.m4 = 0.0f; + matFrustum.m5 = ((float) znear*2.0f)/tb; + matFrustum.m6 = 0.0f; + matFrustum.m7 = 0.0f; + + matFrustum.m8 = ((float)right + (float)left)/rl; + matFrustum.m9 = ((float)top + (float)bottom)/tb; + matFrustum.m10 = -((float)zfar + (float)znear)/fn; + matFrustum.m11 = -1.0f; + + matFrustum.m12 = 0.0f; + matFrustum.m13 = 0.0f; + matFrustum.m14 = -((float)zfar*(float)znear*2.0f)/fn; + matFrustum.m15 = 0.0f; + + *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matFrustum); +} + +// Multiply the current matrix by an orthographic matrix generated by parameters +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + // NOTE: If left-right and top-botton values are equal it could create a division by zero, + // response to it is platform/compiler dependant + Matrix matOrtho = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(zfar - znear); + + matOrtho.m0 = 2.0f/rl; + matOrtho.m1 = 0.0f; + matOrtho.m2 = 0.0f; + matOrtho.m3 = 0.0f; + matOrtho.m4 = 0.0f; + matOrtho.m5 = 2.0f/tb; + matOrtho.m6 = 0.0f; + matOrtho.m7 = 0.0f; + matOrtho.m8 = 0.0f; + matOrtho.m9 = 0.0f; + matOrtho.m10 = -2.0f/fn; + matOrtho.m11 = 0.0f; + matOrtho.m12 = -((float)left + (float)right)/rl; + matOrtho.m13 = -((float)top + (float)bottom)/tb; + matOrtho.m14 = -((float)zfar + (float)znear)/fn; + matOrtho.m15 = 1.0f; + + *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matOrtho); +} +#endif + +// Set the viewport area (transformation from normalized device coordinates to window coordinates) +void rlViewport(int x, int y, int width, int height) +{ + glViewport(x, y, width, height); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vertex level operations +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlBegin(int mode) +{ + switch (mode) + { + case RL_LINES: glBegin(GL_LINES); break; + case RL_TRIANGLES: glBegin(GL_TRIANGLES); break; + case RL_QUADS: glBegin(GL_QUADS); break; + default: break; + } +} + +void rlEnd() { glEnd(); } +void rlVertex2i(int x, int y) { glVertex2i(x, y); } +void rlVertex2f(float x, float y) { glVertex2f(x, y); } +void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } +void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); } +void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); } +void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { glColor4ub(r, g, b, a); } +void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); } +void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Initialize drawing mode (how to organize vertex) +void rlBegin(int mode) +{ + // Draw mode can be RL_LINES, RL_TRIANGLES and RL_QUADS + // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode != mode) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) + { + RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; + RLGL.currentBatch->drawCounter++; + } + } + + if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = mode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = RLGL.State.defaultTextureId; + } +} + +// Finish vertex providing +void rlEnd(void) +{ + // NOTE: Depth increment is dependant on rlOrtho(): z-near and z-far values, + // as well as depth buffer bit-depth (16bit or 24bit or 32bit) + // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) + RLGL.currentBatch->currentDepth += (1.0f/20000.0f); + + // Verify internal buffers limits + // NOTE: This check is combined with usage of rlCheckRenderBatchLimit() + if (RLGL.State.vertexCounter >= (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4 - 4)) + { + // WARNING: If we are between rlPushMatrix() and rlPopMatrix() and we need to force a rlDrawRenderBatch(), + // we need to call rlPopMatrix() before to recover *RLGL.State.currentMatrix (RLGL.State.modelview) for the next forced draw call! + // If we have multiple matrix pushed, it will require "RLGL.State.stackCounter" pops before launching the draw + for (int i = RLGL.State.stackCounter; i >= 0; i--) rlPopMatrix(); + rlDrawRenderBatch(RLGL.currentBatch); + } +} + +// Define one vertex (position) +// NOTE: Vertex position data is the basic information required for drawing +void rlVertex3f(float x, float y, float z) +{ + float tx = x; + float ty = y; + float tz = z; + + // Transform provided vector if required + if (RLGL.State.transformRequired) + { + tx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z + RLGL.State.transform.m12; + ty = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z + RLGL.State.transform.m13; + tz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z + RLGL.State.transform.m14; + } + + // Verify that current vertex buffer elements limit has not been reached + if (RLGL.State.vertexCounter < (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4)) + { + // Add vertices + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter] = tx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 1] = ty; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 2] = tz; + + // Add current texcoord + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter] = RLGL.State.texcoordx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter + 1] = RLGL.State.texcoordy; + + // TODO: Add current normal + // By default rlVertexBuffer type does not store normals + + // Add current color + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter] = RLGL.State.colorr; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 1] = RLGL.State.colorg; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 2] = RLGL.State.colorb; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 3] = RLGL.State.colora; + + RLGL.State.vertexCounter++; + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount++; + } + else TRACELOG(RL_LOG_ERROR, "RLGL: Batch elements overflow"); +} + +// Define one vertex (position) +void rlVertex2f(float x, float y) +{ + rlVertex3f(x, y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (position) +void rlVertex2i(int x, int y) +{ + rlVertex3f((float)x, (float)y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (texture coordinate) +// NOTE: Texture coordinates are limited to QUADS only +void rlTexCoord2f(float x, float y) +{ + RLGL.State.texcoordx = x; + RLGL.State.texcoordy = y; +} + +// Define one vertex (normal) +// NOTE: Normals limited to TRIANGLES only? +void rlNormal3f(float x, float y, float z) +{ + RLGL.State.normalx = x; + RLGL.State.normaly = y; + RLGL.State.normalz = z; +} + +// Define one vertex (color) +void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) +{ + RLGL.State.colorr = x; + RLGL.State.colorg = y; + RLGL.State.colorb = z; + RLGL.State.colora = w; +} + +// Define one vertex (color) +void rlColor4f(float r, float g, float b, float a) +{ + rlColor4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); +} + +// Define one vertex (color) +void rlColor3f(float x, float y, float z) +{ + rlColor4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); +} + +#endif + +//-------------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL style functions (common to 1.1, 3.3+, ES2) +//-------------------------------------------------------------------------------------- + +// Set current texture to use +void rlSetTexture(unsigned int id) +{ + if (id == 0) + { +#if defined(GRAPHICS_API_OPENGL_11) + rlDisableTexture(); +#else + // NOTE: If quads batch limit is reached, we force a draw call and next batch starts + if (RLGL.State.vertexCounter >= + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4) + { + rlDrawRenderBatch(RLGL.currentBatch); + } +#endif + } + else + { +#if defined(GRAPHICS_API_OPENGL_11) + rlEnableTexture(id); +#else + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId != id) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) + { + RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; + + RLGL.currentBatch->drawCounter++; + } + } + + if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = id; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; + } +#endif + } +} + +// Select and active a texture slot +void rlActiveTextureSlot(int slot) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glActiveTexture(GL_TEXTURE0 + slot); +#endif +} + +// Enable texture +void rlEnableTexture(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, id); +} + +// Disable texture +void rlDisableTexture(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Enable texture cubemap +void rlEnableTextureCubemap(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_CUBE_MAP, id); +#endif +} + +// Disable texture cubemap +void rlDisableTextureCubemap(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif +} + +// Set texture parameters (wrap mode/filter mode) +void rlTextureParameters(unsigned int id, int param, int value) +{ + glBindTexture(GL_TEXTURE_2D, id); + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_2D, param, value); + else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); +#endif + } + else glTexParameteri(GL_TEXTURE_2D, param, value); + + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; + case RL_TEXTURE_FILTER_ANISOTROPIC: + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) + { + TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, RLGL.ExtSupported.maxAnisotropyLevel); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); +#endif + } break; + default: break; + } + + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Enable shader program +void rlEnableShader(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(id); +#endif +} + +// Disable shader program +void rlDisableShader(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(0); +#endif +} + +// Enable rendering to texture (fbo) +void rlEnableFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); +#endif +} + +// Disable rendering to texture +void rlDisableFramebuffer(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Activate multiple draw color buffers +// NOTE: One color buffer is always active by default +void rlActiveDrawBuffers(int count) +{ +#if (defined(GRAPHICS_API_OPENGL_33) && defined(RLGL_RENDER_TEXTURES_HINT)) + // NOTE: Maximum number of draw buffers supported is implementation dependant, + // it can be queried with glGet*() but it must be at least 8 + //GLint maxDrawBuffers = 0; + //glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); + + if (count > 0) + { + if (count > 8) TRACELOG(LOG_WARNING, "GL: Max color buffers limited to 8"); + else + { + unsigned int buffers[8] = { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3, + GL_COLOR_ATTACHMENT4, + GL_COLOR_ATTACHMENT5, + GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT7, + }; + + glDrawBuffers(count, buffers); + } + } + else TRACELOG(LOG_WARNING, "GL: One color buffer active by default"); +#endif +} + +//---------------------------------------------------------------------------------- +// General render state configuration +//---------------------------------------------------------------------------------- + +// Enable color blending +void rlEnableColorBlend(void) { glEnable(GL_BLEND); } + +// Disable color blending +void rlDisableColorBlend(void) { glDisable(GL_BLEND); } + +// Enable depth test +void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } + +// Disable depth test +void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } + +// Enable depth write +void rlEnableDepthMask(void) { glDepthMask(GL_TRUE); } + +// Disable depth write +void rlDisableDepthMask(void) { glDepthMask(GL_FALSE); } + +// Enable backface culling +void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } + +// Disable backface culling +void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } + +// Enable scissor test +void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } + +// Disable scissor test +void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } + +// Scissor test +void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } + +// Enable wire mode +void rlEnableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); +#endif +} + +// Disable wire mode +void rlDisableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +} + +// Set the line drawing width +void rlSetLineWidth(float width) { glLineWidth(width); } + +// Get the line drawing width +float rlGetLineWidth(void) +{ + float width = 0; + glGetFloatv(GL_LINE_WIDTH, &width); + return width; +} + +// Enable line aliasing +void rlEnableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_LINE_SMOOTH); +#endif +} + +// Disable line aliasing +void rlDisableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_LINE_SMOOTH); +#endif +} + +// Enable stereo rendering +void rlEnableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = true; +#endif +} + +// Disable stereo rendering +void rlDisableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = false; +#endif +} + +// Check if stereo render is enabled +bool rlIsStereoRenderEnabled(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + return RLGL.State.stereoRender; +#else + return false; +#endif +} + +// Clear color buffer with color +void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + // Color values clamp to 0.0f(0) and 1.0f(255) + float cr = (float)r/255; + float cg = (float)g/255; + float cb = (float)b/255; + float ca = (float)a/255; + + glClearColor(cr, cg, cb, ca); +} + +// Clear used screen buffers (color and depth) +void rlClearScreenBuffers(void) +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: Color and Depth (Depth is used for 3D) + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... +} + +// Check and log OpenGL error codes +void rlCheckErrors() +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int check = 1; + while (check) + { + const GLenum err = glGetError(); + switch (err) + { + case GL_NO_ERROR: check = 0; break; + case 0x0500: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); break; + case 0x0501: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); break; + case 0x0502: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); break; + case 0x0503: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); break; + case 0x0504: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); break; + case 0x0505: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); break; + case 0x0506: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); break; + default: TRACELOG(RL_LOG_WARNING, "GL: Error detected: Unknown error code: %x", err); break; + } + } +#endif +} + +// Set blend mode +void rlSetBlendMode(int mode) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentBlendMode != mode) + { + rlDrawRenderBatch(RLGL.currentBatch); + + switch (mode) + { + case RL_BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; + case RL_BLEND_CUSTOM: glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); break; + default: break; + } + + RLGL.State.currentBlendMode = mode; + } +#endif +} + +// Set blending mode factor and equation +void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.glBlendSrcFactor = glSrcFactor; + RLGL.State.glBlendDstFactor = glDstFactor; + RLGL.State.glBlendEquation = glEquation; +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL Debug +//---------------------------------------------------------------------------------- +#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) +static void GLAPIENTRY rlDebugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) +{ + // Ignore non-significant error/warning codes (NVidia drivers) + // NOTE: Here there are the details with a sample output: + // - #131169 - Framebuffer detailed info: The driver allocated storage for renderbuffer 2. (severity: low) + // - #131185 - Buffer detailed info: Buffer object 1 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_ENUM_88e4) + // will use VIDEO memory as the source for buffer object operations. (severity: low) + // - #131218 - Program/shader state performance warning: Vertex shader in program 7 is being recompiled based on GL state. (severity: medium) + // - #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0 does not have + // a defined base level and cannot be used for texture mapping. (severity: low) + if ((id == 131169) || (id == 131185) || (id == 131218) || (id == 131204)) return; + + const char *msgSource = NULL; + switch (source) + { + case GL_DEBUG_SOURCE_API: msgSource = "API"; break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: msgSource = "WINDOW_SYSTEM"; break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: msgSource = "SHADER_COMPILER"; break; + case GL_DEBUG_SOURCE_THIRD_PARTY: msgSource = "THIRD_PARTY"; break; + case GL_DEBUG_SOURCE_APPLICATION: msgSource = "APPLICATION"; break; + case GL_DEBUG_SOURCE_OTHER: msgSource = "OTHER"; break; + default: break; + } + + const char *msgType = NULL; + switch (type) + { + case GL_DEBUG_TYPE_ERROR: msgType = "ERROR"; break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: msgType = "DEPRECATED_BEHAVIOR"; break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: msgType = "UNDEFINED_BEHAVIOR"; break; + case GL_DEBUG_TYPE_PORTABILITY: msgType = "PORTABILITY"; break; + case GL_DEBUG_TYPE_PERFORMANCE: msgType = "PERFORMANCE"; break; + case GL_DEBUG_TYPE_MARKER: msgType = "MARKER"; break; + case GL_DEBUG_TYPE_PUSH_GROUP: msgType = "PUSH_GROUP"; break; + case GL_DEBUG_TYPE_POP_GROUP: msgType = "POP_GROUP"; break; + case GL_DEBUG_TYPE_OTHER: msgType = "OTHER"; break; + default: break; + } + + const char *msgSeverity = "DEFAULT"; + switch (severity) + { + case GL_DEBUG_SEVERITY_LOW: msgSeverity = "LOW"; break; + case GL_DEBUG_SEVERITY_MEDIUM: msgSeverity = "MEDIUM"; break; + case GL_DEBUG_SEVERITY_HIGH: msgSeverity = "HIGH"; break; + case GL_DEBUG_SEVERITY_NOTIFICATION: msgSeverity = "NOTIFICATION"; break; + default: break; + } + + TRACELOG(LOG_WARNING, "GL: OpenGL debug message: %s", message); + TRACELOG(LOG_WARNING, " > Type: %s", msgType); + TRACELOG(LOG_WARNING, " > Source = %s", msgSource); + TRACELOG(LOG_WARNING, " > Severity = %s", msgSeverity); +} +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition - rlgl functionality +//---------------------------------------------------------------------------------- + +// Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states +void rlglInit(int width, int height) +{ + // Enable OpenGL debug context if required +#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) + if ((glDebugMessageCallback != NULL) && (glDebugMessageControl != NULL)) + { + glDebugMessageCallback(rlDebugMessageCallback, 0); + // glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_HIGH, 0, 0, GL_TRUE); // TODO: Filter message + + // Debug context options: + // - GL_DEBUG_OUTPUT - Faster version but not useful for breakpoints + // - GL_DEBUG_OUTPUT_SYNCHRONUS - Callback is in sync with errors, so a breakpoint can be placed on the callback in order to get a stacktrace for the GL error + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + } +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Init default white texture + unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) + RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + + if (RLGL.State.defaultTextureId != 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully", RLGL.State.defaultTextureId); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load default texture"); + + // Init default Shader (customized for GL 3.3 and ES2) + // Loaded: RLGL.State.defaultShaderId + RLGL.State.defaultShaderLocs + rlLoadShaderDefault(); + RLGL.State.currentShaderId = RLGL.State.defaultShaderId; + RLGL.State.currentShaderLocs = RLGL.State.defaultShaderLocs; + + // Init default vertex arrays buffers + RLGL.defaultBatch = rlLoadRenderBatch(RL_DEFAULT_BATCH_BUFFERS, RL_DEFAULT_BATCH_BUFFER_ELEMENTS); + RLGL.currentBatch = &RLGL.defaultBatch; + + // Init stack matrices (emulating OpenGL 1.1) + for (int i = 0; i < RL_MAX_MATRIX_STACK_SIZE; i++) RLGL.State.stack[i] = rlMatrixIdentity(); + + // Init internal matrices + RLGL.State.transform = rlMatrixIdentity(); + RLGL.State.projection = rlMatrixIdentity(); + RLGL.State.modelview = rlMatrixIdentity(); + RLGL.State.currentMatrix = &RLGL.State.modelview; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + + // Initialize OpenGL default states + //---------------------------------------------------------- + // Init state: Depth test + glDepthFunc(GL_LEQUAL); // Type of depth testing to apply + glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) + + // Init state: Blending mode + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Color blending function (how colors are mixed) + glEnable(GL_BLEND); // Enable color blending (required to work with transparencies) + + // Init state: Culling + // NOTE: All shapes/models triangles are drawn CCW + glCullFace(GL_BACK); // Cull the back face (default) + glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) + glEnable(GL_CULL_FACE); // Enable backface culling + + // Init state: Cubemap seamless +#if defined(GRAPHICS_API_OPENGL_33) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + // Init state: Color hints (deprecated in OpenGL 3.0+) + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation + glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Store screen size into global variables + RLGL.State.framebufferWidth = width; + RLGL.State.framebufferHeight = height; + + TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); + //---------------------------------------------------------- +#endif + + // Init state: Color/Depth buffers clear + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) + glClearDepth(1.0f); // Set clear depth value (default) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) +} + +// Vertex Buffer Object deinitialization (memory free) +void rlglClose(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlUnloadRenderBatch(RLGL.defaultBatch); + + rlUnloadShaderDefault(); // Unload default shader + + glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId); +#endif +} + +// Load OpenGL extensions +// NOTE: External loader function must be provided +void rlLoadExtensions(void *loader) +{ +#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 + // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) + #if !defined(__APPLE__) + if (gladLoadGL((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); + else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); + #endif + + // Get number of supported extensions + GLint numExt = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + // Get supported extensions list + // WARNING: glGetStringi() not available on OpenGL 2.1 + TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", glGetStringi(GL_EXTENSIONS, i)); +#endif + + // Register supported extensions flags + // OpenGL 3.3 extensions supported by default (core) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; + #if defined(GRAPHICS_API_OPENGL_43) + if (GLAD_GL_ARB_compute_shader) RLGL.ExtSupported.computeShader = true; + if (GLAD_GL_ARB_shader_storage_buffer_object) RLGL.ExtSupported.ssbo = true; + #endif + #if !defined(__APPLE__) + // NOTE: With GLAD, we can check if an extension is supported using the GLAD_GL_xxx booleans + if (GLAD_GL_EXT_texture_compression_s3tc) RLGL.ExtSupported.texCompDXT = true; // Texture compression: DXT + if (GLAD_GL_ARB_ES3_compatibility) RLGL.ExtSupported.texCompETC2 = true; // Texture compression: ETC2/EAC + #endif +#endif // GRAPHICS_API_OPENGL_33 + +#if defined(GRAPHICS_API_OPENGL_ES2) + // Get supported extensions list + GLint numExt = 0; + const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) + const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string + + // NOTE: We have to duplicate string because glGetString() returns a const string + int size = strlen(extensions) + 1; // Get extensions string size in bytes + char *extensionsDup = (char *)RL_CALLOC(size, sizeof(char)); + strcpy(extensionsDup, extensions); + extList[numExt] = extensionsDup; + + for (int i = 0; i < size; i++) + { + if (extensionsDup[i] == ' ') + { + extensionsDup[i] = '\0'; + numExt++; + extList[numExt] = &extensionsDup[i + 1]; + } + } + + TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", extList[i]); +#endif + + // Check required extensions + for (int i = 0; i < numExt; i++) + { + // Check VAO support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) + { + // The extension is supported by our hardware and driver, try to get related functions pointers + // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... + glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glGenVertexArraysOES"); + glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)((rlglLoadProc)loader)("glBindVertexArrayOES"); + glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glDeleteVertexArraysOES"); + //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)loader("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted + + if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; + } + + // Check instanced rendering support + if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // Web ANGLE + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); + + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + else + { + if ((strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) && // Standard EXT + (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0)) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); + + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + } + + // Check NPOT textures support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; + + // Check texture float support + if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; + + // 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_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; + if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; + + // Check texture compression support: DXT + if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; + + // Check texture compression support: ETC1 + if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; + + // Check texture compression support: ETC2/EAC + if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; + + // Check texture compression support: PVR + if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; + + // Check texture compression support: ASTC + if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; + + // Check anisotropic texture filter support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) RLGL.ExtSupported.texAnisoFilter = true; + + // Check clamp mirror wrap mode support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; + } + + // Free extensions pointers + RL_FREE(extList); + RL_FREE(extensionsDup); // Duplicated string must be deallocated +#endif // GRAPHICS_API_OPENGL_ES2 + + // Check OpenGL information and capabilities + //------------------------------------------------------------------------------ + // Show current OpenGL and GLSL version + TRACELOG(RL_LOG_INFO, "GL: OpenGL device information:"); + TRACELOG(RL_LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); + TRACELOG(RL_LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); + TRACELOG(RL_LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); + TRACELOG(RL_LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Anisotropy levels capability is an extension + #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + #endif + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &RLGL.ExtSupported.maxAnisotropyLevel); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + // Show some OpenGL GPU capabilities + TRACELOG(RL_LOG_INFO, "GL: OpenGL capabilities:"); + GLint capability = 0; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_CUBE_MAP_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_IMAGE_UNITS: %i", capability); + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIBS: %i", capability); + #if !defined(GRAPHICS_API_OPENGL_ES2) + glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_BLOCK_SIZE: %i", capability); + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_DRAW_BUFFERS: %i", capability); + if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_MAX_ANISOTROPY: %.0f", RLGL.ExtSupported.maxAnisotropyLevel); + #endif + glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &capability); + TRACELOG(RL_LOG_INFO, " GL_NUM_COMPRESSED_TEXTURE_FORMATS: %i", capability); + GLint *compFormats = (GLint *)RL_CALLOC(capability, sizeof(GLint)); + glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, compFormats); + for (int i = 0; i < capability; i++) TRACELOG(RL_LOG_INFO, " %s", rlGetCompressedFormatName(compFormats[i])); + RL_FREE(compFormats); + +#if defined(GRAPHICS_API_OPENGL_43) + glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIB_BINDINGS: %i", capability); + glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_LOCATIONS: %i", capability); +#endif // GRAPHICS_API_OPENGL_43 +#else // RLGL_SHOW_GL_DETAILS_INFO + + // Show some basic info about GL supported features + #if defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) TRACELOG(RL_LOG_INFO, "GL: VAO extension detected, VAO functions loaded successfully"); + else TRACELOG(RL_LOG_WARNING, "GL: VAO extension not found, VAO not supported"); + if (RLGL.ExtSupported.texNPOT) TRACELOG(RL_LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); + else TRACELOG(RL_LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); + #endif + if (RLGL.ExtSupported.texCompDXT) TRACELOG(RL_LOG_INFO, "GL: DXT compressed textures supported"); + if (RLGL.ExtSupported.texCompETC1) TRACELOG(RL_LOG_INFO, "GL: ETC1 compressed textures supported"); + if (RLGL.ExtSupported.texCompETC2) TRACELOG(RL_LOG_INFO, "GL: ETC2/EAC compressed textures supported"); + if (RLGL.ExtSupported.texCompPVRT) TRACELOG(RL_LOG_INFO, "GL: PVRT compressed textures supported"); + if (RLGL.ExtSupported.texCompASTC) TRACELOG(RL_LOG_INFO, "GL: ASTC compressed textures supported"); + if (RLGL.ExtSupported.computeShader) TRACELOG(RL_LOG_INFO, "GL: Compute shaders supported"); + if (RLGL.ExtSupported.ssbo) TRACELOG(RL_LOG_INFO, "GL: Shader storage buffer objects supported"); +#endif // RLGL_SHOW_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +} + +// Get current OpenGL version +int rlGetVersion(void) +{ + int glVersion = 0; +#if defined(GRAPHICS_API_OPENGL_11) + glVersion = OPENGL_11; +#endif +#if defined(GRAPHICS_API_OPENGL_21) + #if defined(__APPLE__) + glVersion = OPENGL_33; // NOTE: Force OpenGL 3.3 on OSX + #else + glVersion = OPENGL_21; + #endif +#elif defined(GRAPHICS_API_OPENGL_33) + glVersion = OPENGL_33; +#endif +#if defined(GRAPHICS_API_OPENGL_43) + glVersion = OPENGL_43; +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glVersion = OPENGL_ES_20; +#endif + return glVersion; +} + +// Get default framebuffer width +int rlGetFramebufferWidth(void) +{ + int width = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + width = RLGL.State.framebufferWidth; +#endif + return width; +} + +// Get default framebuffer height +int rlGetFramebufferHeight(void) +{ + int height = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + height = RLGL.State.framebufferHeight; +#endif + return height; +} + +// Get default internal texture (white texture) +// NOTE: Default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 +unsigned int rlGetTextureIdDefault(void) +{ + unsigned int id = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + id = RLGL.State.defaultTextureId; +#endif + return id; +} + +// Get default shader id +unsigned int rlGetShaderIdDefault(void) +{ + unsigned int id = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + id = RLGL.State.defaultShaderId; +#endif + return id; +} + +// Get default shader locs +int *rlGetShaderLocsDefault(void) +{ + int *locs = NULL; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + locs = RLGL.State.defaultShaderLocs; +#endif + return locs; +} + +// Render batch management +//------------------------------------------------------------------------------------------------ +// Load render batch +rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) +{ + rlRenderBatch batch = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) + //-------------------------------------------------------------------------------------------- + batch.vertexBuffer = (rlVertexBuffer *)RL_MALLOC(numBuffers*sizeof(rlVertexBuffer)); + + for (int i = 0; i < numBuffers; i++) + { + batch.vertexBuffer[i].elementCount = bufferElements; + + batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad + batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad +#if defined(GRAPHICS_API_OPENGL_33) + batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + batch.vertexBuffer[i].indices = (unsigned short *)RL_MALLOC(bufferElements*6*sizeof(unsigned short)); // 6 int by quad (indices) +#endif + + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; + for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; + for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; + + int k = 0; + + // Indices can be initialized right now + for (int j = 0; j < (6*bufferElements); j += 6) + { + batch.vertexBuffer[i].indices[j] = 4*k; + batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; + batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 3] = 4*k; + batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; + + k++; + } + + RLGL.State.vertexCounter = 0; + } + + TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)"); + //-------------------------------------------------------------------------------------------- + + // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs + //-------------------------------------------------------------------------------------------- + for (int i = 0; i < numBuffers; i++) + { + if (RLGL.ExtSupported.vao) + { + // Initialize Quads VAO + glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); + glBindVertexArray(batch.vertexBuffer[i].vaoId); + } + + // Quads - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + + // Vertex texcoord buffer (shader-location = 1) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + + // Vertex color buffer (shader-location = 3) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + // Fill index buffer + glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); +#if defined(GRAPHICS_API_OPENGL_33) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif + } + + TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)"); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + //-------------------------------------------------------------------------------------------- + + // Init draw calls tracking system + //-------------------------------------------------------------------------------------------- + batch.draws = (rlDrawCall *)RL_MALLOC(RL_DEFAULT_BATCH_DRAWCALLS*sizeof(rlDrawCall)); + + for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) + { + batch.draws[i].mode = RL_QUADS; + batch.draws[i].vertexCount = 0; + batch.draws[i].vertexAlignment = 0; + //batch.draws[i].vaoId = 0; + //batch.draws[i].shaderId = 0; + batch.draws[i].textureId = RLGL.State.defaultTextureId; + //batch.draws[i].RLGL.State.projection = rlMatrixIdentity(); + //batch.draws[i].RLGL.State.modelview = rlMatrixIdentity(); + } + + batch.bufferCount = numBuffers; // Record buffer count + batch.drawCounter = 1; // Reset draws counter + batch.currentDepth = -1.0f; // Reset depth value + //-------------------------------------------------------------------------------------------- +#endif + + return batch; +} + +// Unload default internal buffers vertex data from CPU and GPU +void rlUnloadRenderBatch(rlRenderBatch batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Unbind everything + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // Unload all vertex buffers data + for (int i = 0; i < batch.bufferCount; i++) + { + // Unbind VAO attribs data + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(batch.vertexBuffer[i].vaoId); + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); + glDisableVertexAttribArray(3); + glBindVertexArray(0); + } + + // Delete VBOs from GPU (VRAM) + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); + + // Delete VAOs from GPU (VRAM) + if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); + + // Free vertex arrays memory from CPU (RAM) + RL_FREE(batch.vertexBuffer[i].vertices); + RL_FREE(batch.vertexBuffer[i].texcoords); + RL_FREE(batch.vertexBuffer[i].colors); + RL_FREE(batch.vertexBuffer[i].indices); + } + + // Unload arrays + RL_FREE(batch.vertexBuffer); + RL_FREE(batch.draws); +#endif +} + +// Draw render batch +// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) +void rlDrawRenderBatch(rlRenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Update batch vertex buffers + //------------------------------------------------------------------------------------------------------------ + // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) + // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (change flag required) + if (RLGL.State.vertexCounter > 0) + { + // Activate elements VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + + // Vertex positions buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer + + // Texture coordinates buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer + + // Colors buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer + + // NOTE: glMapBuffer() causes sync issue. + // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job. + // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer(). + // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new + // allocated pointer immediately even if GPU is still working with the previous data. + + // Another option: map the buffer object into client's memory + // Probably this code could be moved somewhere else... + // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + // if (batch->vertexBuffer[batch->currentBuffer].vertices) + // { + // Update vertex data + // } + // glUnmapBuffer(GL_ARRAY_BUFFER); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + } + //------------------------------------------------------------------------------------------------------------ + + // Draw batch vertex buffers (considering VR stereo if required) + //------------------------------------------------------------------------------------------------------------ + Matrix matProjection = RLGL.State.projection; + Matrix matModelView = RLGL.State.modelview; + + int eyeCount = 1; + if (RLGL.State.stereoRender) eyeCount = 2; + + for (int eye = 0; eye < eyeCount; eye++) + { + if (eyeCount == 2) + { + // Setup current eye viewport (half screen width) + rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); + + // Set current eye view offset to modelview matrix + rlSetMatrixModelview(rlMatrixMultiply(matModelView, RLGL.State.viewOffsetStereo[eye])); + // Set current eye projection matrix + rlSetMatrixProjection(RLGL.State.projectionStereo[eye]); + } + + // Draw buffers + if (RLGL.State.vertexCounter > 0) + { + // Set current shader and upload current MVP matrix + glUseProgram(RLGL.State.currentShaderId); + + // Create modelview-projection matrix and upload to shader + Matrix matMVP = rlMatrixMultiply(RLGL.State.modelview, RLGL.State.projection); + float matMVPfloat[16] = { + matMVP.m0, matMVP.m1, matMVP.m2, matMVP.m3, + matMVP.m4, matMVP.m5, matMVP.m6, matMVP.m7, + matMVP.m8, matMVP.m9, matMVP.m10, matMVP.m11, + matMVP.m12, matMVP.m13, matMVP.m14, matMVP.m15 + }; + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MVP], 1, false, matMVPfloat); + + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + else + { + // Bind vertex attrib: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); + + // Bind vertex attrib: texcoord (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + } + + // Setup some default shader values + glUniform4f(RLGL.State.currentShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1i(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE], 0); // Active default sampler2D: texture0 + + // Activate additional sampler textures + // Those additional textures will be common for all draw calls of the batch + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] > 0) + { + glActiveTexture(GL_TEXTURE0 + 1 + i); + glBindTexture(GL_TEXTURE_2D, RLGL.State.activeTextureId[i]); + } + } + + // Activate default sampler2D texture0 (one texture is always active for default batch shader) + // NOTE: Batch system accumulates calls by texture0 changes, additional textures are enabled for all the draw calls + glActiveTexture(GL_TEXTURE0); + + for (int i = 0, vertexOffset = 0; i < batch->drawCounter; i++) + { + // Bind current draw call texture, activated as GL_TEXTURE0 and binded to sampler2D texture0 by default + glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); + + if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); + else + { +#if defined(GRAPHICS_API_OPENGL_33) + // We need to define the number of indices to be processed: elementCount*6 + // NOTE: The final parameter tells the GPU the offset in bytes from the + // start of the index buffer to the location of the first index to process + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); +#endif + } + + vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); + } + + if (!RLGL.ExtSupported.vao) + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO + + glUseProgram(0); // Unbind shader program + } + //------------------------------------------------------------------------------------------------------------ + + // Reset batch buffers + //------------------------------------------------------------------------------------------------------------ + // Reset vertex counter for next frame + RLGL.State.vertexCounter = 0; + + // Reset depth for next draw + batch->currentDepth = -1.0f; + + // Restore projection/modelview matrices + RLGL.State.projection = matProjection; + RLGL.State.modelview = matModelView; + + // Reset RLGL.currentBatch->draws array + for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) + { + batch->draws[i].mode = RL_QUADS; + batch->draws[i].vertexCount = 0; + batch->draws[i].textureId = RLGL.State.defaultTextureId; + } + + // Reset active texture units for next batch + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) RLGL.State.activeTextureId[i] = 0; + + // Reset draws counter to one draw for the batch + batch->drawCounter = 1; + //------------------------------------------------------------------------------------------------------------ + + // Change to next buffer in the list (in case of multi-buffering) + batch->currentBuffer++; + if (batch->currentBuffer >= batch->bufferCount) batch->currentBuffer = 0; +#endif +} + +// Set the active render batch for rlgl +void rlSetRenderBatchActive(rlRenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); + + if (batch != NULL) RLGL.currentBatch = batch; + else RLGL.currentBatch = &RLGL.defaultBatch; +#endif +} + +// Update and draw internal render batch +void rlDrawRenderBatchActive(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside +#endif +} + +// Check internal buffer overflow for a given number of vertex +// and force a rlRenderBatch draw call if required +bool rlCheckRenderBatchLimit(int vCount) +{ + bool overflow = false; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.vertexCounter + vCount) >= + (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4)) + { + int currentMode = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode; + int currentTexture = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId; + + overflow = true; + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside + + // Restore state of last batch so we can continue adding vertices + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = currentMode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = currentTexture; + } +#endif + + return overflow; +} + +// Textures data management +//----------------------------------------------------------------------------------------- +// Convert image data to OpenGL texture (returns OpenGL valid Id) +unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount) +{ + glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding + + unsigned int id = 0; + + // Check texture format support by OpenGL 1.1 (compressed textures not supported) +#if defined(GRAPHICS_API_OPENGL_11) + if (format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(RL_LOG_WARNING, "GL: OpenGL 1.1 does not support GPU compressed texture formats"); + return id; + } +#else + if ((!RLGL.ExtSupported.texCompDXT) && ((format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA) || + (format == RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: DXT compressed texture format not supported"); + return id; + } +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((!RLGL.ExtSupported.texCompETC1) && (format == RL_PIXELFORMAT_COMPRESSED_ETC1_RGB)) + { + TRACELOG(RL_LOG_WARNING, "GL: ETC1 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompETC2) && ((format == RL_PIXELFORMAT_COMPRESSED_ETC2_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: ETC2 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompPVRT) && ((format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: PVRT compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompASTC) && ((format == RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: ASTC compressed texture format not supported"); + return id; + } +#endif +#endif // GRAPHICS_API_OPENGL_11 + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glGenTextures(1, &id); // Generate texture id + + glBindTexture(GL_TEXTURE_2D, id); + + int mipWidth = width; + int mipHeight = height; + int mipOffset = 0; // Mipmap data offset + + // Load the different mipmap levels + for (int i = 0; i < mipmapCount; i++) + { + unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); + + int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + TRACELOGD("TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); + + if (glInternalFormat != -1) + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, (unsigned char *)data + mipOffset); +#if !defined(GRAPHICS_API_OPENGL_11) + else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, (unsigned char *)data + mipOffset); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + } + + mipWidth /= 2; + mipHeight /= 2; + mipOffset += mipSize; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + } + + // Texture parameters configuration + // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used + if (RLGL.ExtSupported.texNPOT) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis + } + else + { + // NOTE: If using negative texture coordinates (LoadOBJ()), it does not work! + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis + } +#else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis +#endif + + // Magnification and minification filters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + +#if defined(GRAPHICS_API_OPENGL_33) + if (mipmapCount > 1) + { + // Activate Trilinear filtering if mipmaps are available + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } +#endif + + // At this point we have the texture loaded in GPU and texture parameters configured + + // NOTE: If mipmaps were not in data, they are not generated automatically + + // Unbind current texture + glBindTexture(GL_TEXTURE_2D, 0); + + if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Texture loaded successfully (%ix%i | %s | %i mipmaps)", id, width, height, rlGetPixelFormatName(format), mipmapCount); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load texture"); + + return id; +} + +// Load depth texture/renderbuffer (to be attached to fbo) +// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture/WEBGL_depth_texture extensions +unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) +{ + unsigned int id = 0; + +#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; +#endif + + if (!useRenderBuffer && RLGL.ExtSupported.texDepth) + { + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + + TRACELOG(RL_LOG_INFO, "TEXTURE: Depth texture loaded successfully"); + } + else + { + // Create the renderbuffer that will serve as the depth attachment for the framebuffer + // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices + glGenRenderbuffers(1, &id); + glBindRenderbuffer(GL_RENDERBUFFER, id); + glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Depth renderbuffer loaded successfully (%i bits)", id, (RLGL.ExtSupported.maxDepthBits >= 24)? RLGL.ExtSupported.maxDepthBits : 16); + } +#endif + + return id; +} + +// Load texture cubemap +// NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), +// expected the following convention: +X, -X, +Y, -Y, +Z, -Z +unsigned int rlLoadTextureCubemap(void *data, int size, int format) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int dataSize = rlGetPixelDataSize(size, size, format); + + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + + int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if (glInternalFormat != -1) + { + // Load cubemap faces + for (unsigned int i = 0; i < 6; i++) + { + if (data == NULL) + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + if (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32) + { + // Instead of using a sized internal texture format (GL_RGB16F, GL_RGB32F), we let the driver to choose the better format for us (GL_RGB) + if (RLGL.ExtSupported.texFloat32) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, size, size, 0, GL_RGB, GL_FLOAT, NULL); + else TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + } + else if ((format == RL_PIXELFORMAT_UNCOMPRESSED_R32) || (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32)) TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, NULL); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); + } + else + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, (unsigned char *)data + i*dataSize); + else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, dataSize, (unsigned char *)data + i*dataSize); + } + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + } + } + + // Set cubemap texture sampling parameters + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#if defined(GRAPHICS_API_OPENGL_33) + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 +#endif + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif + + if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Cubemap texture loaded successfully (%ix%i)", id, size, size); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load cubemap texture"); + + return id; +} + +// Update already loaded texture in GPU with new data +// NOTE: We don't know safely if internal texture format is the expected one... +void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) +{ + glBindTexture(GL_TEXTURE_2D, id); + + int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if ((glInternalFormat != -1) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, data); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to update for current texture format (%i)", id, format); +} + +// Get OpenGL internal formats and data type from raylib PixelFormat +void rlGetGlTextureFormats(int format, int *glInternalFormat, int *glFormat, int *glType) +{ + *glInternalFormat = -1; + *glFormat = -1; + *glType = -1; + + switch (format) + { + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + #if !defined(GRAPHICS_API_OPENGL_11) + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + #endif + #elif defined(GRAPHICS_API_OPENGL_33) + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + #endif + #if !defined(GRAPHICS_API_OPENGL_11) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + #endif + default: TRACELOG(RL_LOG_WARNING, "TEXTURE: Current format not supported (%i)", format); break; + } +} + +// Unload texture from GPU memory +void rlUnloadTexture(unsigned int id) +{ + glDeleteTextures(1, &id); +} + +// Generate mipmap data for selected texture +void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps) +{ + glBindTexture(GL_TEXTURE_2D, id); + + // Check if texture is power-of-two (POT) + bool texIsPOT = false; + + if (((width > 0) && ((width & (width - 1)) == 0)) && + ((height > 0) && ((height & (height - 1)) == 0))) texIsPOT = true; + +#if defined(GRAPHICS_API_OPENGL_11) + if (texIsPOT) + { + // WARNING: Manual mipmap generation only works for RGBA 32bit textures! + if (format == RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + // Retrieve texture data from VRAM + void *texData = rlReadTexturePixels(id, width, height, format); + + // NOTE: Texture data size is reallocated to fit mipmaps data + // NOTE: CPU mipmap generation only supports RGBA 32bit data + int mipmapCount = rlGenTextureMipmapsData(texData, width, height); + + int size = width*height*4; + int offset = size; + + int mipWidth = width/2; + int mipHeight = height/2; + + // Load the mipmaps + for (int level = 1; level < mipmapCount; level++) + { + glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, mipWidth, mipHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)texData + offset); + + size = mipWidth*mipHeight*4; + offset += size; + + mipWidth /= 2; + mipHeight /= 2; + } + + *mipmaps = mipmapCount + 1; + RL_FREE(texData); // Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data + + TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Mipmaps generated manually on CPU side, total: %i", id, *mipmaps); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps for provided texture format", id); + } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) + { + //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorythm: GL_FASTEST, GL_NICEST, GL_DONT_CARE + glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate Trilinear filtering for mipmaps + + #define MIN(a,b) (((a)<(b))?(a):(b)) + #define MAX(a,b) (((a)>(b))?(a):(b)) + + *mipmaps = 1 + (int)floor(log(MAX(width, height))/log(2)); + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Mipmaps generated automatically, total: %i", id, *mipmaps); + } +#endif + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps", id); + + glBindTexture(GL_TEXTURE_2D, 0); +} + + +// Read texture pixel data +void *rlReadTexturePixels(unsigned int id, int width, int height, int format) +{ + void *pixels = NULL; + +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + glBindTexture(GL_TEXTURE_2D, id); + + // NOTE: Using texture id, we can retrieve some texture info (but not on OpenGL ES 2.0) + // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE + //int width, height, format; + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); + + // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding. + // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting. + // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) + // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + unsigned int size = rlGetPixelDataSize(width, height, format); + + if ((glInternalFormat != -1) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + pixels = RL_MALLOC(size); + glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", id, format); + + glBindTexture(GL_TEXTURE_2D, 0); +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + // glGetTexImage() is not available on OpenGL ES 2.0 + // Texture width and height are required on OpenGL ES 2.0. There is no way to get it from texture id. + // Two possible Options: + // 1 - Bind texture to color fbo attachment and glReadPixels() + // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() + // We are using Option 1, just need to care for texture format on retrieval + // NOTE: This behaviour could be conditioned by graphic driver... + unsigned int fboId = rlLoadFramebuffer(width, height); + + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + glBindTexture(GL_TEXTURE_2D, 0); + + // Attach our texture to FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0); + + // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format + pixels = (unsigned char *)RL_MALLOC(rlGetPixelDataSize(width, height, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Clean up temporal fbo + rlUnloadFramebuffer(fboId); +#endif + + return pixels; +} + + +// Read screen pixel data (color buffer) +unsigned char *rlReadScreenPixels(int width, int height) +{ + unsigned char *screenData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); + + // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer + // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData); + + // Flip image vertically! + unsigned char *imgData = (unsigned char *)RL_MALLOC(width*height*4*sizeof(unsigned char)); + + for (int y = height - 1; y >= 0; y--) + { + for (int x = 0; x < (width*4); x++) + { + imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line + + // Set alpha component value to 255 (no trasparent image retrieval) + // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! + if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255; + } + } + + RL_FREE(screenData); + + return imgData; // NOTE: image data should be freed +} + +// Framebuffer management (fbo) +//----------------------------------------------------------------------------------------- +// Load a framebuffer to be used for rendering +// NOTE: No textures attached +unsigned int rlLoadFramebuffer(int width, int height) +{ + unsigned int fboId = 0; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glGenFramebuffers(1, &fboId); // Create the framebuffer object + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer +#endif + + return fboId; +} + +// Attach color buffer texture to an fbo (unloads previous attachment) +// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture +void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + + switch (attachType) + { + case RL_ATTACHMENT_COLOR_CHANNEL0: + case RL_ATTACHMENT_COLOR_CHANNEL1: + case RL_ATTACHMENT_COLOR_CHANNEL2: + case RL_ATTACHMENT_COLOR_CHANNEL3: + case RL_ATTACHMENT_COLOR_CHANNEL4: + case RL_ATTACHMENT_COLOR_CHANNEL5: + case RL_ATTACHMENT_COLOR_CHANNEL6: + case RL_ATTACHMENT_COLOR_CHANNEL7: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); + else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); + + } break; + case RL_ATTACHMENT_DEPTH: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + case RL_ATTACHMENT_STENCIL: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + default: break; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Verify render texture is complete +bool rlFramebufferComplete(unsigned int id) +{ + bool result = false; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + switch (status) + { + case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", id); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", id); break; +#if defined(GRAPHICS_API_OPENGL_ES2) + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", id); break; +#endif + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", id); break; + default: break; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + result = (status == GL_FRAMEBUFFER_COMPLETE); +#endif + + return result; +} + +// Unload framebuffer from GPU memory +// NOTE: All attached textures/cubemaps/renderbuffers are also deleted +void rlUnloadFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + + // Query depth attachment to automatically delete texture/renderbuffer + int depthType = 0, depthId = 0; + glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); + + unsigned int depthIdU = (unsigned int)depthId; + if (depthType == GL_RENDERBUFFER) glDeleteRenderbuffers(1, &depthIdU); + else if (depthType == GL_RENDERBUFFER) glDeleteTextures(1, &depthIdU); + + // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, + // the texture image is automatically detached from the currently bound framebuffer. + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &id); + + TRACELOG(RL_LOG_INFO, "FBO: [ID %i] Unloaded framebuffer from VRAM (GPU)", id); +#endif +} + +// Vertex data management +//----------------------------------------------------------------------------------------- +// Load a new attributes buffer +unsigned int rlLoadVertexBuffer(void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferData(GL_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Load a new attributes element buffer +unsigned int rlLoadVertexBufferElement(void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Enable vertex buffer (VBO) +void rlEnableVertexBuffer(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); +#endif +} + +// Disable vertex buffer (VBO) +void rlDisableVertexBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, 0); +#endif +} + +// Enable vertex buffer element (VBO element) +void rlEnableVertexBufferElement(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); +#endif +} + +// Disable vertex buffer element (VBO element) +void rlDisableVertexBufferElement(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +#endif +} + +// Update vertex buffer with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBuffer(unsigned int id, void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferSubData(GL_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +// Update vertex buffer elements with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBufferElements(unsigned int id, void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +// Enable vertex array object (VAO) +bool rlEnableVertexArray(unsigned int vaoId) +{ + bool result = false; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(vaoId); + result = true; + } +#endif + return result; +} + +// Disable vertex array object (VAO) +void rlDisableVertexArray(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) glBindVertexArray(0); +#endif +} + +// Enable vertex attribute index +void rlEnableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glEnableVertexAttribArray(index); +#endif +} + +// Disable vertex attribute index +void rlDisableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDisableVertexAttribArray(index); +#endif +} + +// Draw vertex array +void rlDrawVertexArray(int offset, int count) +{ + glDrawArrays(GL_TRIANGLES, offset, count); +} + +// Draw vertex array elements +void rlDrawVertexArrayElements(int offset, int count, void *buffer) +{ + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (unsigned short *)buffer + offset); +} + +// Draw vertex array instanced +void rlDrawVertexArrayInstanced(int offset, int count, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawArraysInstanced(GL_TRIANGLES, 0, count, instances); +#endif +} + +// Draw vertex array elements instanced +void rlDrawVertexArrayElementsInstanced(int offset, int count, void *buffer, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (unsigned short *)buffer + offset, instances); +#endif +} + +#if defined(GRAPHICS_API_OPENGL_11) +// Enable vertex state pointer +void rlEnableStatePointer(int vertexAttribType, void *buffer) +{ + if (buffer != NULL) glEnableClientState(vertexAttribType); + switch (vertexAttribType) + { + case GL_VERTEX_ARRAY: glVertexPointer(3, GL_FLOAT, 0, buffer); break; + case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(2, GL_FLOAT, 0, buffer); break; + case GL_NORMAL_ARRAY: if (buffer != NULL) glNormalPointer(GL_FLOAT, 0, buffer); break; + case GL_COLOR_ARRAY: if (buffer != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, buffer); break; + //case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors + default: break; + } +} + +// Disable vertex state pointer +void rlDisableStatePointer(int vertexAttribType) +{ + glDisableClientState(vertexAttribType); +} +#endif + +// Load vertex array object (VAO) +unsigned int rlLoadVertexArray(void) +{ + unsigned int vaoId = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glGenVertexArrays(1, &vaoId); + } +#endif + return vaoId; +} + +// Set vertex attribute +void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, void *pointer) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glVertexAttribPointer(index, compSize, type, normalized, stride, pointer); +#endif +} + +// Set vertex attribute divisor +void rlSetVertexAttributeDivisor(unsigned int index, int divisor) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glVertexAttribDivisor(index, divisor); +#endif +} + +// Unload vertex array object (VAO) +void rlUnloadVertexArray(unsigned int vaoId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(0); + glDeleteVertexArrays(1, &vaoId); + TRACELOG(RL_LOG_INFO, "VAO: [ID %i] Unloaded vertex array data from VRAM (GPU)", vaoId); + } +#endif +} + +// Unload vertex buffer (VBO) +void rlUnloadVertexBuffer(unsigned int vboId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteBuffers(1, &vboId); + //TRACELOG(RL_LOG_INFO, "VBO: Unloaded vertex data from VRAM (GPU)"); +#endif +} + +// Shaders management +//----------------------------------------------------------------------------------------------- +// Load shader from code strings +// NOTE: If shader string is NULL, using default vertex/fragment shaders +unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int vertexShaderId = RLGL.State.defaultVShaderId; + unsigned int fragmentShaderId = RLGL.State.defaultFShaderId; + + if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); + if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); + + if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; + else + { + id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); + + if (vertexShaderId != RLGL.State.defaultVShaderId) + { + // Detach shader before deletion to make sure memory is freed + glDetachShader(id, vertexShaderId); + glDeleteShader(vertexShaderId); + } + if (fragmentShaderId != RLGL.State.defaultFShaderId) + { + // Detach shader before deletion to make sure memory is freed + glDetachShader(id, fragmentShaderId); + glDeleteShader(fragmentShaderId); + } + + if (id == 0) + { + TRACELOG(RL_LOG_WARNING, "SHADER: Failed to load custom shader code"); + id = RLGL.State.defaultShaderId; + } + } + + // Get available shader uniforms + // NOTE: This information is useful for debug... + int uniformCount = -1; + + glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); + + for (int i = 0; i < uniformCount; i++) + { + int namelen = -1; + int num = -1; + char name[256] = { 0 }; // Assume no variable names longer than 256 + GLenum type = GL_ZERO; + + // Get the name of the uniforms + glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); + + name[namelen] = 0; + + TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); + } +#endif + + return id; +} + +// Compile custom shader and return shader id +unsigned int rlCompileShader(const char *shaderCode, int type) +{ + unsigned int shader = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + shader = glCreateShader(type); + glShaderSource(shader, 1, &shaderCode, NULL); + + GLint success = 0; + glCompileShader(shader); + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (success == GL_FALSE) + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile vertex shader code", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile fragment shader code", shader); break; + //case GL_GEOMETRY_SHADER: + #if defined(GRAPHICS_API_OPENGL_43) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; + #endif + default: break; + } + + int maxLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = RL_CALLOC(maxLength, sizeof(char)); + glGetShaderInfoLog(shader, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); + RL_FREE(log); + } + } + else + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Vertex shader compiled successfully", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Fragment shader compiled successfully", shader); break; + //case GL_GEOMETRY_SHADER: + #if defined(GRAPHICS_API_OPENGL_43) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; + #endif + default: break; + } + } +#endif + + return shader; +} + +// Load custom shader strings and return program id +unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + GLint success = 0; + program = glCreateProgram(); + + glAttachShader(program, vShaderId); + glAttachShader(program, fShaderId); + + // NOTE: Default attribute shader locations must be binded before linking + glBindAttribLocation(program, 0, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + glBindAttribLocation(program, 1, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + glBindAttribLocation(program, 2, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + glBindAttribLocation(program, 3, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + glBindAttribLocation(program, 4, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + glBindAttribLocation(program, 5, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + + // NOTE: If some attrib name is no found on the shader, it locations becomes -1 + + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link shader program", program); + + int maxLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(program, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); + RL_FREE(log); + } + + glDeleteProgram(program); + + program = 0; + } + else + { + // Get the size of compiled shader program (not available on OpenGL ES 2.0) + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero. + //GLint binarySize = 0; + //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Program shader loaded successfully", program); + } +#endif + return program; +} + +// Unload shader program +void rlUnloadShaderProgram(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteProgram(id); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", id); +#endif +} + +// Get shader location uniform +int rlGetLocationUniform(unsigned int shaderId, const char *uniformName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetUniformLocation(shaderId, uniformName); + + if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shaderId, uniformName); + else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shaderId, uniformName, location); +#endif + return location; +} + +// Get shader location attribute +int rlGetLocationAttrib(unsigned int shaderId, const char *attribName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetAttribLocation(shaderId, attribName); + + if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader attribute: %s", shaderId, attribName); + else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader attribute (%s) set at location: %i", shaderId, attribName, location); +#endif + return location; +} + +// Set shader value uniform +void rlSetUniform(int locIndex, const void *value, int uniformType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (uniformType) + { + case RL_SHADER_UNIFORM_FLOAT: glUniform1fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC2: glUniform2fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC3: glUniform3fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC4: glUniform4fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_INT: glUniform1iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; + default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); + } +#endif +} + +// Set shader value attribute +void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (attribType) + { + case RL_SHADER_ATTRIB_FLOAT: if (count == 1) glVertexAttrib1fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC2: if (count == 2) glVertexAttrib2fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC3: if (count == 3) glVertexAttrib3fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC4: if (count == 4) glVertexAttrib4fv(locIndex, (float *)value); break; + default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set attrib default value, data type not recognized"); + } +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrix(int locIndex, Matrix mat) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + float matfloat[16] = { + mat.m0, mat.m1, mat.m2, mat.m3, + mat.m4, mat.m5, mat.m6, mat.m7, + mat.m8, mat.m9, mat.m10, mat.m11, + mat.m12, mat.m13, mat.m14, mat.m15 + }; + glUniformMatrix4fv(locIndex, 1, false, matfloat); +#endif +} + +// Set shader value uniform sampler +void rlSetUniformSampler(int locIndex, unsigned int textureId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Check if texture is already active + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) if (RLGL.State.activeTextureId[i] == textureId) return; + + // Register a new active texture for the internal batch system + // NOTE: Default texture is always activated as GL_TEXTURE0 + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] == 0) + { + glUniform1i(locIndex, 1 + i); // Activate new texture unit + RLGL.State.activeTextureId[i] = textureId; // Save texture id for binding on drawing + break; + } + } +#endif +} + +// Set shader currently active (id and locations) +void rlSetShader(unsigned int id, int *locs) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentShaderId != id) + { + rlDrawRenderBatch(RLGL.currentBatch); + RLGL.State.currentShaderId = id; + RLGL.State.currentShaderLocs = locs; + } +#endif +} + +// Load compute shader program +unsigned int rlLoadComputeShaderProgram(unsigned int shaderId) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_43) + GLint success = 0; + program = glCreateProgram(); + glAttachShader(program, shaderId); + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link compute shader program", program); + + int maxLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(program, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); + RL_FREE(log); + } + + glDeleteProgram(program); + + program = 0; + } + else + { + // Get the size of compiled shader program (not available on OpenGL ES 2.0) + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero. + //GLint binarySize = 0; + //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader program loaded successfully", program); + } +#endif + + return program; +} + +// Dispatch compute shader (equivalent to *draw* for graphics pilepine) +void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glDispatchCompute(groupX, groupY, groupZ); +#endif +} + +// Load shader storage buffer object (SSBO) +unsigned int rlLoadShaderBuffer(unsigned long long size, const void *data, int usageHint) +{ + unsigned int ssbo = 0; + +#if defined(GRAPHICS_API_OPENGL_43) + glGenBuffers(1, &ssbo); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); + glBufferData(GL_SHADER_STORAGE_BUFFER, size, data, usageHint? usageHint : RL_STREAM_COPY); + glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, 0); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#endif + + return ssbo; +} + +// Unload shader storage buffer object (SSBO) +void rlUnloadShaderBuffer(unsigned int ssboId) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glDeleteBuffers(1, &ssboId); +#endif +} + +// Update SSBO buffer data +void rlUpdateShaderBufferElements(unsigned int id, const void *data, unsigned long long dataSize, unsigned long long offset) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, dataSize, data); +#endif +} + +// Get SSBO buffer size +unsigned long long rlGetShaderBufferSize(unsigned int id) +{ + long long size = 0; + +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glGetInteger64v(GL_SHADER_STORAGE_BUFFER_SIZE, &size); +#endif + + return (size > 0)? size : 0; +} + +// Read SSBO buffer data +void rlReadShaderBufferElements(unsigned int id, void *dest, unsigned long long count, unsigned long long offset) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, count, dest); +#endif +} + +// Bind SSBO buffer +void rlBindShaderBuffer(unsigned int id, unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index, id); +#endif +} + +// Copy SSBO buffer data +void rlCopyBuffersElements(unsigned int destId, unsigned int srcId, unsigned long long destOffset, unsigned long long srcOffset, unsigned long long count) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_COPY_READ_BUFFER, srcId); + glBindBuffer(GL_COPY_WRITE_BUFFER, destId); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, srcOffset, destOffset, count); +#endif +} + +// Bind image texture +void rlBindImageTexture(unsigned int id, unsigned int index, unsigned int format, int readonly) +{ +#if defined(GRAPHICS_API_OPENGL_43) + int glInternalFormat = 0, glFormat = 0, glType = 0; + + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + glBindImageTexture(index, id, 0, 0, 0, readonly ? GL_READ_ONLY : GL_READ_WRITE, glInternalFormat); +#endif +} + +// Matrix state management +//----------------------------------------------------------------------------------------- +// Get internal modelview matrix +Matrix rlGetMatrixModelview(void) +{ + Matrix matrix = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mat); + matrix.m0 = mat[0]; + matrix.m1 = mat[1]; + matrix.m2 = mat[2]; + matrix.m3 = mat[3]; + matrix.m4 = mat[4]; + matrix.m5 = mat[5]; + matrix.m6 = mat[6]; + matrix.m7 = mat[7]; + matrix.m8 = mat[8]; + matrix.m9 = mat[9]; + matrix.m10 = mat[10]; + matrix.m11 = mat[11]; + matrix.m12 = mat[12]; + matrix.m13 = mat[13]; + matrix.m14 = mat[14]; + matrix.m15 = mat[15]; +#else + matrix = RLGL.State.modelview; +#endif + return matrix; +} + +// Get internal projection matrix +Matrix rlGetMatrixProjection(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_PROJECTION_MATRIX,mat); + Matrix m; + m.m0 = mat[0]; + m.m1 = mat[1]; + m.m2 = mat[2]; + m.m3 = mat[3]; + m.m4 = mat[4]; + m.m5 = mat[5]; + m.m6 = mat[6]; + m.m7 = mat[7]; + m.m8 = mat[8]; + m.m9 = mat[9]; + m.m10 = mat[10]; + m.m11 = mat[11]; + m.m12 = mat[12]; + m.m13 = mat[13]; + m.m14 = mat[14]; + m.m15 = mat[15]; + return m; +#else + return RLGL.State.projection; +#endif +} + +// Get internal accumulated transform matrix +Matrix rlGetMatrixTransform(void) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // TODO: Consider possible transform matrices in the RLGL.State.stack + // Is this the right order? or should we start with the first stored matrix instead of the last one? + //Matrix matStackTransform = rlMatrixIdentity(); + //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = rlMatrixMultiply(RLGL.State.stack[i], matStackTransform); + mat = RLGL.State.transform; +#endif + return mat; +} + +// Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixProjectionStereo(int eye) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.projectionStereo[eye]; +#endif + return mat; +} + +// Get internal view offset matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.viewOffsetStereo[eye]; +#endif + return mat; +} + +// Set a custom modelview matrix (replaces internal modelview matrix) +void rlSetMatrixModelview(Matrix view) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.modelview = view; +#endif +} + +// Set a custom projection matrix (replaces internal projection matrix) +void rlSetMatrixProjection(Matrix projection) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projection = projection; +#endif +} + +// Set eyes projection matrices for stereo rendering +void rlSetMatrixProjectionStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projectionStereo[0] = right; + RLGL.State.projectionStereo[1] = left; +#endif +} + +// Set eyes view offsets matrices for stereo rendering +void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.viewOffsetStereo[0] = right; + RLGL.State.viewOffsetStereo[1] = left; +#endif +} + +// Load and draw a quad in NDC +void rlLoadDrawQuad(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int quadVAO = 0; + unsigned int quadVBO = 0; + + float vertices[] = { + // Positions Texcoords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &quadVBO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, texcoords) + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords + + // Draw quad + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); + + // Delete buffers (VBO and VAO) + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); +#endif +} + +// Load and draw a cube in NDC +void rlLoadDrawCube(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int cubeVAO = 0; + unsigned int cubeVBO = 0; + + float vertices[] = { + // Positions Normals Texcoords + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &cubeVAO); + glBindVertexArray(cubeVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &cubeVBO); + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, normals, texcoords) + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + // Draw cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + + // Delete VBO and VAO + glDeleteBuffers(1, &cubeVBO); + glDeleteVertexArrays(1, &cubeVAO); +#endif +} + +// Get name string for pixel format +const char *rlGetPixelFormatName(unsigned int format) +{ + switch (format) + { + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: return "GRAYSCALE"; break; // 8 bit per pixel (no alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: return "GRAY_ALPHA"; break; // 8*2 bpp (2 channels) + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: return "R5G6B5"; break; // 16 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: return "R8G8B8"; break; // 24 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: return "R5G5B5A1"; break; // 16 bpp (1 bit alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: return "R4G4B4A4"; break; // 16 bpp (4 bit alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: return "R8G8B8A8"; break; // 32 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R32: return "R32"; break; // 32 bpp (1 channel - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: return "R32G32B32"; break; // 32*3 bpp (3 channels - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: return "R32G32B32A32"; break; // 32*4 bpp (4 channels - float) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: return "DXT1_RGB"; break; // 4 bpp (no alpha) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: return "DXT1_RGBA"; break; // 4 bpp (1 bit alpha) + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: return "DXT3_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: return "DXT5_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: return "ETC1_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: return "ETC2_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: return "ETC2_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: return "PVRT_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: return "PVRT_RGBA"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: return "ASTC_4x4_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: return "ASTC_8x8_RGBA"; break; // 2 bpp + default: return "UNKNOWN"; break; + } +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Load default shader (just vertex positioning and texture coloring) +// NOTE: This shader program is used for internal buffers +// NOTE: Loaded: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs +static void rlLoadShaderDefault(void) +{ + RLGL.State.defaultShaderLocs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) RLGL.State.defaultShaderLocs[i] = -1; + + // Vertex shader directly defined, no external file required + const char *defaultVShaderCode = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#endif + "uniform mat4 mvp; \n" + "void main() \n" + "{ \n" + " fragTexCoord = vertexTexCoord; \n" + " fragColor = vertexColor; \n" + " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" + "} \n"; + + // Fragment shader directly defined, no external file required + const char *defaultFShaderCode = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif + + // NOTE: Compiled vertex/fragment shaders are kept for re-use + RLGL.State.defaultVShaderId = rlCompileShader(defaultVShaderCode, GL_VERTEX_SHADER); // Compile default vertex shader + RLGL.State.defaultFShaderId = rlCompileShader(defaultFShaderCode, GL_FRAGMENT_SHADER); // Compile default fragment shader + + RLGL.State.defaultShaderId = rlLoadShaderProgram(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); + + if (RLGL.State.defaultShaderId > 0) + { + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShaderId); + + // Set default shader locations: attributes locations + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShaderId, "vertexPosition"); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShaderId, "vertexTexCoord"); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShaderId, "vertexColor"); + + // Set default shader locations: uniform locations + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShaderId, "mvp"); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, "colDiffuse"); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, "texture0"); + } + else TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShaderId); +} + +// Unload default shader +// NOTE: Unloads: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs +static void rlUnloadShaderDefault(void) +{ + glUseProgram(0); + + glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultVShaderId); + glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultFShaderId); + glDeleteShader(RLGL.State.defaultVShaderId); + glDeleteShader(RLGL.State.defaultFShaderId); + + glDeleteProgram(RLGL.State.defaultShaderId); + + RL_FREE(RLGL.State.defaultShaderLocs); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader unloaded successfully", RLGL.State.defaultShaderId); +} + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) +// Get compressed format official GL identifier name +static char *rlGetCompressedFormatName(int format) +{ + switch (format) + { + // GL_EXT_texture_compression_s3tc + case 0x83F0: return "GL_COMPRESSED_RGB_S3TC_DXT1_EXT"; break; + case 0x83F1: return "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT"; break; + case 0x83F2: return "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT"; break; + case 0x83F3: return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT"; break; + // GL_3DFX_texture_compression_FXT1 + case 0x86B0: return "GL_COMPRESSED_RGB_FXT1_3DFX"; break; + case 0x86B1: return "GL_COMPRESSED_RGBA_FXT1_3DFX"; break; + // GL_IMG_texture_compression_pvrtc + case 0x8C00: return "GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG"; break; + case 0x8C01: return "GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG"; break; + case 0x8C02: return "GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"; break; + case 0x8C03: return "GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"; break; + // GL_OES_compressed_ETC1_RGB8_texture + case 0x8D64: return "GL_ETC1_RGB8_OES"; break; + // GL_ARB_texture_compression_rgtc + case 0x8DBB: return "GL_COMPRESSED_RED_RGTC1"; break; + case 0x8DBC: return "GL_COMPRESSED_SIGNED_RED_RGTC1"; break; + case 0x8DBD: return "GL_COMPRESSED_RG_RGTC2"; break; + case 0x8DBE: return "GL_COMPRESSED_SIGNED_RG_RGTC2"; break; + // GL_ARB_texture_compression_bptc + case 0x8E8C: return "GL_COMPRESSED_RGBA_BPTC_UNORM_ARB"; break; + case 0x8E8D: return "GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB"; break; + case 0x8E8E: return "GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB"; break; + case 0x8E8F: return "GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB"; break; + // GL_ARB_ES3_compatibility + case 0x9274: return "GL_COMPRESSED_RGB8_ETC2"; break; + case 0x9275: return "GL_COMPRESSED_SRGB8_ETC2"; break; + case 0x9276: return "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; + case 0x9277: return "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; + case 0x9278: return "GL_COMPRESSED_RGBA8_ETC2_EAC"; break; + case 0x9279: return "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"; break; + case 0x9270: return "GL_COMPRESSED_R11_EAC"; break; + case 0x9271: return "GL_COMPRESSED_SIGNED_R11_EAC"; break; + case 0x9272: return "GL_COMPRESSED_RG11_EAC"; break; + case 0x9273: return "GL_COMPRESSED_SIGNED_RG11_EAC"; break; + // GL_KHR_texture_compression_astc_hdr + case 0x93B0: return "GL_COMPRESSED_RGBA_ASTC_4x4_KHR"; break; + case 0x93B1: return "GL_COMPRESSED_RGBA_ASTC_5x4_KHR"; break; + case 0x93B2: return "GL_COMPRESSED_RGBA_ASTC_5x5_KHR"; break; + case 0x93B3: return "GL_COMPRESSED_RGBA_ASTC_6x5_KHR"; break; + case 0x93B4: return "GL_COMPRESSED_RGBA_ASTC_6x6_KHR"; break; + case 0x93B5: return "GL_COMPRESSED_RGBA_ASTC_8x5_KHR"; break; + case 0x93B6: return "GL_COMPRESSED_RGBA_ASTC_8x6_KHR"; break; + case 0x93B7: return "GL_COMPRESSED_RGBA_ASTC_8x8_KHR"; break; + case 0x93B8: return "GL_COMPRESSED_RGBA_ASTC_10x5_KHR"; break; + case 0x93B9: return "GL_COMPRESSED_RGBA_ASTC_10x6_KHR"; break; + case 0x93BA: return "GL_COMPRESSED_RGBA_ASTC_10x8_KHR"; break; + case 0x93BB: return "GL_COMPRESSED_RGBA_ASTC_10x10_KHR"; break; + case 0x93BC: return "GL_COMPRESSED_RGBA_ASTC_12x10_KHR"; break; + case 0x93BD: return "GL_COMPRESSED_RGBA_ASTC_12x12_KHR"; break; + case 0x93D0: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"; break; + case 0x93D1: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"; break; + case 0x93D2: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"; break; + case 0x93D3: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"; break; + case 0x93D4: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"; break; + case 0x93D5: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"; break; + case 0x93D6: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"; break; + case 0x93D7: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"; break; + case 0x93D8: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"; break; + case 0x93D9: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"; break; + case 0x93DA: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"; break; + case 0x93DB: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"; break; + case 0x93DC: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"; break; + case 0x93DD: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"; break; + default: return "GL_COMPRESSED_UNKNOWN"; break; + } +} +#endif // RLGL_SHOW_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +#if defined(GRAPHICS_API_OPENGL_11) +// Mipmaps data is generated after image data +// NOTE: Only works with RGBA (4 bytes) data! +static int rlGenTextureMipmapsData(unsigned char *data, int baseWidth, int baseHeight) +{ + int mipmapCount = 1; // Required mipmap levels count (including base level) + int width = baseWidth; + int height = baseHeight; + int size = baseWidth*baseHeight*4; // Size in bytes (will include mipmaps...), RGBA only + + // Count mipmap levels required + while ((width != 1) && (height != 1)) + { + width /= 2; + height /= 2; + + TRACELOGD("TEXTURE: Next mipmap size: %i x %i", width, height); + + mipmapCount++; + + size += (width*height*4); // Add mipmap size (in bytes) + } + + TRACELOGD("TEXTURE: Total mipmaps required: %i", mipmapCount); + TRACELOGD("TEXTURE: Total size of data required: %i", size); + + unsigned char *temp = RL_REALLOC(data, size); + + if (temp != NULL) data = temp; + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to re-allocate required mipmaps memory"); + + width = baseWidth; + height = baseHeight; + size = (width*height*4); // RGBA: 4 bytes + + // Generate mipmaps + // NOTE: Every mipmap data is stored after data (RGBA - 4 bytes) + unsigned char *image = (unsigned char *)RL_MALLOC(width*height*4); + unsigned char *mipmap = NULL; + int offset = 0; + + for (int i = 0; i < size; i += 4) + { + image[i] = data[i]; + image[i + 1] = data[i + 1]; + image[i + 2] = data[i + 2]; + image[i + 3] = data[i + 3]; + } + + TRACELOGD("TEXTURE: Mipmap base size (%ix%i)", width, height); + + for (int mip = 1; mip < mipmapCount; mip++) + { + mipmap = rlGenNextMipmapData(image, width, height); + + offset += (width*height*4); // Size of last mipmap + + width /= 2; + height /= 2; + size = (width*height*4); // Mipmap size to store after offset + + // Add mipmap to data + for (int i = 0; i < size; i += 4) + { + data[offset + i] = mipmap[i]; + data[offset + i + 1] = mipmap[i + 1]; + data[offset + i + 2] = mipmap[i + 2]; + data[offset + i + 3] = mipmap[i + 3]; + } + + RL_FREE(image); + + image = mipmap; + mipmap = NULL; + } + + RL_FREE(mipmap); // free mipmap data + + return mipmapCount; +} + +// Manual mipmap generation (basic scaling algorithm) +static unsigned char *rlGenNextMipmapData(unsigned char *srcData, int srcWidth, int srcHeight) +{ + int x2 = 0; + int y2 = 0; + unsigned char prow[4] = { 0 }; + unsigned char pcol[4] = { 0 }; + + int width = srcWidth/2; + int height = srcHeight/2; + + unsigned char *mipmap = (unsigned char *)RL_MALLOC(width*height*4); + + // Scaling algorithm works perfectly (box-filter) + for (int y = 0; y < height; y++) + { + y2 = 2*y; + + for (int x = 0; x < width; x++) + { + x2 = 2*x; + + prow[0] = (srcData[(y2*srcWidth + x2)*4 + 0] + srcData[(y2*srcWidth + x2 + 1)*4 + 0])/2; + prow[1] = (srcData[(y2*srcWidth + x2)*4 + 1] + srcData[(y2*srcWidth + x2 + 1)*4 + 1])/2; + prow[2] = (srcData[(y2*srcWidth + x2)*4 + 2] + srcData[(y2*srcWidth + x2 + 1)*4 + 2])/2; + prow[3] = (srcData[(y2*srcWidth + x2)*4 + 3] + srcData[(y2*srcWidth + x2 + 1)*4 + 3])/2; + + pcol[0] = (srcData[((y2 + 1)*srcWidth + x2)*4 + 0] + srcData[((y2 + 1)*srcWidth + x2 + 1)*4 + 0])/2; + pcol[1] = (srcData[((y2 + 1)*srcWidth + x2)*4 + 1] + srcData[((y2 + 1)*srcWidth + x2 + 1)*4 + 1])/2; + pcol[2] = (srcData[((y2 + 1)*srcWidth + x2)*4 + 2] + srcData[((y2 + 1)*srcWidth + x2 + 1)*4 + 2])/2; + pcol[3] = (srcData[((y2 + 1)*srcWidth + x2)*4 + 3] + srcData[((y2 + 1)*srcWidth + x2 + 1)*4 + 3])/2; + + mipmap[(y*width + x)*4 + 0] = (prow[0] + pcol[0])/2; + mipmap[(y*width + x)*4 + 1] = (prow[1] + pcol[1])/2; + mipmap[(y*width + x)*4 + 2] = (prow[2] + pcol[2])/2; + mipmap[(y*width + x)*4 + 3] = (prow[3] + pcol[3])/2; + } + } + + TRACELOGD("TEXTURE: Mipmap generated successfully (%ix%i)", width, height); + + return mipmap; +} +#endif // GRAPHICS_API_OPENGL_11 + +// Get pixel data size in bytes (image or texture) +// NOTE: Size depends on pixel format +static int rlGetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + dataSize = width*height*bpp/8; // Total data size in bytes + + // Most compressed formats works on 4x4 blocks, + // if texture is smaller, minimum dataSize is 8 or 16 + if ((width < 4) && (height < 4)) + { + if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + } + + return dataSize; +} + +// Auxiliar math functions + +// Get identity matrix +static Matrix rlMatrixIdentity(void) +{ + Matrix result = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + return result; +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +static Matrix rlMatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +#endif // RLGL_IMPLEMENTATION diff --git a/include/rmath.h b/include/rmath.h new file mode 100644 index 0000000..b43db8b --- /dev/null +++ b/include/rmath.h @@ -0,0 +1,50 @@ +#pragma once + +int imin( int a, int b ); +int imax( int a, int b ); + +/* Vector2. */ +int lmathVector2Add( lua_State *L ); +int lmathVector2Subtract( lua_State *L ); +int lmathVector2Multiply( lua_State *L ); +int lmathVector2Length( lua_State *L ); +int lmathVector2DotProduct( lua_State *L ); +int lmathVector2Distance( lua_State *L ); +int lmathVector2Angle( lua_State *L ); +int lmathVector2Normalize( lua_State *L ); +int lmathVector2Lerp( lua_State *L ); +int lmathVector2Reflect( lua_State *L ); +int lmathVector2Rotate( lua_State *L ); +int lmathVector2MoveTowards( lua_State *L ); +/* Vector3. */ +int lmathVector3Add( lua_State *L ); +int lmathVector3Subtract( lua_State *L ); +int lmathVector3Multiply( lua_State *L ); +int lmathVector3CrossProduct( lua_State *L ); +int lmathVector3Perpendicular( lua_State *L ); +int lmathVector3Length( lua_State *L ); +int lmathVector3LengthSqr( lua_State *L ); +int lmathVector3DotProduct( lua_State *L ); +int lmathVector3Distance( lua_State *L ); +int lmathVector3Normalize( lua_State *L ); +int lmathVector3OrthoNormalize( lua_State *L ); +int lmathVector3Transform( lua_State *L ); +int lmathVector3RotateByQuaternion( lua_State *L ); +int lmathVector3Lerp( lua_State *L ); +int lmathVector3Reflect( lua_State *L ); +/* Matrix. */ +int lmathMatrixDeterminant( lua_State *L ); +int lmathMatrixTranspose( lua_State *L ); +int lmathMatrixInvert( lua_State *L ); +int lmathMatrixNormalize( lua_State *L ); +int lmathMatrixIdentity( lua_State *L ); +int lmathMatrixAdd( lua_State *L ); +int lmathMatrixSubtract( lua_State *L ); +int lmathMatrixMultiply( lua_State *L ); +int lmathMatrixTranslate( lua_State *L ); +int lmathMatrixRotate( lua_State *L ); +int lmathMatrixScale( lua_State *L ); +int lmathMatrixFrustum( lua_State *L ); +int lmathMatrixPerspective( lua_State *L ); +int lmathMatrixOrtho( lua_State *L ); +int lmathMatrixLookAt( lua_State *L ); diff --git a/include/shapes.h b/include/shapes.h new file mode 100644 index 0000000..423a43f --- /dev/null +++ b/include/shapes.h @@ -0,0 +1,21 @@ +#pragma once + +/* Drawing. */ +int lshapesDrawPixel( lua_State *L ); +int lshapesDrawLine( lua_State *L ); +int lshapesDrawCircle( lua_State *L ); +int lshapesDrawCircleLines( lua_State *L ); +int lshapesDrawRectangle( lua_State *L ); +int lshapesDrawRectanglePro( lua_State *L ); +int lshapesDrawTriangle( lua_State *L ); +int lshapesDrawTriangleLines( lua_State *L ); +/* Collision. */ +int lshapesCheckCollisionRecs( lua_State *L ); +int lshapesCheckCollisionCircles( lua_State *L ); +int lshapesCheckCollisionCircleRec( lua_State *L ); +int lshapesCheckCollisionPointRec( lua_State *L ); +int lshapesCheckCollisionPointCircle( lua_State *L ); +int lshapesCheckCollisionPointTriangle( lua_State *L ); +int lshapesCheckCollisionLines( lua_State *L ); +int lshapesCheckCollisionPointLine( lua_State *L ); +int lshapesGetCollisionRec( lua_State *L ); diff --git a/include/state.h b/include/state.h new file mode 100644 index 0000000..3fc4e8e --- /dev/null +++ b/include/state.h @@ -0,0 +1,71 @@ +#pragma once + +#define ALLOC_PAGE_SIZE 256 + +typedef struct { + ModelAnimation *animations; + unsigned int animCount; +} ModelAnimations; + +typedef struct { + char *exePath; + bool hasWindow; + bool run; + lua_State *luaState; + Vector2 resolution; + int targetFPS; + int textureSource; + /* Resources. */ + /* Images. */ + Image **images; + size_t imageCount; + size_t imageAlloc; + /* Textures. */ + Texture **textures; + size_t textureCount; + size_t textureAlloc; + /* RenderTextures. */ + RenderTexture **renderTextures; + size_t renderTextureCount; + size_t renderTextureAlloc; + /* Fonts. */ + Font **fonts; + size_t fontCount; + size_t fontAlloc; + /* Sounds. */ + Sound **sounds; + size_t soundCount; + size_t soundAlloc; + /* Music. */ + Music music; + /* Camera3D's. */ + Camera3D **camera3Ds; + size_t camera3DCount; + size_t camera3DAlloc; + /* Meshes. */ + Mesh **meshes; + size_t meshCount; + size_t meshAlloc; + /* Materials. */ + Material **materials; + size_t materialCount; + size_t materialAlloc; + /* Models. */ + Model **models; + size_t modelCount; + size_t modelAlloc; + /* ModelAnimations. */ + ModelAnimations **animations; + size_t animationCount; + size_t animationAlloc; + /* Shaders. */ + Shader **shaders; + size_t shaderCount; + size_t shaderAlloc; +} State; + +extern State *state; + +bool stateInit( const char *exePath ); +// bool stateRun(); +void stateFree(); diff --git a/include/text.h b/include/text.h new file mode 100644 index 0000000..6e53c70 --- /dev/null +++ b/include/text.h @@ -0,0 +1,8 @@ +#pragma once + +/* Validators. */ +bool validFont( size_t id ); +/* Loading. */ +int lmodelsLoadFont( lua_State *L ); +/* Drawing. */ +int ltextDrawText( lua_State *L ); diff --git a/include/textures.h b/include/textures.h new file mode 100644 index 0000000..1a56614 --- /dev/null +++ b/include/textures.h @@ -0,0 +1,44 @@ +#pragma once + +enum TEXTURE_SOURCES { TEXTURE_SOURCE_TEXTURE, TEXTURE_SOURCE_RENDER_TEXTURE }; + +/* Validators. */ +bool validImage( size_t id ); +bool validTexture( size_t id ); +bool validRenderTexture( size_t id ); +bool validSourceTexture( size_t id ); +Texture2D* texturesGetSourceTexture( size_t index ); +/* File. */ +int ltexturesLoadImage( lua_State *L ); +int ltexturesGenImageColor( lua_State *L ); +int ltexturesUnloadImage( lua_State *L ); +int ltexturesLoadTexture( lua_State *L ); +int ltexturesLoadTextureFromImage( lua_State *L ); +int ltexturesUnloadTexture( lua_State *L ); +int ltexturesLoadRenderTexture( lua_State *L ); +int ltexturesUnloadRenderTexture( lua_State *L ); +/* Image Drawing. */ +int ltexturesImageClearBackground( lua_State *L ); +int ltexturesImageDrawPixel( lua_State *L ); +int ltexturesImageDrawLine( lua_State *L ); +int ltexturesImageDrawCircle( lua_State *L ); +int ltexturesImageDrawRectangle( lua_State *L ); +int ltexturesImageDrawRectangleLines( lua_State *L ); +int ltexturesImageDraw( lua_State *L ); +int ltexturesImageDrawTextEx( lua_State *L ); +/* Texture Drawing. */ +int ltexturesDrawTexture( lua_State *L ); +int ltexturesDrawTextureRec( lua_State *L ); +int ltexturesDrawTextureTiled( lua_State *L ); +int ltexturesDrawTexturePro( lua_State *L ); +int ltexturesDrawTextureNPatch( lua_State *L ); +int ltexturesDrawTexturePoly( lua_State *L ); +int ltexturesBeginTextureMode( lua_State *L ); +int ltexturesEndTextureMode( lua_State *L ); +int ltexturesSetTextureSource( lua_State *L ); +int ltexturesGetTextureSource( lua_State *L ); +/* Conf. */ +int ltexturesGenTextureMipmaps( lua_State *L ); +int ltexturesSetTextureFilter( lua_State *L ); +int ltexturesSetTextureWrap( lua_State *L ); +int ltexturesGetTextureSize( lua_State *L ); diff --git a/src/audio.c b/src/audio.c new file mode 100644 index 0000000..45ee6c9 --- /dev/null +++ b/src/audio.c @@ -0,0 +1,272 @@ +#include "main.h" +#include "state.h" +#include "audio.h" +#include "lua_core.h" + +static bool validSound( size_t id ) { + if ( id < 0 || state->soundCount < id || state->sounds[ id ] == NULL ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid sound", id ); + return false; + } + else { + return true; + } +} + +static void checkSoundRealloc( int i ) { + if ( i == state->soundCount ) { + state->soundCount++; + } + + if ( state->soundCount == state->soundAlloc ) { + state->soundAlloc += ALLOC_PAGE_SIZE; + state->sounds = realloc( state->sounds, state->soundAlloc * sizeof( Sound* ) ); + + for ( i = state->soundCount; i < state->soundAlloc; i++ ) { + state->sounds[i] = NULL; + } + } +} + +/* +## Audio - Sounds +*/ + +/* +> sound = RL_LoadSound( string fileName ) + +Load sound from file + +- Failure return -1 +- Success return int +*/ +int laudioLoadSound( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadSound( string fileName )" ); + lua_pushinteger( L, -1 ); + return 1; + } + + if ( FileExists( lua_tostring( L, -1 ) ) ) { + int i = 0; + + for ( i = 0; i < state->soundCount; i++ ) { + if ( state->sounds[i] == NULL ) { + break; + } + } + state->sounds[i] = malloc( sizeof( Sound ) ); + *state->sounds[i] = LoadSound( lua_tostring( L, -1 ) ); + lua_pushinteger( L, i ); + checkSoundRealloc( i ); + } + + return 1; +} + +/* +> success = RL_PlaySoundMulti( Sound sound ) + +Play a sound ( Using multichannel buffer pool ) + +- Failure return false +- Success return true +*/ +int laudioPlaySoundMulti( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_PlaySoundMulti( Sound sound )" ); + lua_pushboolean( L, false ); + return 1; + } + if ( !validSound( lua_tointeger( L, -1 ) ) ) { + lua_pushboolean( L, false ); + return 1; + } + PlaySoundMulti( *state->sounds[ lua_tointeger( L, -1 ) ] ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetSoundVolume( Sound sound, float volume ) + +Set volume for a sound ( 1.0 is max level ) + +- Failure return false +- Success return true +*/ +int laudioSetSoundVolume( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetSoundVolume( Sound sound, float volume )" ); + lua_pushboolean( L, false ); + return 1; + } + if ( !validSound( lua_tointeger( L, -2 ) ) ) { + lua_pushboolean( L, false ); + return 1; + } + SetSoundVolume( *state->sounds[ lua_tointeger( L, -2 ) ], lua_tonumber( L, -1 ) ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetSoundPitch( Sound sound, float pitch ) + +Set pitch for a sound ( 1.0 is base level ) + +- Failure return false +- Success return true +*/ +int laudioSetSoundPitch( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetSoundPitch( Sound sound, float pitch )" ); + lua_pushboolean( L, false ); + return 1; + } + if ( !validSound( lua_tointeger( L, -2 ) ) ) { + lua_pushboolean( L, false ); + return 1; + } + SetSoundPitch( *state->sounds[ lua_tointeger( L, -2 ) ], lua_tonumber( L, -1 ) ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_UnloadSound( Sound sound ) + +Unload sound + +- Failure return false +- Success return true +*/ +int laudioUnloadSound( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_UnloadSound( Sound sound )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t id = lua_tointeger( L, -1 ); + + if ( !validSound( id ) ) { + lua_pushboolean( L, false ); + return 1; + } + UnloadSound( *state->sounds[ id ] ); + state->sounds[ id ] = NULL; + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Audio - Music +*/ + +/* +> success = RL_LoadMusicStream( string fileName ) + +Load music stream from file + +- Failure return false +- Success return true +*/ +int laudioLoadMusicStream( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadMusicStream( string fileName )" ); + lua_pushboolean( L, false ); + return 1; + } + if ( FileExists( lua_tostring( L, -1 ) ) ) { + state->music = LoadMusicStream( lua_tostring( L, -1 ) ); + state->music.looping = false; + + lua_pushboolean( L, true ); + } + else { + lua_pushboolean( L, false ); + } + + return 1; +} + +/* +> PlayMusicStream() + +Start music playing +*/ +int laudioPlayMusicStream( lua_State *L ) { + PlayMusicStream( state->music ); + + return 1; +} + +/* +> StopMusicStream() + +Stop music playing +*/ +int laudioStopMusicStream( lua_State *L ) { + StopMusicStream( state->music ); + + return 1; +} + +/* +> PauseMusicStream() + +Pause music playing +*/ +int laudioPauseMusicStream( lua_State *L ) { + PauseMusicStream( state->music ); + + return 1; +} + +/* +> ResumeMusicStream() + +Resume playing paused music +*/ +int laudioResumeMusicStream( lua_State *L ) { + ResumeMusicStream( state->music ); + + return 1; +} + +/* +> playing = PlayMusicStream() + +Check if music is playing + +- Success return bool +*/ +int laudioIsMusicStreamPlaying( lua_State *L ) { + lua_pushboolean( L, IsMusicStreamPlaying( state->music ) ); + + return 1; +} + +/* +> success = RL_SetMusicVolume( float volume ) + +Set volume for music ( 1.0 is max level ) + +- Failure return false +- Success return true +*/ +int laudioSetMusicVolume( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetMusicVolume( float volume )" ); + lua_pushboolean( L, false ); + return 1; + } + SetMusicVolume( state->music, lua_tonumber( L, -1 ) ); + lua_pushboolean( L, true ); + + return 1; +} diff --git a/src/core.c b/src/core.c new file mode 100644 index 0000000..91084b1 --- /dev/null +++ b/src/core.c @@ -0,0 +1,1908 @@ +#include "main.h" +#include "state.h" +#include "core.h" +#include "textures.h" +#include "lua_core.h" + +static void checkCamera3DRealloc( int i ) { + if ( i == state->camera3DCount ) { + state->camera3DCount++; + } + + if ( state->camera3DCount == state->camera3DAlloc ) { + state->camera3DAlloc += ALLOC_PAGE_SIZE; + state->camera3Ds = realloc( state->camera3Ds, state->camera3DAlloc * sizeof( Camera3D* ) ); + + for ( i = state->camera3DCount; i < state->camera3DAlloc; i++ ) { + state->camera3Ds[i] = NULL; + } + } +} + +static void checkShaderRealloc( int i ) { + if ( i == state->shaderCount ) { + state->shaderCount++; + } + + if ( state->shaderCount == state->shaderAlloc ) { + state->shaderAlloc += ALLOC_PAGE_SIZE; + state->shaders = realloc( state->shaders, state->shaderAlloc * sizeof( Shader* ) ); + + for ( i = state->shaderCount; i < state->shaderAlloc; i++ ) { + state->shaders[i] = NULL; + } + } +} + +bool validCamera3D( size_t id ) { + if ( id < 0 || state->camera3DCount < id || state->camera3Ds[ id ] == NULL ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid camera", id ); + return false; + } + else { + return true; + } +} + +static inline bool validShader( size_t id ) { + if ( id < 0 || state->shaderCount < id || state->shaders[ id ] == NULL ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid shader", id ); + return false; + } + else { + return true; + } +} + +/* +## Core - Window +*/ + +/* +> success = RL_SetWindowMonitor( int monitor ) + +Set monitor for the current window (fullscreen mode) + +- Failure return false +- Success return true +*/ +int lcoreSetWindowMonitor( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetWindowMonitor( int monitor )" ); + lua_pushboolean( L, false ); + return 1; + } + SetWindowMonitor( lua_tointeger( L, -1 ) ); + lua_pushboolean( L, true ); + return 1; +} + +/* +> success = RL_SetWindowPosition( Vector2 pos ) + +Set window position on screen + +- Failure return false +- Success return true +*/ +int lcoreSetWindowPosition( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetWindowPosition( Vector2 pos )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 pos = uluaGetVector2( L ); + + SetWindowPosition( pos.x, pos.y ); + lua_pushboolean( L, true ); + return 1; +} + +/* +> success = RL_SetWindowSize( Vector2 size ) + +Set window dimensions + +- Failure return false +- Success return true +*/ +int lcoreSetWindowSize( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetWindowSize( Vector2 size )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 size = uluaGetVector2( L ); + + SetWindowSize( (int)size.x, (int)size.y ); + lua_pushboolean( L, true ); + return 1; +} + +/* +> position = RL_GetMonitorPosition( int monitor ) + +Get specified monitor position + +- Failure return nil +- Success return Vector2 +*/ +int lcoreGetMonitorPosition( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetMonitorPosition( int monitor )" ); + lua_pushnil( L ); + return 1; + } + Vector2 pos = GetMonitorPosition( lua_tointeger( L, -1 ) ); + uluaPushVector2( L, pos ); + + return 1; +} + +/* +> size = RL_GetMonitorSize( int monitor ) + +Get specified monitor size + +- Failure return nil +- Success return Vector2 +*/ +int lcoreGetMonitorSize( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetMonitorSize( int monitor )" ); + lua_pushnil( L ); + return 1; + } + Vector2 size = (Vector2){ GetMonitorWidth( lua_tointeger( L, -1 ) ), GetMonitorHeight( lua_tointeger( L, -1 ) ) }; + uluaPushVector2( L, size ); + + return 1; +} + +/* +> position = RL_GetWindowPosition() + +Get window position on monitor + +- Success return Vector2 +*/ +int lcoreGetWindowPosition( lua_State *L ) { + Vector2 pos = GetWindowPosition(); + uluaPushVector2( L, pos ); + + return 1; +} + +/* +> size = RL_GetWindowPosition() + +Get window size + +- Success return Vector2 +*/ +int lcoreGetWindowSize( lua_State *L ) { + Vector2 size = (Vector2){ GetScreenWidth(), GetScreenHeight() }; + uluaPushVector2( L, size ); + + return 1; +} + +/* +> success = RL_SetWindowState( int flag ) + +Set window configuration state using flags ( FLAG_FULLSCREEN_MODE, FLAG_WINDOW_RESIZABLE... ) + +- Failure return false +- Success return true +*/ +int lcoreSetWindowState( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetWindowState( int flags )" ); + lua_pushboolean( L, false ); + return 1; + } + SetWindowState( (unsigned int)lua_tointeger( L, -1 ) ); + lua_pushboolean( L, true ); + return 1; +} + +/* +> state = RL_IsWindowState( int flag ) ) + +Check if one specific window flag is enabled ( FLAG_FULLSCREEN_MODE, FLAG_WINDOW_RESIZABLE... ) + +- Failure return nil +- Success return bool +*/ +int lcoreIsWindowState( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsWindowState( int flags )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsWindowState( (unsigned int)lua_tointeger( L, -1 ) ) ); + + return 1; +} + +/* +> resized = RL_ClearWindowState( int flag ) + +Clear window configuration state flags ( FLAG_FULLSCREEN_MODE, FLAG_WINDOW_RESIZABLE... ) + +- Success return bool +*/ +int lcoreClearWindowState( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_ClearWindowState( int flag )" ); + lua_pushnil( L ); + return 1; + } + ClearWindowState( (unsigned int)lua_tointeger( L, -1 ) ); + + return 1; +} + +/* +> resized = RL_IsWindowResized() + +Check if window has been resized from last frame + +- Success return bool +*/ +int lcoreIsWindowResized( lua_State *L ) { + lua_pushboolean( L, IsWindowResized() ); + + return 1; +} + +/* +> success = RL_SetWindowIcon( Image image ) + +Set icon for window ( Only PLATFORM_DESKTOP ) + +- Failure return false +- Success return true +*/ +int lcoreSetWindowIcon( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetWindowIcon( Image image )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t imageId = lua_tointeger( L, -1 ); + + if ( !validImage( imageId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + SetWindowIcon( *state->images[ imageId ] ); + lua_pushboolean( L, true ); + return 1; +} + +/* +> success = RL_SetWindowTitle( string title ) + +Set title for window ( Only PLATFORM_DESKTOP ) + +- Failure return false +- Success return true +*/ +int lcoreSetWindowTitle( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetWindowTitle( string title )" ); + lua_pushboolean( L, false ); + return 1; + } + SetWindowTitle( lua_tostring( L, -1 ) ); + lua_pushboolean( L, true ); + return 1; +} + +/* +## Core - Timing +*/ + +/* +> success = RL_SetTargetFPS( int fps ) + +Set target FPS ( maximum ) + +- Failure return false +- Success return true +*/ +int lcoreSetTargetFPS( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetTargetFPS( int fps )" ); + lua_pushboolean( L, false ); + return 1; + } + + SetTargetFPS( lua_tointeger( L, -1 ) ); + lua_pushboolean( L, true ); + return 1; +} + +/* +> RL_GetFrameTime() + +Get time in seconds for last frame drawn ( Delta time ) +*/ +int lcoreGetFrameTime( lua_State *L ) { + lua_pushnumber( L, GetFrameTime() ); + + return 1; +} + +/* +> RL_GetTime() + +Get elapsed time in seconds since InitWindow() +*/ +int lcoreGetTime( lua_State *L ) { + lua_pushnumber( L, GetTime() ); + + return 1; +} + +/* +## Core - Misc +*/ + +/* +> success = RL_TraceLog( int logLevel, string text ) + +Show trace log messages ( LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR... ) + +- Failure return false +- Success return true +*/ +int lcoreTraceLog( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_TraceLog( int logLevel, string text )" ); + lua_pushboolean( L, false ); + return 1; + } + TraceLog( lua_tointeger( L, -2 ), "%s", lua_tostring( L, -1 ) ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_OpenURL( string url ) + +Open URL with default system browser ( If available ) + +- Failure return false +- Success return true +*/ +int lcoreOpenURL( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_OpenURL( string url )" ); + lua_pushboolean( L, false ); + return 1; + } + OpenURL( lua_tostring( L, -1 ) ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Core - Cursor +*/ + +/* +> RL_ShowCursor() + +Shows cursor +*/ +int lcoreShowCursor( lua_State *L ) { + ShowCursor(); + + return 0; +} + +/* +> RL_HideCursor() + +Hides cursor +*/ +int lcoreHideCursor( lua_State *L ) { + HideCursor(); + + return 0; +} + +/* +> hidden = RL_IsCursorHidden() + +Check if cursor is not visible + +- Success return bool +*/ +int lcoreIsCursorHidden( lua_State *L ) { + lua_pushboolean( L, IsCursorHidden() ); + + return 1; +} + +/* +> RL_EnableCursor() + +Enables cursor (unlock cursor) +*/ +int lcoreEnableCursor( lua_State *L ) { + EnableCursor(); + + return 0; +} + +/* +> RL_DisableCursor() + +Disables cursor (lock cursor) +*/ +int lcoreDisableCursor( lua_State *L ) { + DisableCursor(); + + return 0; +} + +/* +> onSreen = RL_IsCursorOnScreen() + +Check if cursor is on the screen + +- Success return bool +*/ +int lcoreIsCursorOnScreen( lua_State *L ) { + lua_pushboolean( L, IsCursorOnScreen() ); + + return 1; +} + +/* +## Core - Drawing +*/ + +/* +> success = RL_ClearBackground( Color color ) + +Set background color ( framebuffer clear color ) + +- Failure return false +- Success return true +*/ +int lcoreClearBackground( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_ClearBackground( Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + ClearBackground( color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_BeginBlendMode( int mode ) + +Begin blending mode ( BLEND_ALPHA, BLEND_ADDITIVE, BLEND_MULTIPLIED... ) + +- Failure return false +- Success return true +*/ +int lcoreBeginBlendMode( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_BeginBlendMode( int mode )" ); + lua_pushboolean( L, false ); + return 1; + } + BeginBlendMode( lua_tointeger( L, -1 ) ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> RL_EndBlendMode() + +End blending mode ( reset to default: BLEND_ALPHA ) +*/ +int lcoreEndBlendMode( lua_State *L ) { + EndBlendMode(); + + return 1; +} + +/* +> success = RL_BeginScissorMode( Rectangle rectange ) + +Begin scissor mode ( define screen area for following drawing ) + +- Failure return false +- Success return true +*/ +int lcoreBeginScissorMode( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_BeginScissorMode( Rectangle rectange )" ); + lua_pushboolean( L, false ); + return 1; + } + Rectangle rect = uluaGetRectangle( L ); + + BeginScissorMode( rect.x, rect.y, rect.width, rect.height ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> RL_EndScissorMode() + +End scissor mode +*/ +int lcoreEndScissorMode( lua_State *L ) { + EndScissorMode(); + + return 1; +} + +/* +## Core - Shader +*/ + +/* +> shader = RL_LoadShader( string vsFileName, string fsFileName ) + +Load shader from files and bind default locations + +- Failure return -1 +- Success return int +*/ +int lcoreLoadShader( lua_State *L ) { + // if ( !lua_isstring( L, -2 ) || !lua_isstring( L, -1 ) ) { + // TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadShader( string vsFileName, string fsFileName )" ); + // lua_pushinteger( L, -1 ); + // return 1; + // } + + char fsFileName[ STRING_LEN ] = { '\0' }; + char vsFileName[ STRING_LEN ] = { '\0' }; + + if ( lua_isstring( L, -1 ) ) { + if ( FileExists( lua_tostring( L, -1 ) ) ) { + strcpy( fsFileName, lua_tostring( L, -1 ) ); + } + } + if ( lua_isstring( L, -2 ) ) { + if ( FileExists( lua_tostring( L, -2 ) ) ) { + strcpy( vsFileName, lua_tostring( L, -2 ) ); + } + } + + int i = 0; + + for ( i = 0; i < state->shaderCount; i++ ) { + if ( state->shaders[i] == NULL ) { + break; + } + } + state->shaders[i] = malloc( sizeof( Shader ) ); + // *state->shaders[i] = LoadShader( lua_tostring( L, -2 ), lua_tostring( L, -1 ) ); + // *state->shaders[i] = LoadShader( vsFileName, fsFileName ); + *state->shaders[i] = LoadShader( 0, fsFileName ); + lua_pushinteger( L, i ); + checkShaderRealloc( i ); + + return 1; +} + +/* +> shader = RL_LoadShaderFromMemory( string vsCode, string fsCode ) + +Load shader from code strings and bind default locations + +- Failure return -1 +- Success return int +*/ + +//TODO Should also allow only one shader. +int lcoreLoadShaderFromMemory( lua_State *L ) { + if ( !lua_isstring( L, -2 ) || !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadShaderFromMemory( string vsCode, string fsCode )" ); + lua_pushinteger( L, -1 ); + return 1; + } + int i = 0; + + for ( i = 0; i < state->shaderCount; i++ ) { + if ( state->shaders[i] == NULL ) { + break; + } + } + state->shaders[i] = malloc( sizeof( Shader ) ); + *state->shaders[i] = LoadShaderFromMemory( lua_tostring( L, -2 ), lua_tostring( L, -1 ) ); + lua_pushinteger( L, i ); + checkShaderRealloc( i ); + + return 1; +} + +/* +> success = RL_BeginShaderMode( Shader shader ) + +Begin custom shader drawing + +- Failure return false +- Success return true +*/ +int lcoreBeginShaderMode( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_BeginShaderMode( Shader shader )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t shaderId = lua_tointeger( L, -1 ); + + if ( !validShader( shaderId ) ) { + lua_pushboolean( L, false ); + return 1; + } + BeginShaderMode( *state->shaders[ shaderId ] ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> EndShaderMode() + +End custom shader drawing ( use default shader ) +*/ +int lcoreEndShaderMode( lua_State *L ) { + EndShaderMode(); + + return 1; +} + +/* +> location = RL_GetShaderLocation( Shader shader, string uniformName ) + +Get shader uniform location + +- Failure return -1 +- Success return int +*/ +int lcoreGetShaderLocation( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetShaderLocation( Shader shader, string uniformName )" ); + lua_pushinteger( L, -1 ); + return 1; + } + size_t shaderId = lua_tointeger( L, -2 ); + + if ( !validShader( shaderId ) ) { + lua_pushinteger( L, -1 ); + return 1; + } + lua_pushinteger( L, GetShaderLocation( *state->shaders[ shaderId ], lua_tostring( L, -1 ) ) ); + + return 1; +} + +/* +> location = RL_GetShaderLocationAttrib( Shader shader, string attribName ) + +Get shader attribute location + +- Failure return -1 +- Success return int +*/ +int lcoreGetShaderLocationAttrib( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetShaderLocationAttrib( Shader shader, string attribName )" ); + lua_pushinteger( L, -1 ); + return 1; + } + size_t shaderId = lua_tointeger( L, -2 ); + + if ( !validShader( shaderId ) ) { + lua_pushinteger( L, -1 ); + return 1; + } + lua_pushinteger( L, GetShaderLocationAttrib( *state->shaders[ shaderId ], lua_tostring( L, -1 ) ) ); + + return 1; +} + +/* +> success = RL_SetShaderValueMatrix( Shader shader, int locIndex, Matrix mat ) + +Set shader uniform value ( matrix 4x4 ) + +- Failure return false +- Success return true +*/ +int lcoreSetShaderValueMatrix( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetShaderValueMatrix( Shader shader, int locIndex, Matrix mat )" ); + lua_pushboolean( L, false ); + return 1; + } + Matrix mat = uluaGetMatrix( L ); + lua_pop( L, 1 ); + int locIndex = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + size_t shaderId = lua_tointeger( L, -1 ); + + if ( !validShader( shaderId ) ) { + lua_pushboolean( L, false ); + return 1; + } + SetShaderValueMatrix( *state->shaders[ shaderId ], locIndex, mat ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetShaderValueTexture( Shader shader, int locIndex, Texture2D texture ) + +Set shader uniform value for texture ( sampler2d ) + +- Failure return false +- Success return true +*/ +int lcoreSetShaderValueTexture( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetShaderValueTexture( Shader shader, int locIndex, Texture2D texture )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t textureId = lua_tointeger( L, -1 ); + int locIndex = lua_tointeger( L, -2 ); + size_t shaderId = lua_tointeger( L, -3 ); + + if ( !validShader( shaderId ) || !validTexture( textureId ) ) { + lua_pushboolean( L, false ); + return 1; + } + SetShaderValueTexture( *state->shaders[ shaderId ], locIndex, *state->textures[ textureId ] ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetShaderValue( Shader shader, int locIndex, number{} values, int uniformType ) + +Set shader uniform value +NOTE: Even one value should be in table + +- Failure return false +- Success return true +*/ +int lcoreSetShaderValue( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetShaderValue( Shader shader, int locIndex, number{} values, int uniformType )" ); + lua_pushboolean( L, false ); + return 1; + } + int uniformType = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + + /* Read values. */ + size_t valueCount = uluaGetTableLen( L ); + float floats[ valueCount ]; + int ints[ valueCount ]; + + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_isnumber( L, -1 ) ) { + floats[i] = lua_tonumber( L, -1 ); + ints[i] = lua_tointeger( L, -1 ); + } + i++; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + /* Read values end. */ + + int locIndex = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + size_t shaderId = lua_tointeger( L, -1 ); + + if ( !validShader( shaderId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + if ( uniformType == SHADER_UNIFORM_FLOAT || uniformType == SHADER_UNIFORM_VEC2 + || uniformType == SHADER_UNIFORM_VEC3 || uniformType == SHADER_UNIFORM_VEC4 ) { + SetShaderValue( *state->shaders[ shaderId ], locIndex, floats, uniformType ); + } + else { + SetShaderValue( *state->shaders[ shaderId ], locIndex, ints, uniformType ); + } + + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetShaderValueV( Shader shader, int locIndex, number{} values, int uniformType, int count ) + +Set shader uniform value vector +NOTE: Even one value should be in table + +- Failure return false +- Success return true +*/ +int lcoreSetShaderValueV( lua_State *L ) { + if ( !lua_isnumber( L, -5 ) || !lua_isnumber( L, -4 ) || !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetShaderValueV( Shader shader, int locIndex, number{} values, int uniformType, int count )" ); + lua_pushboolean( L, false ); + return 1; + } + int count = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + int uniformType = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + + /* Read values. */ + size_t valueCount = uluaGetTableLen( L ); + float floats[ valueCount * count ]; + int ints[ valueCount * count ]; + + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_isnumber( L, -1 ) ) { + floats[i] = lua_tonumber( L, -1 ); + ints[i] = lua_tointeger( L, -1 ); + } + i++; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + /* Read values end. */ + + int locIndex = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + size_t shaderId = lua_tointeger( L, -1 ); + + if ( !validShader( shaderId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + if ( uniformType == SHADER_UNIFORM_FLOAT || uniformType == SHADER_UNIFORM_VEC2 + || uniformType == SHADER_UNIFORM_VEC3 || uniformType == SHADER_UNIFORM_VEC4 ) { + SetShaderValueV( *state->shaders[ shaderId ], locIndex, floats, uniformType, count ); + } + else { + SetShaderValueV( *state->shaders[ shaderId ], locIndex, ints, uniformType, count ); + } + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_UnloadShader( Shader shader ) + +Unload shader from GPU memory ( VRAM ) + +- Failure return false +- Success return true +*/ +int lcoreUnloadShader( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_UnloadShader( Shader shader )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t id = lua_tointeger( L, -1 ); + + if ( !validShader( id ) ) { + lua_pushboolean( L, false ); + return 1; + } + UnloadShader( *state->shaders[ id ] ); + state->shaders[ id ] = NULL; + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Core - Input +*/ + +/* +> pressed = RL_IsKeyPressed( int key ) + +Detect if a key has been pressed once + +- Failure return nil +- Success return bool +*/ +int lcoreIsKeyPressed( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsKeyPressed( int key )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsKeyPressed( lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> pressed = RL_IsKeyDown( int key ) + +Detect if a key is being pressed + +- Failure return nil +- Success return bool +*/ +int lcoreIsKeyDown( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsKeyDown( int key )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsKeyDown( lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> released = RL_IsKeyReleased( int key ) + +Detect if a key has been released once + +- Failure return nil +- Success return bool +*/ +int lcoreIsKeyReleased( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsKeyReleased( int key )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsKeyReleased( lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> keycode = RL_GetKeyPressed() + +Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty + +- Success return int +*/ +int lcoreGetKeyPressed( lua_State *L ) { + lua_pushinteger( L, GetKeyPressed() ); + + return 1; +} + +/* +> unicode = RL_GetCharPressed() + +Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty + +- Success return int +*/ +int lcoreGetCharPressed( lua_State *L ) { + lua_pushinteger( L, GetCharPressed() ); + + return 1; +} + +/* +> RL_SetExitKey( int key ) + +Set a custom key to exit program ( default is ESC ) +*/ +int lcoreSetExitKey( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetExitKey( int key )" ); + lua_pushnil( L ); + return 1; + } + SetExitKey( lua_tointeger( L, -1 ) ); + + return 1; +} + +/* +> available = RL_IsGamepadAvailable( int gamepad ) + +Detect if a gamepad is available + +- Failure return nil +- Success return bool +*/ +int lcoreIsGamepadAvailable( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsGamepadAvailable( int gamepad )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsGamepadAvailable( lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> pressed = RL_IsGamepadButtonPressed( int gamepad, int button ) + +Detect if a gamepad button has been pressed once + +- Failure return nil +- Success return bool +*/ +int lcoreIsGamepadButtonPressed( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsGamepadButtonPressed( int gamepad, int button )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsGamepadButtonPressed( lua_tointeger( L, -2 ), lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> pressed = RL_IsGamepadButtonDown( int gamepad, int button ) + +Detect if a gamepad button is being pressed + +- Failure return nil +- Success return bool +*/ +int lcoreIsGamepadButtonDown( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsGamepadButtonDown( int gamepad, int button )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsGamepadButtonDown( lua_tointeger( L, -2 ), lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> released = RL_IsGamepadButtonReleased( int gamepad, int button ) + +Detect if a gamepad button has been released once + +- Failure return nil +- Success return bool +*/ +int lcoreIsGamepadButtonReleased( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsGamepadButtonReleased( int gamepad, int button )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsGamepadButtonReleased( lua_tointeger( L, -2 ), lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> count = RL_GetGamepadAxisCount( int gamepad ) + +Return gamepad axis count for a gamepad + +- Failure return false +- Success return int +*/ +int lcoreGetGamepadAxisCount( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetGamepadAxisCount( int gamepad )" ); + lua_pushboolean( L, false ); + return 1; + } + lua_pushinteger( L, GetGamepadAxisCount( lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> value = RL_GetGamepadAxisMovement( int gamepad, int axis ) + +Return axis movement value for a gamepad axis + +- Failure return false +- Success return float +*/ +int lcoreGetGamepadAxisMovement( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetGamepadAxisMovement( int gamepad, int axis )" ); + lua_pushboolean( L, false ); + return 1; + } + lua_pushnumber( L, GetGamepadAxisMovement( lua_tointeger( L, -2 ), lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> name = RL_GetGamepadName( int gamepad ) + +Return gamepad internal name id + +- Failure return false +- Success return string +*/ +int lcoreGetGamepadName( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetGamepadName( int gamepad )" ); + lua_pushboolean( L, false ); + return 1; + } + lua_pushstring( L, GetGamepadName( lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> pressed = RL_IsMouseButtonPressed( int button ) + +Detect if a mouse button has been pressed once + +- Failure return nil +- Success return bool +*/ +int lcoreIsMouseButtonPressed( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsMouseButtonPressed( int button )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsMouseButtonPressed( lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> pressed = RL_IsMouseButtonDown( int button ) + +Detect if a mouse button is being pressed + +- Failure return nil +- Success return bool +*/ +int lcoreIsMouseButtonDown( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsMouseButtonDown( int button )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsMouseButtonDown( lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> released = RL_IsMouseButtonReleased( int button ) + +Detect if a mouse button has been released once + +- Failure return nil +- Success return bool +*/ +int lcoreIsMouseButtonReleased( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsMouseButtonReleased( int button )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsMouseButtonReleased( lua_tointeger( L, -1 ) ) ); + return 1; +} + +/* +> position = RL_GetMousePosition() + +Returns mouse position + +- Success return Vector2 +*/ +int lcoreGetMousePosition( lua_State *L ) { + uluaPushVector2( L, GetMousePosition() ); + return 1; +} + +/* +> position = RL_GetMouseDelta() + +Get mouse delta between frames + +- Success return Vector2 +*/ +int lcoreGetMouseDelta( lua_State *L ) { + uluaPushVector2( L, GetMouseDelta() ); + return 1; +} + +/* +> movement = RL_GetMouseWheelMove() + +Returns mouse wheel movement Y + +- Success return float +*/ +int lcoreGetMouseWheelMove( lua_State *L ) { + lua_pushnumber( L, GetMouseWheelMove() ); + return 1; +} + +/* +> success = RL_SetMousePosition( Vector2 position ) + +Set mouse position XY + +- Failure return false +- Success return true +*/ +int lcoreSetMousePosition( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetMousePosition( Vector2 position )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 pos = uluaGetVector2( L ); + + SetMousePosition( pos.x, pos.y ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Core - File +*/ + +/* +> path = RL_GetBasePath() + +Return game directory ( where main.lua is located ) + +- Success return string +*/ +int lcoreGetBasePath( lua_State *L ) { + lua_pushstring( L, state->exePath ); + + return 1; +} + +/* +> fileExists = RL_FileExists( string fileName ) + +Check if file exists + +- Failure return nil +- Success return bool +*/ +int lcoreFileExists( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_FileExists( string fileName )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, FileExists( lua_tostring( L, -1 ) ) ); + return 1; +} + +/* +> dirExists = RL_DirectoryExists( string dirPath ) + +Check if a directory path exists + +- Failure return nil +- Success return bool +*/ +int lcoreDirectoryExists( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DirectoryExists( string dirPath )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, DirectoryExists( lua_tostring( L, -1 ) ) ); + return 1; +} + +/* +> hasFileExtension = RL_IsFileExtension( string fileName, string ext ) + +Check file extension ( Including point: .png, .wav ) + +- Failure return nil +- Success return bool +*/ +int lcoreIsFileExtension( lua_State *L ) { + if ( !lua_isstring( L, -2 ) || !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_IsFileExtension( string fileName, string ext )" ); + lua_pushnil( L ); + return 1; + } + lua_pushboolean( L, IsFileExtension( lua_tostring( L, -2 ), lua_tostring( L, -1 ) ) ); + return 1; +} + +/* +> extension = RL_GetFileExtension( string fileName ) + +Get pointer to extension for a filename string ( Includes dot: '.png' ) + +- Failure return false +- Success return string +*/ +int lcoreGetFileExtension( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetFileExtension( string fileName )" ); + lua_pushboolean( L, false ); + return 1; + } + lua_pushstring( L, GetFileExtension( lua_tostring( L, -1 ) ) ); + return 1; +} + +/* +> filePath = RL_GetFileName( string filePath ) + +Get pointer to filename for a path string + +- Failure return false +- Success return string +*/ +int lcoreGetFileName( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetFileName( string filePath )" ); + lua_pushboolean( L, false ); + return 1; + } + lua_pushstring( L, GetFileName( lua_tostring( L, -1 ) ) ); + return 1; +} + +/* +> filePath = RL_GetFileNameWithoutExt( string filePath ) + +Get filename string without extension ( Uses static string ) + +- Failure return false +- Success return string +*/ +int lcoreGetFileNameWithoutExt( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetFileNameWithoutExt( string filePath )" ); + lua_pushboolean( L, false ); + return 1; + } + lua_pushstring( L, GetFileNameWithoutExt( lua_tostring( L, -1 ) ) ); + return 1; +} + +/* +> filePath = RL_GetDirectoryPath( string filePath ) + +Get full path for a given fileName with path ( Uses static string ) + +- Failure return false +- Success return string +*/ +int lcoreGetDirectoryPath( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetDirectoryPath( string filePath )" ); + lua_pushboolean( L, false ); + return 1; + } + lua_pushstring( L, GetDirectoryPath( lua_tostring( L, -1 ) ) ); + return 1; +} + +/* +> filePath = RL_GetPrevDirectoryPath( string dirPath ) + +Get previous directory path for a given path ( Uses static string ) + +- Failure return false +- Success return string +*/ +int lcoreGetPrevDirectoryPath( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetPrevDirectoryPath( string dirPath )" ); + lua_pushboolean( L, false ); + return 1; + } + lua_pushstring( L, GetPrevDirectoryPath( lua_tostring( L, -1 ) ) ); + return 1; +} + +/* +> filePath = RL_GetWorkingDirectory() + +Get current working directory ( Uses static string ) + +- Failure return false +- Success return string +*/ +int lcoreGetWorkingDirectory( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetWorkingDirectory()" ); + lua_pushboolean( L, false ); + return 1; + } + lua_pushstring( L, GetWorkingDirectory() ); + return 1; +} + +/* +> fileNames = RL_GetDirectoryFiles( string dirPath ) + +Get filenames in a directory path + +- Failure return false +- Success return string{} +*/ +int lcoreGetDirectoryFiles( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetDirectoryFiles( string dirPath )" ); + lua_pushboolean( L, false ); + return 1; + } + int count = 0; + char **strings = GetDirectoryFiles( lua_tostring( L, -1 ), &count ); + + lua_createtable( L, count, 0 ); + + for ( int i = 0; i < count; ++i ) { + lua_pushstring( L, strings[i] ); + lua_rawseti( L, -2, i+1 ); + } + ClearDirectoryFiles(); + + return 1; +} + +/* +> time = RL_GetFileModTime( string fileName ) + +Get file modification time ( Last write time ) + +- Failure return false +- Success return int +*/ +int lcoreGetFileModTime( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetFileModTime( string fileName )" ); + lua_pushboolean( L, false ); + return 1; + } + lua_pushinteger( L, GetFileModTime( lua_tostring( L, -1 ) ) ); + return 1; +} + +/* +## Core - Camera +*/ + +/* +> camera = RL_CreateCamera3D() + +Return camera3D id set to default configuration + +- Success return int +*/ +int lcoreCreateCamera3D( lua_State *L ) { + int i = 0; + + for ( i = 0; i < state->camera3DCount; i++ ) { + if ( state->camera3Ds[i] == NULL ) { + break; + } + } + state->camera3Ds[i] = malloc( sizeof( Camera3D ) ); + state->camera3Ds[i]->fovy = 45.0f; + state->camera3Ds[i]->projection = CAMERA_PERSPECTIVE; + SetCameraMode( *state->camera3Ds[i], CAMERA_CUSTOM ); + + lua_pushinteger( L, i ); + checkCamera3DRealloc(i); + + return 1; +} + +/* +> success = RL_UnloadCamera3D( int Camera3D ) + +Unload Camera3D + +- Failure return false +- Success return true +*/ +int lcoreUnloadCamera3D( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_UnloadCamera3D( int Camera3D )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t id = lua_tointeger( L, -1 ); + + if ( !validCamera3D( id ) ) { + lua_pushboolean( L, false ); + return 1; + } + + free( state->camera3Ds[ id ] ); + state->camera3Ds[ id ] = NULL; + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_BeginMode3D( camera3D camera ) + +Initializes 3D mode with custom camera ( 3D ) + +- Failure return false +- Success return true +*/ +int lcoreBeginMode3D( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_BeginMode3D( camera3D camera )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t id = lua_tointeger( L, -1 ); + + if ( !validCamera3D( id ) ) { + lua_pushboolean( L, false ); + return 1; + } + + BeginMode3D( *state->camera3Ds[ id ] ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> RL_EndMode3D() + +Ends 3D mode and returns to default 2D orthographic mode +*/ +int lcoreEndMode3D( lua_State *L ) { + EndMode3D(); + + return 1; +} + +/* +> success = RL_SetCamera3DPosition( camera3D camera, Vector3 position ) + +Set camera position ( Remember to call "RL_UpdateCamera3D()" to apply changes ) + +- Failure return false +- Success return true +*/ +int lcoreSetCamera3DPosition( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetCamera3DPosition( camera3D camera, Vector3 position )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 pos = uluaGetVector3( L ); + size_t cameraId = lua_tointeger( L, -2 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + state->camera3Ds[ cameraId ]->position = pos; + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetCamera3DTarget( camera3D camera, Vector3 target ) + +Set camera target it looks-at + +- Failure return false +- Success return true +*/ +int lcoreSetCamera3DTarget( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetCamera3DTarget( camera3D camera, Vector3 target )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 target = uluaGetVector3( L ); + size_t cameraId = lua_tointeger( L, -2 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + state->camera3Ds[ cameraId ]->target = target; + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetCamera3DUp( camera3D camera, Vector3 up ) + +Set camera up vector ( Rotation over it's axis ) + +- Failure return false +- Success return true +*/ +int lcoreSetCamera3DUp( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetCamera3DUp( camera3D camera, Vector3 up )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 up = uluaGetVector3( L ); + size_t cameraId = lua_tointeger( L, -2 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + state->camera3Ds[ cameraId ]->up = up; + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetCamera3DFovy( camera3D camera, Vector3 fovy ) + +Set camera field-of-view apperture in Y ( degrees ) in perspective, used as near plane width in orthographic + +- Failure return false +- Success return true +*/ +int lcoreSetCamera3DFovy( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetCamera3DFovy( camera3D camera, Vector3 fovy )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t cameraId = lua_tointeger( L, -2 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + state->camera3Ds[ cameraId ]->fovy = lua_tonumber( L, -1 ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetCamera3DProjection( camera3D camera, int projection ) + +Set camera projection mode ( CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC ) + +- Failure return false +- Success return true +*/ +int lcoreSetCamera3DProjection( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetCamera3DProjection( camera3D camera, int projection )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t cameraId = lua_tointeger( L, -2 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + state->camera3Ds[ cameraId ]->projection = lua_tointeger( L, -1 ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetCamera3DMode( camera3D camera, int mode ) + +Set camera mode ( CAMERA_CUSTOM, CAMERA_FREE, CAMERA_ORBITAL... ) + +- Failure return false +- Success return true +*/ +int lcoreSetCamera3DMode( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetCamera3DMode( camera3D camera, int mode )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t cameraId = lua_tointeger( L, -2 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + SetCameraMode( *state->camera3Ds[ cameraId ], lua_tointeger( L, -1 ) ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> position = RL_GetCamera3DPosition( camera3D camera ) + +Get camera position + +- Failure return nil +- Success return Vector3 +*/ +int lcoreGetCamera3DPosition( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetCamera3DPosition( camera3D camera )" ); + lua_pushnil( L ); + return 1; + } + size_t cameraId = lua_tointeger( L, -1 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushnil( L ); + return 1; + } + + uluaPushVector3( L, state->camera3Ds[ cameraId ]->position ); + + return 1; +} + +/* +> target = RL_GetCamera3DTarget( camera3D camera ) + +Get camera target it looks-at + +- Failure return nil +- Success return Vector3 +*/ +int lcoreGetCamera3DTarget( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetCamera3DTarget( camera3D camera )" ); + lua_pushnil( L ); + return 1; + } + size_t cameraId = lua_tointeger( L, -1 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushnil( L ); + return 1; + } + + uluaPushVector3( L, state->camera3Ds[ cameraId ]->target ); + + return 1; +} + +/* +> up = RL_GetCamera3DUp( camera3D camera ) + +Get camera up vector ( Rotation over it's axis ) + +- Failure return nil +- Success return Vector3 +*/ +int lcoreGetCamera3DUp( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetCamera3DUp( camera3D camera )" ); + lua_pushnil( L ); + return 1; + } + size_t cameraId = lua_tointeger( L, -1 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushnil( L ); + return 1; + } + + uluaPushVector3( L, state->camera3Ds[ cameraId ]->up ); + + return 1; +} + +/* +> fovy = RL_GetCamera3DFovy( camera3D camera ) + +Get camera field-of-view apperture in Y ( degrees ) in perspective, used as near plane width in orthographic + +- Failure return nil +- Success return float +*/ +int lcoreGetCamera3DFovy( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetCamera3DFovy( camera3D camera )" ); + lua_pushnil( L ); + return 1; + } + size_t cameraId = lua_tointeger( L, -1 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushnil( L ); + return 1; + } + + lua_pushnumber( L, state->camera3Ds[ cameraId ]->fovy ); + + return 1; +} + +/* +> projection = RL_GetCamera3DProjection( camera3D camera ) + +Get camera projection mode + +- Failure return nil +- Success return int +*/ +int lcoreGetCamera3DProjection( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetCamera3DProjection( camera3D camera )" ); + lua_pushnil( L ); + return 1; + } + size_t cameraId = lua_tointeger( L, -1 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushnil( L ); + return 1; + } + + lua_pushinteger( L, state->camera3Ds[ cameraId ]->projection ); + + return 1; +} + +/* +> success = RL_UpdateCamera3D( camera3D camera ) + +Update camera position for selected mode + +- Failure return false +- Success return true +*/ +int lcoreUpdateCamera3D( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_UpdateCamera3D( camera3D camera )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t cameraId = lua_tointeger( L, -1 ); + + if ( !validCamera3D( cameraId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + UpdateCamera( state->camera3Ds[ cameraId ] ); + lua_pushboolean( L, true ); + + return 1; +} diff --git a/src/lua_core.c b/src/lua_core.c new file mode 100644 index 0000000..ab59c1d --- /dev/null +++ b/src/lua_core.c @@ -0,0 +1,1033 @@ +#include "main.h" +#include "state.h" +#include "lua_core.h" +#include "core.h" +#include "shapes.h" +#include "textures.h" +#include "models.h" +#include "text.h" +#include "audio.h" +#include "rmath.h" +#include "rgui.h" + +static void assignGlobalInt( int value, const char *name ) { + lua_State *L = state->luaState; + lua_pushinteger( L, value ); + lua_setglobal( L, name ); +} + +static void assignGlobalFloat( float value, const char *name ) { + lua_State *L = state->luaState; + lua_pushnumber( L, value ); + lua_setglobal( L, name ); +} + +static void assignGlobalColor( Color color, const char *name ) { + lua_State *L = state->luaState; + uluaPushColor( L, color ); + lua_setglobal( L, name ); +} + +void defineGlobals() { +/*DOC_START*/ + /* Keys */ + assignGlobalInt( KEY_ENTER, "KEY_ENTER" ); + assignGlobalInt( KEY_SPACE, "KEY_SPACE" ); + assignGlobalInt( KEY_ESCAPE, "KEY_ESCAPE" ); + assignGlobalInt( KEY_ENTER, "KEY_ENTER" ); + assignGlobalInt( KEY_TAB, "KEY_TAB" ); + assignGlobalInt( KEY_BACKSPACE, "KEY_BACKSPACE" ); + assignGlobalInt( KEY_INSERT, "KEY_INSERT" ); + assignGlobalInt( KEY_DELETE, "KEY_DELETE" ); + assignGlobalInt( KEY_RIGHT, "KEY_RIGHT" ); + assignGlobalInt( KEY_LEFT, "KEY_LEFT" ); + assignGlobalInt( KEY_DOWN, "KEY_DOWN" ); + assignGlobalInt( KEY_UP, "KEY_UP" ); + /* WindowFlags */ + assignGlobalInt( FLAG_VSYNC_HINT, "FLAG_VSYNC_HINT" ); + assignGlobalInt( FLAG_FULLSCREEN_MODE, "FLAG_FULLSCREEN_MODE" ); + assignGlobalInt( FLAG_WINDOW_RESIZABLE, "FLAG_WINDOW_RESIZABLE" ); + assignGlobalInt( FLAG_WINDOW_UNDECORATED, "FLAG_WINDOW_UNDECORATED" ); + assignGlobalInt( FLAG_WINDOW_HIDDEN, "FLAG_WINDOW_HIDDEN" ); + assignGlobalInt( FLAG_WINDOW_MINIMIZED, "FLAG_WINDOW_MINIMIZED" ); + assignGlobalInt( FLAG_WINDOW_MAXIMIZED, "FLAG_WINDOW_MAXIMIZED" ); + assignGlobalInt( FLAG_WINDOW_UNFOCUSED, "FLAG_WINDOW_UNFOCUSED" ); + assignGlobalInt( FLAG_WINDOW_TOPMOST, "FLAG_WINDOW_TOPMOST" ); + assignGlobalInt( FLAG_WINDOW_ALWAYS_RUN, "FLAG_WINDOW_ALWAYS_RUN" ); + assignGlobalInt( FLAG_WINDOW_TRANSPARENT, "FLAG_WINDOW_TRANSPARENT" ); + assignGlobalInt( FLAG_WINDOW_HIGHDPI, "FLAG_WINDOW_HIGHDPI" ); + assignGlobalInt( FLAG_MSAA_4X_HINT, "FLAG_MSAA_4X_HINT" ); + assignGlobalInt( FLAG_INTERLACED_HINT, "FLAG_INTERLACED_HINT" ); + /* BlendModes */ + assignGlobalInt( BLEND_ALPHA, "BLEND_ALPHA" ); + assignGlobalInt( BLEND_ADDITIVE, "BLEND_ADDITIVE" ); + assignGlobalInt( BLEND_MULTIPLIED, "BLEND_MULTIPLIED" ); + assignGlobalInt( BLEND_ADD_COLORS, "BLEND_ADD_COLORS" ); + assignGlobalInt( BLEND_SUBTRACT_COLORS, "BLEND_SUBTRACT_COLORS" ); + assignGlobalInt( BLEND_CUSTOM, "BLEND_CUSTOM" ); + /* TextureModes */ + assignGlobalInt( TEXTURE_SOURCE_TEXTURE, "TEXTURE_SOURCE_TEXTURE" ); + assignGlobalInt( TEXTURE_SOURCE_RENDER_TEXTURE, "TEXTURE_SOURCE_RENDER_TEXTURE" ); + /* CameraProjections */ + assignGlobalInt( CAMERA_PERSPECTIVE, "CAMERA_PERSPECTIVE" ); + assignGlobalInt( CAMERA_ORTHOGRAPHIC, "CAMERA_ORTHOGRAPHIC" ); + /* CameraMode */ + assignGlobalInt( CAMERA_CUSTOM, "CAMERA_CUSTOM" ); + assignGlobalInt( CAMERA_FREE, "CAMERA_FREE" ); + assignGlobalInt( CAMERA_ORBITAL, "CAMERA_ORBITAL" ); + assignGlobalInt( CAMERA_FIRST_PERSON, "CAMERA_FIRST_PERSON" ); + assignGlobalInt( CAMERA_THIRD_PERSON, "CAMERA_THIRD_PERSON" ); + /* MapTypes */ + assignGlobalInt( MATERIAL_MAP_ALBEDO, "MATERIAL_MAP_ALBEDO" ); + assignGlobalInt( MATERIAL_MAP_METALNESS, "MATERIAL_MAP_METALNESS" ); + assignGlobalInt( MATERIAL_MAP_NORMAL, "MATERIAL_MAP_NORMAL" ); + assignGlobalInt( MATERIAL_MAP_ROUGHNESS, "MATERIAL_MAP_ROUGHNESS" ); + assignGlobalInt( MATERIAL_MAP_OCCLUSION, "MATERIAL_MAP_OCCLUSION" ); + assignGlobalInt( MATERIAL_MAP_EMISSION, "MATERIAL_MAP_EMISSION" ); + assignGlobalInt( MATERIAL_MAP_HEIGHT, "MATERIAL_MAP_HEIGHT" ); + assignGlobalInt( MATERIAL_MAP_CUBEMAP, "MATERIAL_MAP_CUBEMAP" ); + assignGlobalInt( MATERIAL_MAP_IRRADIANCE, "MATERIAL_MAP_IRRADIANCE" ); + assignGlobalInt( MATERIAL_MAP_PREFILTER, "MATERIAL_MAP_PREFILTER" ); + assignGlobalInt( MATERIAL_MAP_BRDF, "MATERIAL_MAP_BRDF" ); + /* TextureFilters */ + assignGlobalInt( TEXTURE_FILTER_POINT, "TEXTURE_FILTER_POINT" ); + assignGlobalInt( TEXTURE_FILTER_BILINEAR, "TEXTURE_FILTER_BILINEAR" ); + assignGlobalInt( TEXTURE_FILTER_TRILINEAR, "TEXTURE_FILTER_TRILINEAR" ); + assignGlobalInt( TEXTURE_FILTER_ANISOTROPIC_4X, "TEXTURE_FILTER_ANISOTROPIC_4X" ); + assignGlobalInt( TEXTURE_FILTER_ANISOTROPIC_8X, "TEXTURE_FILTER_ANISOTROPIC_8X" ); + assignGlobalInt( TEXTURE_FILTER_ANISOTROPIC_16X, "TEXTURE_FILTER_ANISOTROPIC_16X" ); + /* TextureWrapModes */ + assignGlobalInt( TEXTURE_WRAP_REPEAT, "TEXTURE_WRAP_REPEAT" ); + assignGlobalInt( TEXTURE_WRAP_CLAMP, "TEXTURE_WRAP_CLAMP" ); + assignGlobalInt( TEXTURE_WRAP_MIRROR_REPEAT, "TEXTURE_WRAP_MIRROR_REPEAT" ); + assignGlobalInt( TEXTURE_WRAP_MIRROR_CLAMP, "TEXTURE_WRAP_MIRROR_CLAMP" ); + /* TraceLogLevel */ + assignGlobalInt( LOG_ALL, "LOG_ALL" ); + assignGlobalInt( LOG_TRACE, "LOG_TRACE" ); + assignGlobalInt( LOG_DEBUG, "LOG_DEBUG" ); + assignGlobalInt( LOG_INFO, "LOG_INFO" ); + assignGlobalInt( LOG_WARNING, "LOG_WARNING" ); + assignGlobalInt( LOG_ERROR, "LOG_ERROR" ); + assignGlobalInt( LOG_FATAL, "LOG_FATAL" ); + assignGlobalInt( LOG_NONE, "LOG_NONE" ); + /* N-patchLayout */ + assignGlobalInt( NPATCH_NINE_PATCH, "NPATCH_NINE_PATCH" ); + assignGlobalInt( NPATCH_THREE_PATCH_VERTICAL, "NPATCH_THREE_PATCH_VERTICAL" ); + assignGlobalInt( NPATCH_THREE_PATCH_HORIZONTAL, "NPATCH_THREE_PATCH_HORIZONTAL" ); + /* Shader location index */ + assignGlobalInt( SHADER_LOC_VERTEX_POSITION, "SHADER_LOC_VERTEX_POSITION" ); + assignGlobalInt( SHADER_LOC_VERTEX_TEXCOORD01, "SHADER_LOC_VERTEX_TEXCOORD01" ); + assignGlobalInt( SHADER_LOC_VERTEX_TEXCOORD02, "SHADER_LOC_VERTEX_TEXCOORD02" ); + assignGlobalInt( SHADER_LOC_VERTEX_NORMAL, "SHADER_LOC_VERTEX_NORMAL" ); + assignGlobalInt( SHADER_LOC_VERTEX_TANGENT, "SHADER_LOC_VERTEX_TANGENT" ); + assignGlobalInt( SHADER_LOC_VERTEX_COLOR, "SHADER_LOC_VERTEX_COLOR" ); + assignGlobalInt( SHADER_LOC_MATRIX_MVP, "SHADER_LOC_MATRIX_MVP" ); + assignGlobalInt( SHADER_LOC_MATRIX_VIEW, "SHADER_LOC_MATRIX_VIEW" ); + assignGlobalInt( SHADER_LOC_MATRIX_PROJECTION, "SHADER_LOC_MATRIX_PROJECTION" ); + assignGlobalInt( SHADER_LOC_MATRIX_MODEL, "SHADER_LOC_MATRIX_MODEL" ); + assignGlobalInt( SHADER_LOC_MATRIX_NORMAL, "SHADER_LOC_MATRIX_NORMAL" ); + assignGlobalInt( SHADER_LOC_VECTOR_VIEW, "SHADER_LOC_VECTOR_VIEW" ); + assignGlobalInt( SHADER_LOC_COLOR_DIFFUSE, "SHADER_LOC_COLOR_DIFFUSE" ); + assignGlobalInt( SHADER_LOC_COLOR_SPECULAR, "SHADER_LOC_COLOR_SPECULAR" ); + assignGlobalInt( SHADER_LOC_COLOR_AMBIENT, "SHADER_LOC_COLOR_AMBIENT" ); + assignGlobalInt( SHADER_LOC_MAP_ALBEDO, "SHADER_LOC_MAP_ALBEDO" ); + assignGlobalInt( SHADER_LOC_MAP_METALNESS, "SHADER_LOC_MAP_METALNESS" ); + assignGlobalInt( SHADER_LOC_MAP_NORMAL, "SHADER_LOC_MAP_NORMAL" ); + assignGlobalInt( SHADER_LOC_MAP_ROUGHNESS, "SHADER_LOC_MAP_ROUGHNESS" ); + assignGlobalInt( SHADER_LOC_MAP_OCCLUSION, "SHADER_LOC_MAP_OCCLUSION" ); + assignGlobalInt( SHADER_LOC_MAP_EMISSION, "SHADER_LOC_MAP_EMISSION" ); + assignGlobalInt( SHADER_LOC_MAP_HEIGHT, "SHADER_LOC_MAP_HEIGHT" ); + assignGlobalInt( SHADER_LOC_MAP_CUBEMAP, "SHADER_LOC_MAP_CUBEMAP" ); + assignGlobalInt( SHADER_LOC_MAP_IRRADIANCE, "SHADER_LOC_MAP_IRRADIANCE" ); + assignGlobalInt( SHADER_LOC_MAP_PREFILTER, "SHADER_LOC_MAP_PREFILTER" ); + assignGlobalInt( SHADER_LOC_MAP_BRDF, "SHADER_LOC_MAP_BRDF" ); + /* Shader uniform data type */ + assignGlobalInt( SHADER_UNIFORM_FLOAT, "SHADER_UNIFORM_FLOAT" ); + assignGlobalInt( SHADER_UNIFORM_VEC2, "SHADER_UNIFORM_VEC2" ); + assignGlobalInt( SHADER_UNIFORM_VEC3, "SHADER_UNIFORM_VEC3" ); + assignGlobalInt( SHADER_UNIFORM_VEC4, "SHADER_UNIFORM_VEC4" ); + assignGlobalInt( SHADER_UNIFORM_INT, "SHADER_UNIFORM_INT" ); + assignGlobalInt( SHADER_UNIFORM_IVEC2, "SHADER_UNIFORM_IVEC2" ); + assignGlobalInt( SHADER_UNIFORM_IVEC3, "SHADER_UNIFORM_IVEC3" ); + assignGlobalInt( SHADER_UNIFORM_IVEC4, "SHADER_UNIFORM_IVEC4" ); + assignGlobalInt( SHADER_UNIFORM_SAMPLER2D, "SHADER_UNIFORM_SAMPLER2D" ); + /* Shader attribute data types */ + assignGlobalInt( SHADER_ATTRIB_FLOAT, "SHADER_ATTRIB_FLOAT" ); + assignGlobalInt( SHADER_ATTRIB_VEC2, "SHADER_ATTRIB_VEC2" ); + assignGlobalInt( SHADER_ATTRIB_VEC3, "SHADER_ATTRIB_VEC3" ); + assignGlobalInt( SHADER_ATTRIB_VEC4, "SHADER_ATTRIB_VEC4" ); + /* Colors */ + assignGlobalColor( WHITE, "WHITE" ); + assignGlobalColor( BLACK, "BLACK" ); + assignGlobalColor( BLANK, "BLANK" ); + assignGlobalColor( MAGENTA, "MAGENTA" ); + assignGlobalColor( RAYWHITE, "RAYWHITE" ); + assignGlobalColor( RED, "RED" ); + assignGlobalColor( GREEN, "GREEN" ); + assignGlobalColor( BLUE, "BLUE" ); + /* Math */ + assignGlobalFloat( PI, "PI" ); +/*DOC_END*/ +} + +bool luaInit() { + state->luaState = luaL_newstate(); + + luaL_openlibs( state->luaState ); + + if ( state->luaState == NULL ) { + TraceLog( LOG_WARNING, "%s", "Failed to init Lua" ); + + return false; + } + luaRegister(); + defineGlobals(); + + return luaCallMain(); +} + +int luaTraceback( lua_State *L ) { + lua_getglobal( L, "debug" ); + + if ( !lua_istable( L, -1 ) ) { + lua_pop( L, 1 ); + return 1; + } + + lua_getfield( L, -1, "traceback" ); + + if ( !lua_isfunction( L, -1 ) ) { + lua_pop(L, 2); + return 1; + } + + lua_pushvalue( L, 1 ); // pass error message + lua_pushinteger( L, 2 ); // skip this function and traceback + lua_call( L, 2, 1 ); // call debug.traceback + + return 1; +} + +bool luaCallMain() { + lua_State *L = state->luaState; + + char path[ STRING_LEN ] = { '\0' }; + + sprintf( path, "%smain.lua", state->exePath ); + + luaL_dofile( L, path ); + + /* Check errors in main.lua */ + if ( lua_tostring( state->luaState, -1 ) ) { + TraceLog( LOG_WARNING, "Lua error: %s\n", lua_tostring( state->luaState, -1 ) ); + } + + lua_pushcfunction( L, luaTraceback ); + int tracebackidx = lua_gettop( L ); + + lua_getglobal( L, "init" ); + + if ( lua_isfunction( L, -1 ) ) { + if ( lua_pcall( L, 0, 0, tracebackidx ) != 0 ) { + TraceLog( LOG_WARNING, "Lua error: %s", lua_tostring( L, -1 ) ); + return false; + } + } + else { + TraceLog( LOG_WARNING, "%s", "No Lua main found!" ); + return false; + } + + return true; +} + +void luaCallProcess() { + lua_State *L = state->luaState; + + lua_pushcfunction( L, luaTraceback ); + int tracebackidx = lua_gettop(L); + + lua_getglobal( L, "process" ); + + if ( lua_isfunction( L, -1 ) ) { + lua_pushnumber( L, GetFrameTime() ); + + if ( lua_pcall( L, 1, 0, tracebackidx ) != 0 ) { + TraceLog( LOG_WARNING, "Lua error: %s", lua_tostring( L, -1 ) ); + state->run = false; + return; + } + } + // else { + // TraceLog( LOG_WARNING, "%s", "No Lua process found!" ); + // state->run = false; + // return; + // } + lua_pop( L, -1 ); +} + +void luaCallDraw() { + lua_State *L = state->luaState; + lua_pushcfunction( L, luaTraceback ); + int tracebackidx = lua_gettop(L); + + lua_getglobal( L, "draw" ); + + if ( lua_isfunction( L, -1 ) ) { + BeginDrawing(); + + if ( lua_pcall( L, 0, 0, tracebackidx ) != 0 ) { + TraceLog( LOG_WARNING, "Lua error: %s", lua_tostring( L, -1 ) ); + // state->run = false; + return; + } + + EndDrawing(); + } + // else { + // TraceLog( LOG_WARNING, "%s", "No Lua render found!" ); + // state->run = false; + // return; + // } + lua_pop( L, -1 ); +} + +void luaRegister() { + lua_State *L = state->luaState; + + /* Core. */ + /* Window. */ + lua_register( L, "RL_SetWindowMonitor", lcoreSetWindowMonitor ); + lua_register( L, "RL_SetWindowPosition", lcoreSetWindowPosition ); + lua_register( L, "RL_SetWindowSize", lcoreSetWindowSize ); + lua_register( L, "RL_GetMonitorPosition", lcoreGetMonitorPosition ); + lua_register( L, "RL_GetMonitorSize", lcoreGetMonitorSize ); + lua_register( L, "RL_GetWindowPosition", lcoreGetWindowPosition ); + lua_register( L, "RL_GetWindowSize", lcoreGetWindowSize ); + lua_register( L, "RL_SetWindowState", lcoreSetWindowState ); + lua_register( L, "RL_IsWindowState", lcoreIsWindowState ); + lua_register( L, "RL_ClearWindowState", lcoreClearWindowState ); + lua_register( L, "RL_IsWindowResized", lcoreIsWindowResized ); + lua_register( L, "RL_SetWindowIcon", lcoreSetWindowIcon ); + lua_register( L, "RL_SetWindowTitle", lcoreSetWindowTitle ); + /* Timing. */ + lua_register( L, "RL_SetTargetFPS", lcoreSetTargetFPS ); + lua_register( L, "RL_GetFrameTime", lcoreGetFrameTime ); + lua_register( L, "RL_GetTime", lcoreGetTime ); + /* Misc. */ + lua_register( L, "RL_TraceLog", lcoreTraceLog ); + lua_register( L, "RL_OpenURL", lcoreOpenURL ); + /* Cursor. */ + lua_register( L, "RL_ShowCursor", lcoreShowCursor ); + lua_register( L, "RL_HideCursor", lcoreHideCursor ); + lua_register( L, "RL_IsCursorHidden", lcoreIsCursorHidden ); + lua_register( L, "RL_EnableCursor", lcoreEnableCursor ); + lua_register( L, "RL_DisableCursor", lcoreDisableCursor ); + lua_register( L, "RL_IsCursorOnScreen", lcoreIsCursorOnScreen ); + /* Drawing. */ + lua_register( L, "RL_ClearBackground", lcoreClearBackground ); + lua_register( L, "RL_BeginBlendMode", lcoreBeginBlendMode ); + lua_register( L, "RL_EndBlendMode", lcoreEndBlendMode ); + lua_register( L, "RL_BeginScissorMode", lcoreBeginScissorMode ); + lua_register( L, "RL_EndScissorMode", lcoreEndScissorMode ); + /* Shader. */ + lua_register( L, "RL_LoadShader", lcoreLoadShader ); + lua_register( L, "RL_LoadShaderFromMemory", lcoreLoadShaderFromMemory ); + lua_register( L, "RL_BeginShaderMode", lcoreBeginShaderMode ); + lua_register( L, "RL_EndShaderMode", lcoreEndShaderMode ); + lua_register( L, "RL_GetShaderLocation", lcoreGetShaderLocation ); + lua_register( L, "RL_GetShaderLocationAttrib", lcoreGetShaderLocationAttrib ); + lua_register( L, "RL_SetShaderValueMatrix", lcoreSetShaderValueMatrix ); + lua_register( L, "RL_SetShaderValueTexture", lcoreSetShaderValueTexture ); + lua_register( L, "RL_SetShaderValue", lcoreSetShaderValue ); + lua_register( L, "RL_SetShaderValueV", lcoreSetShaderValueV ); + lua_register( L, "RL_UnloadShader", lcoreUnloadShader ); + /* File. */ + lua_register( L, "RL_GetBasePath", lcoreGetBasePath ); + lua_register( L, "RL_FileExists", lcoreFileExists ); + lua_register( L, "RL_DirectoryExists", lcoreDirectoryExists ); + lua_register( L, "RL_IsFileExtension", lcoreIsFileExtension ); + lua_register( L, "RL_GetFileExtension", lcoreGetFileExtension ); + lua_register( L, "RL_GetFileName", lcoreGetFileName ); + lua_register( L, "RL_GetFileNameWithoutExt", lcoreGetFileNameWithoutExt ); + lua_register( L, "RL_GetDirectoryPath", lcoreGetDirectoryPath ); + lua_register( L, "RL_GetPrevDirectoryPath", lcoreGetPrevDirectoryPath ); + lua_register( L, "RL_GetWorkingDirectory", lcoreGetWorkingDirectory ); + lua_register( L, "RL_GetDirectoryFiles", lcoreGetDirectoryFiles ); + lua_register( L, "RL_GetFileModTime", lcoreGetFileModTime ); + /* Camera. */ + lua_register( L, "RL_CreateCamera3D", lcoreCreateCamera3D ); + lua_register( L, "RL_UnloadCamera3D", lcoreUnloadCamera3D ); + lua_register( L, "RL_BeginMode3D", lcoreBeginMode3D ); + lua_register( L, "RL_EndMode3D", lcoreEndMode3D ); + lua_register( L, "RL_SetCamera3DPosition", lcoreSetCamera3DPosition ); + lua_register( L, "RL_SetCamera3DTarget", lcoreSetCamera3DTarget ); + lua_register( L, "RL_SetCamera3DUp", lcoreSetCamera3DUp ); + lua_register( L, "RL_SetCamera3DFovy", lcoreSetCamera3DFovy ); + lua_register( L, "RL_SetCamera3DProjection", lcoreSetCamera3DProjection ); + lua_register( L, "RL_GetCamera3DPosition", lcoreGetCamera3DPosition ); + lua_register( L, "RL_GetCamera3DTarget", lcoreGetCamera3DTarget ); + lua_register( L, "RL_GetCamera3DUp", lcoreGetCamera3DUp ); + lua_register( L, "RL_GetCamera3DFovy", lcoreGetCamera3DFovy ); + lua_register( L, "RL_GetCamera3DProjection", lcoreGetCamera3DProjection ); + lua_register( L, "RL_UpdateCamera3D", lcoreUpdateCamera3D ); + lua_register( L, "RL_SetCamera3DMode", lcoreSetCamera3DMode ); + /* Input. */ + lua_register( L, "RL_IsKeyPressed", lcoreIsKeyPressed ); + lua_register( L, "RL_IsKeyDown", lcoreIsKeyDown ); + lua_register( L, "RL_IsKeyReleased", lcoreIsKeyReleased ); + lua_register( L, "RL_GetKeyPressed", lcoreGetKeyPressed ); + lua_register( L, "RL_GetCharPressed", lcoreGetCharPressed ); + lua_register( L, "RL_SetExitKey", lcoreSetExitKey ); + lua_register( L, "RL_IsGamepadAvailable", lcoreIsGamepadAvailable ); + lua_register( L, "RL_IsGamepadButtonPressed", lcoreIsGamepadButtonPressed ); + lua_register( L, "RL_IsGamepadButtonDown", lcoreIsGamepadButtonDown ); + lua_register( L, "RL_IsGamepadButtonReleased", lcoreIsGamepadButtonReleased ); + lua_register( L, "RL_GetGamepadAxisCount", lcoreGetGamepadAxisCount ); + lua_register( L, "RL_GetGamepadAxisMovement", lcoreGetGamepadAxisMovement ); + lua_register( L, "RL_GetGamepadName", lcoreGetGamepadName ); + lua_register( L, "RL_IsMouseButtonPressed", lcoreIsMouseButtonPressed ); + lua_register( L, "RL_IsMouseButtonDown", lcoreIsMouseButtonDown ); + lua_register( L, "RL_IsMouseButtonReleased", lcoreIsMouseButtonReleased ); + lua_register( L, "RL_GetMousePosition", lcoreGetMousePosition ); + lua_register( L, "RL_GetMouseDelta", lcoreGetMouseDelta ); + lua_register( L, "RL_GetMouseWheelMove", lcoreGetMouseWheelMove ); + lua_register( L, "RL_SetMousePosition", lcoreSetMousePosition ); + + /* Shapes. */ + /* Drawing. */ + lua_register( L, "RL_DrawPixel", lshapesDrawPixel ); + lua_register( L, "RL_DrawLine", lshapesDrawLine ); + lua_register( L, "RL_DrawCircle", lshapesDrawCircle ); + lua_register( L, "RL_DrawCircleLines", lshapesDrawCircleLines ); + lua_register( L, "RL_DrawRectangle", lshapesDrawRectangle ); + lua_register( L, "RL_DrawRectanglePro", lshapesDrawRectanglePro ); + lua_register( L, "RL_DrawTriangle", lshapesDrawTriangle ); + lua_register( L, "RL_DrawTriangleLines", lshapesDrawTriangleLines ); + /* Collision. */ + lua_register( L, "RL_CheckCollisionRecs", lshapesCheckCollisionRecs ); + lua_register( L, "RL_CheckCollisionCircles", lshapesCheckCollisionCircles ); + lua_register( L, "RL_CheckCollisionCircleRec", lshapesCheckCollisionCircleRec ); + lua_register( L, "RL_CheckCollisionPointRec", lshapesCheckCollisionPointRec ); + lua_register( L, "RL_CheckCollisionPointCircle", lshapesCheckCollisionPointCircle ); + lua_register( L, "RL_CheckCollisionPointTriangle", lshapesCheckCollisionPointTriangle ); + lua_register( L, "RL_CheckCollisionLines", lshapesCheckCollisionLines ); + lua_register( L, "RL_CheckCollisionPointLine", lshapesCheckCollisionPointLine ); + lua_register( L, "RL_GetCollisionRec", lshapesGetCollisionRec ); + + /* Textures. */ + /* File. */ + lua_register( L, "RL_LoadImage", ltexturesLoadImage ); + lua_register( L, "RL_GenImageColor", ltexturesGenImageColor ); + lua_register( L, "RL_UnloadImage", ltexturesUnloadImage ); + lua_register( L, "RL_LoadTexture", ltexturesLoadTexture ); + lua_register( L, "RL_LoadTextureFromImage", ltexturesLoadTextureFromImage ); + lua_register( L, "RL_UnloadTexture", ltexturesUnloadTexture ); + lua_register( L, "RL_LoadRenderTexture", ltexturesLoadRenderTexture ); + lua_register( L, "RL_UnloadRenderTexture", ltexturesUnloadRenderTexture ); + /* Image Drawing. */ + lua_register( L, "RL_ImageClearBackground", ltexturesImageClearBackground ); + lua_register( L, "RL_ImageDrawPixel", ltexturesImageDrawPixel ); + lua_register( L, "RL_ImageDrawLine", ltexturesImageDrawLine ); + lua_register( L, "RL_ImageDrawCircle", ltexturesImageDrawCircle ); + lua_register( L, "RL_ImageDrawRectangle", ltexturesImageDrawRectangle ); + lua_register( L, "RL_ImageDrawRectangleLines", ltexturesImageDrawRectangleLines ); + lua_register( L, "RL_ImageDraw", ltexturesImageDraw ); + lua_register( L, "RL_ImageDrawTextEx", ltexturesImageDrawTextEx ); + /* Texture Drawing. */ + lua_register( L, "RL_DrawTexture", ltexturesDrawTexture ); + lua_register( L, "RL_DrawTextureRec", ltexturesDrawTextureRec ); + lua_register( L, "RL_DrawTextureTiled", ltexturesDrawTextureTiled ); + lua_register( L, "RL_DrawTexturePro", ltexturesDrawTexturePro ); + lua_register( L, "RL_DrawTextureNPatch", ltexturesDrawTextureNPatch ); + lua_register( L, "RL_DrawTexturePoly", ltexturesDrawTexturePoly ); + lua_register( L, "RL_BeginTextureMode", ltexturesBeginTextureMode ); + lua_register( L, "RL_EndTextureMode", ltexturesEndTextureMode ); + lua_register( L, "RL_SetTextureSource", ltexturesSetTextureSource ); + lua_register( L, "RL_GetTextureSource", ltexturesGetTextureSource ); + /* Conf. */ + lua_register( L, "RL_GenTextureMipmaps", ltexturesGenTextureMipmaps ); + lua_register( L, "RL_SetTextureFilter", ltexturesSetTextureFilter ); + lua_register( L, "RL_SetTextureWrap", ltexturesSetTextureWrap ); + lua_register( L, "RL_GetTextureSize", ltexturesGetTextureSize ); + + /* Models. */ + /* Basic. */ + lua_register( L, "RL_DrawLine3D", lmodelsDrawLine3D ); + lua_register( L, "RL_DrawPoint3D", lmodelsDrawPoint3D ); + lua_register( L, "RL_DrawCircle3D", lmodelsDrawCircle3D ); + lua_register( L, "RL_DrawTriangle3D", lmodelsDrawTriangle3D ); + lua_register( L, "RL_DrawCube", lmodelsDrawCube ); + lua_register( L, "RL_DrawCubeWires", lmodelsDrawCubeWires ); + lua_register( L, "RL_DrawCubeTexture", lmodelsDrawCubeTexture ); + lua_register( L, "RL_DrawSphere", lmodelsDrawSphere ); + lua_register( L, "RL_DrawSphereEx", lmodelsDrawSphereEx ); + lua_register( L, "RL_DrawSphereWires", lmodelsDrawSphereWires ); + lua_register( L, "RL_DrawCylinder", lmodelsDrawCylinder ); + lua_register( L, "RL_DrawCylinderEx", lmodelsDrawCylinderEx ); + lua_register( L, "RL_DrawCylinderWires", lmodelsDrawCylinderWires ); + lua_register( L, "RL_DrawCylinderWiresEx", lmodelsDrawCylinderWiresEx ); + lua_register( L, "RL_DrawPlane", lmodelsDrawPlane ); + lua_register( L, "RL_DrawQuad3DTexture", lmodelDrawQuad3DTexture ); + lua_register( L, "RL_DrawRay", lmodelsDrawRay ); + lua_register( L, "RL_DrawGrid", lmodelsDrawGrid ); + /* Mesh. */ + lua_register( L, "RL_GenMeshPoly", lmodelsGenMeshPoly ); + lua_register( L, "RL_GenMeshPlane", lmodelsGenMeshPlane ); + lua_register( L, "RL_GenMeshCube", lmodelsGenMeshCube ); + lua_register( L, "RL_GenMeshSphere", lmodelsGenMeshSphere ); + lua_register( L, "RL_GenMeshCylinder", lmodelsGenMeshCylinder ); + lua_register( L, "RL_GenMeshCone", lmodelsGenMeshCone ); + lua_register( L, "RL_GenMeshTorus", lmodelsGenMeshTorus ); + lua_register( L, "RL_GenMeshKnot", lmodelsGenMeshKnot ); + lua_register( L, "RL_GenMeshHeightmap", lmodelsGenMeshHeightmap ); + lua_register( L, "RL_GenMeshCustom", lmodelsGenMeshCustom ); + lua_register( L, "RL_UnloadMesh", lmodelsUnloadMesh ); + lua_register( L, "RL_DrawMesh", lmodelsDrawMesh ); + lua_register( L, "RL_DrawMeshInstanced", lmodelsDrawMeshInstanced ); + lua_register( L, "RL_SetMeshColor", lmodelsSetMeshColor ); + /* Material. */ + lua_register( L, "RL_LoadMaterialDefault", lmodelsLoadMaterialDefault ); + lua_register( L, "RL_CreateMaterial", lmodelsCreateMaterial ); + lua_register( L, "RL_UnloadMaterial", lmodelsUnloadMaterial ); + lua_register( L, "RL_SetMaterialTexture", lmodelsSetMaterialTexture ); + lua_register( L, "RL_SetMaterialColor", lmodelsSetMaterialColor ); + lua_register( L, "RL_SetMaterialValue", lmodelsSetMaterialValue ); + /* Model. */ + lua_register( L, "RL_LoadModel", lmodelsLoadModel ); + lua_register( L, "RL_LoadModelFromMesh", lmodelsLoadModelFromMesh ); + lua_register( L, "RL_UnloadModel", lmodelsUnloadModel ); + lua_register( L, "RL_DrawModel", lmodelsDrawModel ); + lua_register( L, "RL_DrawModelEx", lmodelsDrawModelEx ); + lua_register( L, "RL_SetModelMaterial", lmodelsSetModelMaterial ); + lua_register( L, "RL_SetModelMeshMaterial", lmodelsSetModelMeshMaterial ); + lua_register( L, "RL_DrawBillboard", lmodelsDrawBillboard ); + lua_register( L, "RL_DrawBillboardRec", lmodelsDrawBillboardRec ); + /* Animations. */ + lua_register( L, "RL_LoadModelAnimations", lmodelsLoadModelAnimations ); + lua_register( L, "RL_UpdateModelAnimation", lmodelsUpdateModelAnimation ); + lua_register( L, "RL_GetModelAnimationBoneCount", lmodelsGetModelAnimationBoneCount ); + lua_register( L, "RL_GetModelAnimationFrameCount", lmodelsGetModelAnimationFrameCount ); + /* Collision. */ + lua_register( L, "RL_CheckCollisionSpheres", lmodelsCheckCollisionSpheres ); + lua_register( L, "RL_CheckCollisionBoxes", lmodelsCheckCollisionBoxes ); + lua_register( L, "RL_CheckCollisionBoxSphere", lmodelsCheckCollisionBoxSphere ); + lua_register( L, "RL_GetRayCollisionSphere", lmodelsGetRayCollisionSphere ); + lua_register( L, "RL_GetRayCollisionBox", lmodelsGetRayCollisionBox ); + lua_register( L, "RL_GetRayCollisionModel", lmodelsGetRayCollisionModel ); + lua_register( L, "RL_GetRayCollisionMesh", lmodelsGetRayCollisionMesh ); + lua_register( L, "RL_GetRayCollisionTriangle", lmodelsGetRayCollisionTriangle ); + lua_register( L, "RL_GetRayCollisionQuad", lmodelsGetRayCollisionQuad ); + + /* Text. */ + /* Loading. */ + lua_register( L, "RL_LoadFont", lmodelsLoadFont ); + /* Drawing. */ + lua_register( L, "RL_DrawText", ltextDrawText ); + + /* Audio. */ + /* Sound. */ + lua_register( L, "RL_LoadSound", laudioLoadSound ); + lua_register( L, "RL_PlaySoundMulti", laudioPlaySoundMulti ); + lua_register( L, "RL_SetSoundVolume", laudioSetSoundVolume ); + lua_register( L, "RL_SetSoundPitch", laudioSetSoundPitch ); + lua_register( L, "RL_UnloadSound", laudioUnloadSound ); + /* Music. */ + lua_register( L, "RL_LoadMusicStream", laudioLoadMusicStream ); + lua_register( L, "RL_PlayMusicStream", laudioPlayMusicStream ); + lua_register( L, "RL_StopMusicStream", laudioStopMusicStream ); + lua_register( L, "RL_PauseMusicStream", laudioPauseMusicStream ); + lua_register( L, "RL_ResumeMusicStream", laudioResumeMusicStream ); + lua_register( L, "RL_IsMusicStreamPlaying", laudioIsMusicStreamPlaying ); + lua_register( L, "RL_SetMusicVolume", laudioSetMusicVolume ); + + /* Math. */ + /* Vector2. */ + lua_register( L, "RL_Vector2Add", lmathVector2Add ); + lua_register( L, "RL_Vector2Subtract", lmathVector2Subtract ); + lua_register( L, "RL_Vector2Multiply", lmathVector2Multiply ); + lua_register( L, "RL_Vector2Length", lmathVector2Length ); + lua_register( L, "RL_Vector2DotProduct", lmathVector2DotProduct ); + lua_register( L, "RL_Vector2Distance", lmathVector2Distance ); + lua_register( L, "RL_Vector2Angle", lmathVector2Angle ); + lua_register( L, "RL_Vector2Normalize", lmathVector2Normalize ); + lua_register( L, "RL_Vector2Lerp", lmathVector2Lerp ); + lua_register( L, "RL_Vector2Reflect", lmathVector2Reflect ); + lua_register( L, "RL_Vector2Rotate", lmathVector2Rotate ); + lua_register( L, "RL_Vector2MoveTowards", lmathVector2MoveTowards ); + /* Vector3. */ + lua_register( L, "RL_Vector3Add", lmathVector3Add ); + lua_register( L, "RL_Vector3Subtract", lmathVector3Subtract ); + lua_register( L, "RL_Vector3Multiply", lmathVector3Multiply ); + lua_register( L, "RL_Vector3CrossProduct", lmathVector3CrossProduct ); + lua_register( L, "RL_Vector3Perpendicular", lmathVector3Perpendicular ); + lua_register( L, "RL_Vector3Length", lmathVector3Length ); + lua_register( L, "RL_Vector3LengthSqr", lmathVector3LengthSqr ); + lua_register( L, "RL_Vector3DotProduct", lmathVector3DotProduct ); + lua_register( L, "RL_Vector3Distance", lmathVector3Distance ); + lua_register( L, "RL_Vector3Normalize", lmathVector3Normalize ); + lua_register( L, "RL_Vector3OrthoNormalize", lmathVector3OrthoNormalize ); + lua_register( L, "RL_Vector3Transform", lmathVector3Transform ); + lua_register( L, "RL_Vector3RotateByQuaternion", lmathVector3RotateByQuaternion ); + lua_register( L, "RL_Vector3Lerp", lmathVector3Lerp ); + lua_register( L, "RL_Vector3Reflect", lmathVector3Reflect ); + /* Matrix. */ + lua_register( L, "RL_MatrixDeterminant", lmathMatrixDeterminant ); + lua_register( L, "RL_MatrixTranspose", lmathMatrixTranspose ); + lua_register( L, "RL_MatrixInvert", lmathMatrixInvert ); + lua_register( L, "RL_MatrixNormalize", lmathMatrixNormalize ); + lua_register( L, "RL_MatrixIdentity", lmathMatrixIdentity ); + lua_register( L, "RL_MatrixAdd", lmathMatrixAdd ); + lua_register( L, "RL_MatrixSubtract", lmathMatrixSubtract ); + lua_register( L, "RL_MatrixMultiply", lmathMatrixMultiply ); + lua_register( L, "RL_MatrixTranslate", lmathMatrixTranslate ); + lua_register( L, "RL_MatrixRotate", lmathMatrixRotate ); + lua_register( L, "RL_MatrixScale", lmathMatrixScale ); + lua_register( L, "RL_MatrixFrustum", lmathMatrixFrustum ); + lua_register( L, "RL_MatrixPerspective", lmathMatrixPerspective ); + lua_register( L, "RL_MatrixOrtho", lmathMatrixOrtho ); + lua_register( L, "RL_MatrixLookAt", lmathMatrixLookAt ); + + /* Gui. */ + /* Global. */ + lua_register( L, "RL_GuiEnable", lguiGuiEnable ); + lua_register( L, "RL_GuiDisable", lguiGuiDisable ); + lua_register( L, "RL_GuiLock", lguiGuiLock ); + lua_register( L, "RL_GuiUnlock", lguiGuiUnlock ); + /* Font. */ + lua_register( L, "RL_GuiSetFont", lguiGuiSetFont ); + /* Container. */ + lua_register( L, "RL_GuiWindowBox", lguiGuiWindowBox ); + lua_register( L, "RL_GuiPanel", lguiGuiPanel ); + lua_register( L, "RL_GuiScrollPanel", lguiGuiScrollPanel ); + /* Basic. */ + lua_register( L, "RL_GuiLabel", lguiGuiLabel ); + lua_register( L, "RL_GuiButton", lguiGuiButton ); + lua_register( L, "RL_GuiToggle", lguiGuiToggle ); + lua_register( L, "RL_GuiCheckBox", lguiGuiCheckBox ); + lua_register( L, "RL_GuiTextBox", lguiGuiTextBox ); + lua_register( L, "RL_GuiTextBoxMulti", lguiGuiTextBoxMulti ); + lua_register( L, "RL_GuiSpinner", lguiGuiSpinner ); + lua_register( L, "RL_GuiValueBox", lguiGuiValueBox ); + lua_register( L, "RL_GuiSlider", lguiGuiSlider ); + lua_register( L, "RL_GuiSliderBar", lguiGuiSliderBar ); + lua_register( L, "RL_GuiProgressBar", lguiGuiProgressBar ); + lua_register( L, "RL_GuiScrollBar", lguiGuiScrollBar ); + lua_register( L, "RL_GuiDropdownBox", lguiGuiDropdownBox ); +} + +/* Lua util functions. */ + +Color uluaGetColor( lua_State *L ) { + Color color = { 0, 0, 0, 255 }; + + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Error. Wrong color value. Returning { 0, 0, 0, 255 }" ); + return color; + } + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_isnumber( L, -1 ) ) { + switch ( i ) { + case 0: + color.r = (uint8_t)lua_tointeger( L, -1 ); + break; + case 1: + color.g = (uint8_t)lua_tointeger( L, -1 ); + break; + case 2: + color.b = (uint8_t)lua_tointeger( L, -1 ); + break; + case 3: + color.a = (uint8_t)lua_tointeger( L, -1 ); + break; + default: + break; + } + } + i++; + lua_pop( L, 1 ); + } + return color; +} + +Vector2 uluaGetVector2( lua_State *L ) { + Vector2 vector = { 0.0f, 0.0f }; + + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Error. Wrong vector value. Returning { 0, 0 }" ); + return vector; + } + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_isnumber( L, -1 ) ) { + switch ( i ) { + case 0: + vector.x = lua_tonumber( L, -1 ); + break; + case 1: + vector.y = lua_tonumber( L, -1 ); + break; + default: + break; + } + } + i++; + lua_pop( L, 1 ); + } + return vector; +} + +Vector3 uluaGetVector3( lua_State *L ) { + Vector3 vector = { 0.0f, 0.0f, 0.0f }; + + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Error. Wrong vector value. Returning { 0, 0, 0 }" ); + return vector; + } + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_isnumber( L, -1 ) ) { + switch ( i ) { + case 0: + vector.x = lua_tonumber( L, -1 ); + break; + case 1: + vector.y = lua_tonumber( L, -1 ); + break; + case 2: + vector.z = lua_tonumber( L, -1 ); + break; + default: + break; + } + } + i++; + lua_pop( L, 1 ); + } + return vector; +} + +Rectangle uluaGetRectangle( lua_State *L ) { + Rectangle rect = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Error. Wrong rectangle value. Returning { 0, 0, 0, 0 }" ); + return rect; + } + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_isnumber( L, -1 ) ) { + switch ( i ) { + case 0: + rect.x = lua_tonumber( L, -1 ); + break; + case 1: + rect.y = lua_tonumber( L, -1 ); + break; + case 2: + rect.width = lua_tonumber( L, -1 ); + break; + case 3: + rect.height = lua_tonumber( L, -1 ); + break; + default: + break; + } + } + i++; + lua_pop( L, 1 ); + } + return rect; +} + +Quaternion uluaGetQuaternion( lua_State *L ) { + Quaternion quaternion = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Error. Wrong quaternion value. Returning { 0, 0, 0, 0 }" ); + return quaternion; + } + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_isnumber( L, -1 ) ) { + switch ( i ) { + case 0: + quaternion.x = lua_tonumber( L, -1 ); + break; + case 1: + quaternion.y = lua_tonumber( L, -1 ); + break; + case 2: + quaternion.z = lua_tonumber( L, -1 ); + break; + case 3: + quaternion.w = lua_tonumber( L, -1 ); + break; + default: + break; + } + } + i++; + lua_pop( L, 1 ); + } + return quaternion; +} + +Matrix uluaGetMatrix( lua_State *L ) { + Matrix matrix = { 0.0f }; + float m[4][4]; + + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Error. Wrong matrix value. Returning MatrixIdentity." ); + return MatrixIdentity(); + } + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_istable( L, -1 ) ) { + int t2 = lua_gettop( L ), j = 0; + lua_pushnil( L ); + + while ( lua_next( L, t2 ) != 0 ) { + if ( lua_isnumber( L, -1 ) ) { + m[i][j] = lua_tonumber( L, -1 ); + } + j++; + lua_pop( L, 1 ); + } + } + i++; + lua_pop( L, 1 ); + } + matrix.m0 = m[0][0]; matrix.m1 = m[0][1]; matrix.m2 = m[0][2]; matrix.m3 = m[0][3]; + matrix.m4 = m[1][0]; matrix.m5 = m[1][1]; matrix.m6 = m[1][2]; matrix.m7 = m[1][3]; + matrix.m8 = m[2][0]; matrix.m9 = m[2][1]; matrix.m10 = m[2][2]; matrix.m11 = m[2][3]; + matrix.m12 = m[3][0]; matrix.m13 = m[3][1]; matrix.m14 = m[3][2]; matrix.m15 = m[3][3]; + + return matrix; +} + +BoundingBox uluaGetBoundingBox( lua_State *L ) { + BoundingBox box = { .min = { 0.0, 0.0, 0.0 }, .max = { 0.0, 0.0, 0.0 } }; + + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Error. Wrong boundingbox value. Returning { min{ 0, 0, 0 }, max{ 0, 0, 0 } }." ); + return box; + } + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_istable( L, -1 ) ) { + switch ( i ) { + case 0: + box.min = uluaGetVector3( L ); + break; + case 1: + box.max = uluaGetVector3( L ); + break; + default: + break; + } + } + i++; + lua_pop( L, 1 ); + } + + return box; +} + +Ray uluaGetRay( lua_State *L ) { + Ray ray = { .position = { 0.0, 0.0, 0.0 }, .direction = { 0.0, 0.0, 0.0 } }; + + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Error. Wrong ray value. Returning { position{ 0, 0, 0 }, direction{ 0, 0, 0 } }." ); + return ray; + } + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_istable( L, -1 ) ) { + switch ( i ) { + case 0: + ray.position = uluaGetVector3( L ); + break; + case 1: + ray.direction = uluaGetVector3( L ); + break; + default: + break; + } + } + i++; + lua_pop( L, 1 ); + } + + return ray; +} + +NPatchInfo uluaGetNPatchInfo( lua_State *L ) { + NPatchInfo npatch = { .source = { 0.0, 0.0, 0.0, 0.0 }, .left = 0, .top = 0, .right = 0, .bottom = 0, .layout = NPATCH_NINE_PATCH }; + + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Error. Wrong ray value. Returning { source = { 0.0, 0.0, 0.0, 0.0 }, left = 0, top = 0, right = 0, bottom = 0, layout = NPATCH_NINE_PATCH }." ); + return npatch; + } + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + switch ( i ) { + case 0: + npatch.source = uluaGetRectangle( L ); + break; + case 1: + npatch.left = lua_tointeger( L, -1 ); + break; + case 2: + npatch.top = lua_tointeger( L, -1 ); + break; + case 3: + npatch.right = lua_tointeger( L, -1 ); + break; + case 4: + npatch.bottom = lua_tointeger( L, -1 ); + break; + case 5: + npatch.layout = lua_tointeger( L, -1 ); + break; + default: + break; + } + i++; + lua_pop( L, 1 ); + } + + return npatch; +} + +void uluaPushColor( lua_State *L, Color color ) { + lua_createtable( L, 3, 0 ); + lua_pushnumber( L, color.r ); + lua_rawseti( L, -2, 1 ); + lua_pushnumber( L, color.g ); + lua_rawseti( L, -2, 2 ); + lua_pushnumber( L, color.b ); + lua_rawseti( L, -2, 3 ); + lua_pushnumber( L, color.a ); + lua_rawseti( L, -2, 4 ); +} + +void uluaPushVector2( lua_State *L, Vector2 vector ) { + lua_createtable( L, 2, 0 ); + lua_pushnumber( L, vector.x ); + lua_rawseti( L, -2, 1 ); + lua_pushnumber( L, vector.y ); + lua_rawseti( L, -2, 2 ); +} + +void uluaPushVector3( lua_State *L, Vector3 vector ) { + lua_createtable( L, 3, 0 ); + lua_pushnumber( L, vector.x ); + lua_rawseti( L, -2, 1 ); + lua_pushnumber( L, vector.y ); + lua_rawseti( L, -2, 2 ); + lua_pushnumber( L, vector.z ); + lua_rawseti( L, -2, 3 ); +} + +void uluaPushRectangle( lua_State *L, Rectangle rect ) { + lua_createtable( L, 4, 0 ); + lua_pushnumber( L, rect.x ); + lua_rawseti( L, -2, 1 ); + lua_pushnumber( L, rect.y ); + lua_rawseti( L, -2, 2 ); + lua_pushnumber( L, rect.width ); + lua_rawseti( L, -2, 3 ); + lua_pushnumber( L, rect.height ); + lua_rawseti( L, -2, 4 ); +} + +void uluaPushMatrix( lua_State *L, Matrix matrix ) { + lua_createtable( L, 4, 0 ); + + lua_createtable( L, 4, 0 ); + lua_pushnumber( L, matrix.m0 ); + lua_rawseti( L, -2, 1 ); + lua_pushnumber( L, matrix.m1 ); + lua_rawseti( L, -2, 2 ); + lua_pushnumber( L, matrix.m2 ); + lua_rawseti( L, -2, 3 ); + lua_pushnumber( L, matrix.m3 ); + lua_rawseti( L, -2, 4 ); + lua_rawseti( L, -2, 1 ); + + lua_createtable( L, 4, 0 ); + lua_pushnumber( L, matrix.m4 ); + lua_rawseti( L, -2, 1 ); + lua_pushnumber( L, matrix.m5 ); + lua_rawseti( L, -2, 2 ); + lua_pushnumber( L, matrix.m6 ); + lua_rawseti( L, -2, 3 ); + lua_pushnumber( L, matrix.m7 ); + lua_rawseti( L, -2, 4 ); + lua_rawseti( L, -2, 2 ); + + lua_createtable( L, 4, 0 ); + lua_pushnumber( L, matrix.m8 ); + lua_rawseti( L, -2, 1 ); + lua_pushnumber( L, matrix.m9 ); + lua_rawseti( L, -2, 2 ); + lua_pushnumber( L, matrix.m10 ); + lua_rawseti( L, -2, 3 ); + lua_pushnumber( L, matrix.m11 ); + lua_rawseti( L, -2, 4 ); + lua_rawseti( L, -2, 3 ); + + lua_createtable( L, 4, 0 ); + lua_pushnumber( L, matrix.m12 ); + lua_rawseti( L, -2, 1 ); + lua_pushnumber( L, matrix.m13 ); + lua_rawseti( L, -2, 2 ); + lua_pushnumber( L, matrix.m14 ); + lua_rawseti( L, -2, 3 ); + lua_pushnumber( L, matrix.m15 ); + lua_rawseti( L, -2, 4 ); + lua_rawseti( L, -2, 4 ); +} + +void uluaPushRayCollision( lua_State *L, RayCollision rayCol ) { + lua_createtable( L, 4, 0 ); + lua_pushboolean( L, rayCol.hit ); + lua_setfield( L, -2, "hit" ); + lua_pushnumber( L, rayCol.distance ); + lua_setfield( L, -2, "distance" ); + uluaPushVector3( L, rayCol.point ); + lua_setfield( L, -2, "point" ); + uluaPushVector3( L, rayCol.normal ); + lua_setfield( L, -2, "normal" ); +} + +int uluaGetTableLen( lua_State *L ) { + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + i++; + lua_pop( L, 1 ); + } + return i; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..e9d5db2 --- /dev/null +++ b/src/main.c @@ -0,0 +1,37 @@ +#include "main.h" +#include "state.h" +#include "lua_core.h" + +int main( int argn, const char **argc ) { + char exePath[ STRING_LEN ] = { '\0' }; + + if ( 1 < argn ) { + if ( strcmp( argc[1], "--version" ) == 0 || strcmp( argc[1], "-v" ) == 0 ) { + printf( "ReiLua %d.%d\n", VERSION_MAJOR, VERSION_MINOR ); + + return 1; + } + else{ + sprintf( exePath, "%s/%s", GetWorkingDirectory(), argc[1] ); + } + } + else { + sprintf( exePath, "%s/", GetWorkingDirectory() ); + } + + stateInit( exePath ); + + while ( state->run ) { + if ( WindowShouldClose() ) { + state->run = false; + } + if ( IsAudioDeviceReady() ) { + UpdateMusicStream( state->music ); + } + luaCallProcess(); + luaCallDraw(); + } + stateFree(); + + return 1; +} diff --git a/src/models.c b/src/models.c new file mode 100644 index 0000000..17683b8 --- /dev/null +++ b/src/models.c @@ -0,0 +1,2113 @@ +#include "main.h" +#include "state.h" +#include "models.h" +#include "lua_core.h" +#include "rmath.h" +#include "textures.h" +#include "core.h" + +static void checkMeshRealloc( int i ) { + if ( i == state->meshCount ) { + state->meshCount++; + } + + if ( state->meshCount == state->meshAlloc ) { + state->meshAlloc += ALLOC_PAGE_SIZE; + state->meshes = realloc( state->meshes, state->meshAlloc * sizeof( Mesh* ) ); + + for ( i = state->meshCount; i < state->meshAlloc; i++ ) { + state->meshes[i] = NULL; + } + } +} + +static void checkMaterialRealloc( int i ) { + if ( i == state->materialCount ) { + state->materialCount++; + } + + if ( state->materialCount == state->materialAlloc ) { + state->materialAlloc += ALLOC_PAGE_SIZE; + state->materials = realloc( state->materials, state->materialAlloc * sizeof( Material* ) ); + + for ( i = state->materialCount; i < state->materialAlloc; i++ ) { + state->materials[i] = NULL; + } + } +} + +static void checkModelRealloc( int i ) { + if ( i == state->modelCount ) { + state->modelCount++; + } + + if ( state->modelCount == state->modelAlloc ) { + state->modelAlloc += ALLOC_PAGE_SIZE; + state->models = realloc( state->models, state->modelAlloc * sizeof( Model* ) ); + + for ( i = state->modelCount; i < state->modelAlloc; i++ ) { + state->models[i] = NULL; + } + } +} + +static void checkAnimationRealloc( int i ) { + if ( i == state->animationCount ) { + state->animationCount++; + } + + if ( state->animationCount == state->animationAlloc ) { + state->animationAlloc += ALLOC_PAGE_SIZE; + state->animations = realloc( state->animations, state->animationAlloc * sizeof( ModelAnimations* ) ); + + for ( i = state->animationCount; i < state->animationAlloc; i++ ) { + state->animations[i] = NULL; + } + } +} + +static bool validMesh( size_t id ) { + if ( id < 0 || state->meshCount < id || state->meshes[ id ] == NULL ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid mesh", id ); + return false; + } + else { + return true; + } +} + +static bool validMaterial( size_t id ) { + if ( id < 0 || state->materialCount < id || state->materials[ id ] == NULL ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid material", id ); + return false; + } + else { + return true; + } +} + +static bool validModel( size_t id ) { + if ( id < 0 || state->modelCount < id || state->models[ id ] == NULL ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid model", id ); + return false; + } + else { + return true; + } +} + +static bool validAnimation( size_t id ) { + if ( id < 0 || state->animationCount < id || state->animations[ id ] == NULL ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid animation", id ); + return false; + } + else { + return true; + } +} + +/* +## Models - Basic +*/ + +/* +> success = RL_DrawLine3D( Vector3 startPos, Vector3 endPos, Color color ) + +Draw a line in 3D world space + +- Failure return false +- Success return true +*/ +int lmodelsDrawLine3D( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawLine3D( Vector3 startPos, Vector3 endPos, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector3 endPos = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 startPos = uluaGetVector3( L ); + + DrawLine3D( startPos, endPos, color); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawPoint3D( Vector3 position, Color color ) + +Draw a point in 3D space, actually a small line + +- Failure return false +- Success return true +*/ +int lmodelsDrawPoint3D( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawPoint3D( Vector3 position, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector3 position = uluaGetVector3( L ); + + DrawPoint3D( position, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawCircle3D( Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color ) + +Draw a circle in 3D world space + +- Failure return false +- Success return true +*/ +int lmodelsDrawCircle3D( lua_State *L ) { + if ( !lua_istable( L, -5 ) || !lua_isnumber( L, -4 ) || !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawCircle3D( Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + float rotationAngle = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 rotationAxis = uluaGetVector3( L ); + lua_pop( L, 1 ); + float radius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 center = uluaGetVector3( L ); + + DrawCircle3D( center, radius, rotationAxis, rotationAngle, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawTriangle3D( Vector3 v1, Vector3 v2, Vector3 v3, Color color ) + +Draw a color-filled triangle ( Vertex in counter-clockwise order! ) + +- Failure return false +- Success return true +*/ +int lmodelsDrawTriangle3D( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawTriangle3D( Vector3 v1, Vector3 v2, Vector3 v3, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector3 v3 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 v2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 v1 = uluaGetVector3( L ); + + DrawTriangle3D( v1, v2, v3, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawCube( Vector3 position, Vector3 size, Color color ) + +Draw cube + +- Failure return false +- Success return true +*/ +int lmodelsDrawCube( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawCube( Vector3 position, Vector3 size, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector3 size = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 pos = uluaGetVector3( L ); + + DrawCubeV( pos, size, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawCubeWires( Vector3 position, Vector3 size, Color color ) + +Draw cube wires + +- Failure return false +- Success return true +*/ +int lmodelsDrawCubeWires( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawCubeWires( Vector3 position, Vector3 size, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector3 size = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 pos = uluaGetVector3( L ); + + DrawCubeWiresV( pos, size, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawCubeTexture( Texture2D texture, Vector3 position, Vector3 size, Color color ) + +Draw cube textured + +- Failure return false +- Success return true +*/ +int lmodelsDrawCubeTexture( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawCubeTexture( Texture2D texture, Vector3 position, Vector3 size, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector3 size = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 pos = uluaGetVector3( L ); + lua_pop( L, 1 ); + size_t texId = lua_tointeger( L, -1 ); + + if ( !validSourceTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + DrawCubeTexture( *texturesGetSourceTexture( texId ), pos, size.x, size.y, size.z, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawSphere( Vector3 centerPos, float radius, Color color ) + +Draw sphere + +- Failure return false +- Success return true +*/ +int lmodelsDrawSphere( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawSphere( Vector3 centerPos, float radius, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + float radius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 centerPos = uluaGetVector3( L ); + + DrawSphere( centerPos, radius, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawSphereEx( Vector3 centerPos, float radius, int rings, int slices, Color color ) + +Draw sphere with extended parameters + +- Failure return false +- Success return true +*/ +int lmodelsDrawSphereEx( lua_State *L ) { + if ( !lua_istable( L, -5 ) || !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawSphereEx( Vector3 centerPos, float radius, int rings, int slices, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + int slices = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + int rings = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + float radius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 centerPos = uluaGetVector3( L ); + + DrawSphereEx( centerPos, radius, rings, slices, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawSphereWires( Vector3 centerPos, float radius, int rings, int slices, Color color ) + +Draw sphere wires + +- Failure return false +- Success return true +*/ +int lmodelsDrawSphereWires( lua_State *L ) { + if ( !lua_istable( L, -5 ) || !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawSphereWires( Vector3 centerPos, float radius, int rings, int slices, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + int slices = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + int rings = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + float radius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 centerPos = uluaGetVector3( L ); + + DrawSphereWires( centerPos, radius, rings, slices, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawCylinder( Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color ) + +Draw a cylinder/cone + +- Failure return false +- Success return true +*/ +int lmodelsDrawCylinder( lua_State *L ) { + if ( !lua_istable( L, -6 ) || !lua_isnumber( L, -5 ) || !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawCylinder( Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + int slices = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + float height = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float radiusBottom = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float radiusTop = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 position = uluaGetVector3( L ); + + DrawCylinder( position, radiusTop, radiusBottom, height, slices, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawCylinderEx( Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color ) + +Draw a cylinder with base at startPos and top at endPos + +- Failure return false +- Success return true +*/ +int lmodelsDrawCylinderEx( lua_State *L ) { + if ( !lua_istable( L, -6 ) || !lua_istable( L, -5 ) || !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawCylinderEx( Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + int sides = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + float endRadius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float startRadius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 endPos = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 startPos = uluaGetVector3( L ); + + DrawCylinderEx( startPos, endPos, startRadius, endRadius, sides, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawCylinderWires( Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color ) + +Draw a cylinder/cone wires + +- Failure return false +- Success return true +*/ +int lmodelsDrawCylinderWires( lua_State *L ) { + if ( !lua_istable( L, -6 ) || !lua_isnumber( L, -5 ) || !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawCylinderWires( Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + int slices = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + float height = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float radiusBottom = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float radiusTop = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 position = uluaGetVector3( L ); + + DrawCylinderWires( position, radiusTop, radiusBottom, height, slices, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawCylinderWiresEx( Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color ) + +Draw a cylinder wires with base at startPos and top at endPos + +- Failure return false +- Success return true +*/ +int lmodelsDrawCylinderWiresEx( lua_State *L ) { + if ( !lua_istable( L, -6 ) || !lua_istable( L, -5 ) || !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawCylinderWiresEx( Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + int sides = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + float endRadius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float startRadius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 endPos = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 startPos = uluaGetVector3( L ); + + DrawCylinderWiresEx( startPos, endPos, startRadius, endRadius, sides, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawPlane( Vector3 centerPos, Vector2 size, Color color ) + +Draw a plane XZ + +- Failure return false +- Success return true +*/ +int lmodelsDrawPlane( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawPlane( Vector3 centerPos, Vector2 size, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector2 size = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector3 centerPos = uluaGetVector3( L ); + + DrawPlane( centerPos, size, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawQuad3DTexture( texture, Vector3{} vertices, Vector2{} texCoords, Color color ) + +Draw 3D quad texture using vertices and texture coordinates. Texture coordinates opengl style 0.0 - 1.0. +Note! Could be replaced something like "DrawPlaneTextureRec" + +- Failure return false +- Success return true +*/ +int lmodelDrawQuad3DTexture( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawQuad3DTexture( texture, Vector3{} vertices, Vector2{} texCoords, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + + /* TexCoords. */ + Vector2 texcoords[4] = { 0 }; + + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_istable( L, -1 ) && i < 4 ) { + texcoords[i] = uluaGetVector2( L ); + } + i++; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + + /* Vertices. */ + Vector3 vertices[4] = { 0 }; + + t = lua_gettop( L ); + i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_istable( L, -1 ) && i < 4 ) { + vertices[i] = uluaGetVector3( L ); + } + i++; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + size_t texId = lua_tointeger( L, -1 ); + + if ( !validSourceTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + // Draw. + rlCheckRenderBatchLimit( 4 ); + rlSetTexture( texturesGetSourceTexture( texId )->id ); + + //TODO Normals. maybe something like Vector3Normalize(Vector3CrossProduct(Vector3Subtract(vB, vA), Vector3Subtract(vC, vA))); + + rlBegin( RL_QUADS ); + rlColor4ub( color.r, color.g, color.b, color.a ); + + for ( i = 0; i < 4; ++i ) { + rlTexCoord2f( texcoords[i].x, texcoords[i].y ); + rlVertex3f( vertices[i].x, vertices[i].y, vertices[i].z ); + } + rlEnd(); + rlSetTexture( 0 ); + + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawRay( Ray ray, Color color ) + +Draw a ray line + +- Failure return false +- Success return true +*/ +int lmodelsDrawRay( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawRay( Ray ray, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Ray ray = uluaGetRay( L ); + + DrawRay( ray, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawGrid( int slices, float spacing ) + +Draw a grid ( Centered at ( 0, 0, 0 ) ) + +- Failure return false +- Success return true +*/ +int lmodelsDrawGrid( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawGrid( int slices, float spacing )" ); + lua_pushboolean( L, false ); + return 1; + } + DrawGrid( lua_tointeger( L, -2 ), lua_tonumber( L, -1 ) ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Models - Mesh +*/ + +/* +> mesh = RL_GenMeshPoly( int sides, float radius ) + +Generate polygonal mesh + +- Failure return -1 +- Success return int +*/ +int lmodelsGenMeshPoly( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenMeshPoly( int sides, float radius )" ); + lua_pushinteger( L, -1 ); + return 1; + } + float radius = lua_tonumber( L, -1 ); + int sides = lua_tointeger( L, -2 ); + int i = 0; + + for ( i = 0; i < state->meshCount; i++ ) { + if ( state->meshes[i] == NULL ) { + break; + } + } + state->meshes[i] = malloc( sizeof( Mesh ) ); + *state->meshes[i] = GenMeshPoly( sides, radius ); + lua_pushinteger( L, i ); + checkMeshRealloc( i ); + + return 1; +} + +/* +> mesh = RL_GenMeshPlane( float width, float length, int resX, int resZ ) + +Generate plane mesh ( With subdivisions ) + +- Failure return -1 +- Success return int +*/ +int lmodelsGenMeshPlane( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenMeshPlane( float width, float length, int resX, int resZ )" ); + lua_pushinteger( L, -1 ); + return 1; + } + int resZ = lua_tointeger( L, -1 ); + int resX = lua_tointeger( L, -2 ); + float length = lua_tonumber( L, -3 ); + float width = lua_tonumber( L, -4 ); + int i = 0; + + for ( i = 0; i < state->meshCount; i++ ) { + if ( state->meshes[i] == NULL ) { + break; + } + } + state->meshes[i] = malloc( sizeof( Mesh ) ); + *state->meshes[i] = GenMeshPlane( width, length, resX, resZ ); + lua_pushinteger( L, i ); + checkMeshRealloc( i ); + + return 1; +} + +/* +> mesh = RL_GenMeshCube( Vector3 size ) + +Generate cuboid mesh + +- Failure return -1 +- Success return int +*/ +int lmodelsGenMeshCube( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenMeshCube( Vector3 size )" ); + lua_pushinteger( L, -1 ); + return 1; + } + Vector3 size = uluaGetVector3( L ); + int i = 0; + + for ( i = 0; i < state->meshCount; i++ ) { + if ( state->meshes[i] == NULL ) { + break; + } + } + state->meshes[i] = malloc( sizeof( Mesh ) ); + *state->meshes[i] = GenMeshCube( size.x, size.y, size.z ); + lua_pushinteger( L, i ); + checkMeshRealloc( i ); + + return 1; +} + +/* +> mesh = RL_GenMeshSphere( float radius, int rings, int slices ) + +Generate sphere mesh ( Standard sphere ) + +- Failure return -1 +- Success return int +*/ +int lmodelsGenMeshSphere( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenMeshSphere( float radius, int rings, int slices )" ); + lua_pushinteger( L, -1 ); + return 1; + } + int slices = lua_tointeger( L, -1 ); + int rings = lua_tointeger( L, -2 ); + float radius = lua_tonumber( L, -3 ); + int i = 0; + + for ( i = 0; i < state->meshCount; i++ ) { + if ( state->meshes[i] == NULL ) { + break; + } + } + state->meshes[i] = malloc( sizeof( Mesh ) ); + *state->meshes[i] = GenMeshSphere( radius, rings, slices ); + lua_pushinteger( L, i ); + checkMeshRealloc( i ); + + return 1; +} + +/* +> mesh = RL_GenMeshCylinder( float radius, float height, int slices ) + +Generate cylinder mesh + +- Failure return -1 +- Success return int +*/ +int lmodelsGenMeshCylinder( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenMeshCylinder( float radius, float height, int slices )" ); + lua_pushinteger( L, -1 ); + return 1; + } + int slices = lua_tointeger( L, -1 ); + float height = lua_tonumber( L, -2 ); + float radius = lua_tonumber( L, -3 ); + int i = 0; + + for ( i = 0; i < state->meshCount; i++ ) { + if ( state->meshes[i] == NULL ) { + break; + } + } + state->meshes[i] = malloc( sizeof( Mesh ) ); + *state->meshes[i] = GenMeshCylinder( radius, height, slices); + lua_pushinteger( L, i ); + checkMeshRealloc( i ); + + return 1; +} + +/* +> mesh = RL_GenMeshCone( float radius, float height, int slices ) + +Generate cone/pyramid mesh + +- Failure return -1 +- Success return int +*/ +int lmodelsGenMeshCone( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenMeshCone( float radius, float height, int slices )" ); + lua_pushinteger( L, -1 ); + return 1; + } + int slices = lua_tointeger( L, -1 ); + float height = lua_tonumber( L, -2 ); + float radius = lua_tonumber( L, -3 ); + int i = 0; + + for ( i = 0; i < state->meshCount; i++ ) { + if ( state->meshes[i] == NULL ) { + break; + } + } + state->meshes[i] = malloc( sizeof( Mesh ) ); + *state->meshes[i] = GenMeshCone( radius, height, slices); + lua_pushinteger( L, i ); + checkMeshRealloc( i ); + + return 1; +} + +/* +> mesh = RL_GenMeshTorus( float radius, float size, int radSeg, int sides ) + +Generate torus mesh + +- Failure return -1 +- Success return int +*/ +int lmodelsGenMeshTorus( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenMeshTorus( float radius, float size, int radSeg, int sides )" ); + lua_pushinteger( L, -1 ); + return 1; + } + int sides = lua_tointeger( L, -1 ); + int radSeg = lua_tointeger( L, -2 ); + float size = lua_tonumber( L, -3 ); + float radius = lua_tonumber( L, -4 ); + int i = 0; + + for ( i = 0; i < state->meshCount; i++ ) { + if ( state->meshes[i] == NULL ) { + break; + } + } + state->meshes[i] = malloc( sizeof( Mesh ) ); + *state->meshes[i] = GenMeshTorus( radius, size, radSeg, sides ); + lua_pushinteger( L, i ); + checkMeshRealloc( i ); + + return 1; +} + +/* +> mesh = RL_GenMeshKnot( float radius, float size, int radSeg, int sides ) + +Generate torus mesh + +- Failure return -1 +- Success return int +*/ +int lmodelsGenMeshKnot( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenMeshKnot( float radius, float size, int radSeg, int sides )" ); + lua_pushinteger( L, -1 ); + return 1; + } + int sides = lua_tointeger( L, -1 ); + int radSeg = lua_tointeger( L, -2 ); + float size = lua_tonumber( L, -3 ); + float radius = lua_tonumber( L, -4 ); + int i = 0; + + for ( i = 0; i < state->meshCount; i++ ) { + if ( state->meshes[i] == NULL ) { + break; + } + } + state->meshes[i] = malloc( sizeof( Mesh ) ); + *state->meshes[i] = GenMeshKnot( radius, size, radSeg, sides ); + lua_pushinteger( L, i ); + checkMeshRealloc( i ); + + return 1; +} + +/* +> mesh = RL_GenMeshHeightmap( Image heightmap, Vector3 size ) + +Generate heightmap mesh from image data + +- Failure return -1 +- Success return int +*/ +int lmodelsGenMeshHeightmap( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenMeshHeightmap( Image heightmap, Vector3 size )" ); + lua_pushinteger( L, -1 ); + return 1; + } + Vector3 size = uluaGetVector3( L ); + lua_pop( L, 1 ); + Image *heightmap = state->images[ lua_tointeger( L, -1 ) ]; + int i = 0; + + for ( i = 0; i < state->meshCount; i++ ) { + if ( state->meshes[i] == NULL ) { + break; + } + } + state->meshes[i] = malloc( sizeof( Mesh ) ); + *state->meshes[i] = GenMeshHeightmap( *heightmap, size ); + lua_pushinteger( L, i ); + checkMeshRealloc( i ); + + return 1; +} + +/* +> mesh = RL_GenMeshCustom( Vector3{} vertices, Vector2{} texCoords, Vector3{} normals ) + +Generate custom mesh + +- Failure return -1 +- Success return int +*/ +int lmodelsGenMeshCustom( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenMeshCustom( Vector3{} vertices, Vector2{} texCoords, Vector3{} normals )" ); + lua_pushinteger( L, -1 ); + return 1; + } + Mesh mesh = { 0 }; + size_t len = uluaGetTableLen( L ); + + mesh.vertexCount = len * 3; + mesh.triangleCount = len; + + mesh.vertices = (float*)MemAlloc( mesh.vertexCount * 3 * sizeof(float) ); + mesh.texcoords = (float*)MemAlloc( mesh.vertexCount * 2 * sizeof(float) ); + mesh.normals = (float*)MemAlloc( mesh.vertexCount * 3 * sizeof(float) ); + mesh.colors = (unsigned char*)MemAlloc( mesh.vertexCount * 4 * sizeof(unsigned char) ); + + /* Normals. */ + + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_istable( L, -1 ) ) { + Vector3 vec = uluaGetVector3( L ); + mesh.normals[(i*3)+0] = vec.x; + mesh.normals[(i*3)+1] = vec.y; + mesh.normals[(i*3)+2] = vec.z; + } + i++; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + + /* TexCoords. */ + + t = lua_gettop( L ); + i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_istable( L, -1 ) ) { + Vector2 vec = uluaGetVector2( L ); + mesh.texcoords[(i*2)+0] = vec.x; + mesh.texcoords[(i*2)+1] = vec.y; + } + i++; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + + /* Vertices. */ + + t = lua_gettop( L ); + i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_istable( L, -1 ) ) { + Vector3 vec = uluaGetVector3( L ); + mesh.vertices[(i*3)+0] = vec.x; + mesh.vertices[(i*3)+1] = vec.y; + mesh.vertices[(i*3)+2] = vec.z; + + mesh.colors[(i*4)+0] = (unsigned char)255; + mesh.colors[(i*4)+1] = (unsigned char)255; + mesh.colors[(i*4)+2] = (unsigned char)255; + mesh.colors[(i*4)+3] = (unsigned char)255; + } + i++; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + + UploadMesh( &mesh, false ); + + int j = 0; + + for ( j = 0; j < state->meshCount; j++ ) { + if ( state->meshes[j] == NULL ) { + break; + } + } + state->meshes[j] = malloc( sizeof( Mesh ) ); + *state->meshes[j] = mesh; + lua_pushinteger( L, j ); + checkMeshRealloc( j ); + + return 1; +} + +/* +> success = RL_UnloadMesh( Mesh mesh ) + +Unload mesh data from CPU and GPU + +- Failure return false +- Success return true +*/ +int lmodelsUnloadMesh( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_UnloadMesh( Mesh mesh )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t id = lua_tointeger( L, -1 ); + + if ( !validMesh( id ) ) { + lua_pushboolean( L, false ); + return 1; + } + UnloadMesh( *state->meshes[ id ] ); + state->meshes[ id ] = NULL; + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawMesh( Mesh mesh, Material material, Matrix transform ) + +Draw a 3d mesh with material and transform + +- Failure return false +- Success return true +*/ +int lmodelsDrawMesh( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawMesh( Mesh mesh, Material material, Matrix transform )" ); + lua_pushboolean( L, false ); + return 1; + } + Matrix matrix = uluaGetMatrix( L ); + lua_pop( L, 1 ); + size_t materialId = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + size_t meshId = lua_tointeger( L, -1 ); + + if ( !validMesh( meshId ) || !validMaterial( materialId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + DrawMesh( *state->meshes[ meshId ], *state->materials[ materialId ], matrix ); + lua_pushboolean( L, true ); + + return 1; +} + +/* TODO Needs shader to work. Test it when we have shaders. */ +/* +> success = RL_DrawMeshInstanced( Mesh mesh, Material material, Matrix{} transforms, int instances ) + +Draw multiple mesh instances with material and different transforms + +- Failure return false +- Success return true +*/ +int lmodelsDrawMeshInstanced( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawMeshInstanced( Mesh mesh, Material material, Matrix{} transforms, int instances )" ); + lua_pushboolean( L, false ); + return 1; + } + int instances = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + Matrix matrises[ instances ]; + + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( lua_istable( L, -1 ) ) { + matrises[i] = uluaGetMatrix( L ); + } + i++; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + size_t materialId = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + size_t meshId = lua_tointeger( L, -1 ); + + if ( !validMesh( meshId ) || !validMaterial( materialId ) ) { + lua_pushboolean( L, false ); + return 1; + } + DrawMeshInstanced( *state->meshes[ meshId ], *state->materials[ materialId ], matrises, instances ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetMeshColor( Mesh mesh, Color color ) + +Updades mesh color vertex attribute buffer +NOTE: Currently only works on custom mesh + +- Failure return false +- Success return true +*/ +int lmodelsSetMeshColor( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetMeshColor( Mesh mesh, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + size_t meshId = lua_tointeger( L, -1 ); + + if ( !validMesh( meshId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + Mesh *mesh = state->meshes[ meshId ]; + + if ( mesh->colors == NULL ) { + TraceLog( LOG_WARNING, "Mesh %d %s", meshId, "Mesh doesn't have vertex colors allocated" ); + lua_pushboolean( L, false ); + return 1; + } + + for ( int i = 0; i < mesh->vertexCount; ++i ) { + mesh->colors[(i*4)+0] = (unsigned char)color.r; + mesh->colors[(i*4)+1] = (unsigned char)color.g; + mesh->colors[(i*4)+2] = (unsigned char)color.b; + mesh->colors[(i*4)+3] = (unsigned char)color.a; + } + /* Update vertex attribute: color */ + rlUpdateVertexBuffer( mesh->vboId[3], mesh->colors, mesh->vertexCount * 4 * sizeof( unsigned char ), 0 ); + + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Models - Material +*/ + +/* +> material = RL_LoadMaterialDefault() + +Load default material + +- Success return int +*/ +int lmodelsLoadMaterialDefault( lua_State *L ) { + int i = 0; + + for ( i = 0; i < state->materialCount; i++ ) { + if ( state->materials[i] == NULL ) { + break; + } + } + state->materials[i] = malloc( sizeof( Material ) ); + *state->materials[i] = LoadMaterialDefault(); + lua_pushinteger( L, i ); + checkMaterialRealloc( i ); + + return 1; +} + +/* +> material = RL_CreateMaterial( material{} ) + +Load material from table. See material table definition + +- Failure return false +- Success return int +*/ +int lmodelsCreateMaterial( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CreateMaterial( material{} )" ); + lua_pushboolean( L, false ); + return 1; + } + int i = 0; + + for ( i = 0; i < state->materialCount; i++ ) { + if ( state->materials[i] == NULL ) { + break; + } + } + state->materials[i] = malloc( sizeof( Material ) ); + *state->materials[i] = LoadMaterialDefault(); + + int t = lua_gettop( L ); + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + if ( strcmp( "maps", (char*)lua_tostring( L, -2 ) ) == 0 && lua_istable( L, -1 ) ) { + int t2 = lua_gettop( L ); + lua_pushnil( L ); + + while ( lua_next( L, t2 ) != 0 ) { + /* Loop maps. Array where we don't care about the index value. */ + if ( lua_istable( L, -1 ) ) { + int t3 = lua_gettop( L ), j = 0, map = 0; + lua_pushnil( L ); + + while ( lua_next( L, t3 ) != 0 ) { + switch ( j ) { + case 0: /* Map */ + map = lua_tointeger( L, -1 ); + break; + case 1: /* Parameters */ + { + int t4 = lua_gettop( L ); + lua_pushnil( L ); + + while ( lua_next( L, t4 ) != 0 ) { + if ( strcmp( "texture", (char*)lua_tostring( L, -2 ) ) == 0 && lua_isnumber( L, -1 ) ) { + state->materials[i]->maps[map].texture = *state->textures[ lua_tointeger( L, -1 ) ]; + } + else if ( strcmp( "color", (char*)lua_tostring( L, -2 ) ) == 0 && lua_istable( L, -1 ) ) { + state->materials[i]->maps[map].color = uluaGetColor( L ); + } + else if ( strcmp( "value", (char*)lua_tostring( L, -2 ) ) == 0 && lua_isnumber( L, -1 ) ) { + state->materials[i]->maps[map].value = lua_tonumber( L, -1 ); + } + lua_pop( L, 1 ); + } + } + break; + default: + break; + } + j++; + lua_pop( L, 1 ); + } + } + lua_pop( L, 1 ); + } + } + else if ( strcmp( "params", (char*)lua_tostring( L, -2 ) ) == 0 && lua_istable( L, -1 ) ) { + int t2 = lua_gettop( L ), j = 0; + lua_pushnil( L ); + + while ( lua_next( L, t2 ) != 0 ) { + if ( j <= 3 ) { + state->materials[i]->params[j] = lua_tonumber( L, -1 ); + } + j++; + lua_pop( L, 1 ); + } + } + else if ( strcmp( "shader", (char*)lua_tostring( L, -2 ) ) == 0 && lua_isnumber( L, -1 ) ) { + /* TODO Shader when implemented. */ + } + lua_pop( L, 1 ); + } + lua_pushinteger( L, i ); + checkMaterialRealloc( i ); + + return 1; +} + +/* +> success = RL_UnloadMaterial( Material material ) + +Unload material from GPU memory ( VRAM ) + +- Failure return false +- Success return true +*/ +int lmodelsUnloadMaterial( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_UnloadMaterial( Material material )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t id = lua_tointeger( L, -1 ); + + if ( !validMaterial( id ) ) { + lua_pushboolean( L, false ); + return 1; + } + UnloadMaterial( *state->materials[ id ] ); + state->materials[ id ] = NULL; + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetMaterialTexture( Material material, int mapType, Texture2D texture ) + +Set texture for a material map type ( MATERIAL_MAP_ALBEDO, MATERIAL_MAP_METALNESS... ) + +- Failure return false +- Success return true +*/ +int lmodelsSetMaterialTexture( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetMaterialTexture( Material material, int mapType, Texture2D texture )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t texId = lua_tointeger( L, -1 ); + size_t materialId = lua_tointeger( L, -3 ); + + if ( !validMaterial( materialId ) || !validSourceTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + // SetMaterialTexture( state->materials[ lua_tointeger( L, -3 ) ], lua_tointeger( L, -2 ), *state->textures[ lua_tointeger( L, -1 ) ] ); + SetMaterialTexture( state->materials[ materialId ], lua_tointeger( L, -2 ), *texturesGetSourceTexture( texId ) ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetMaterialColor( Material material, int mapType, Color color ) + +Set color for a material map type + +- Failure return false +- Success return true +*/ +int lmodelsSetMaterialColor( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetMaterialColor( Material material, int mapType, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + size_t mapType = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + size_t materialId = lua_tointeger( L, -1 ); + + if ( !validMaterial( materialId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + state->materials[ materialId ]->maps[ mapType ].color = color; + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetMaterialValue( Material material, int mapType, float value ) + +Set value for a material map type + +- Failure return false +- Success return true +*/ +int lmodelsSetMaterialValue( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetMaterialValue( Material material, int mapType, float value )" ); + lua_pushboolean( L, false ); + return 1; + } + float value = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + size_t mapType = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + size_t materialId = lua_tointeger( L, -1 ); + + if ( !validMaterial( materialId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + state->materials[ materialId ]->maps[ mapType ].value = value; + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Models - Model +*/ + +/* +> model = RL_LoadModel( string fileName ) + +Load model from files ( Meshes and materials ) + +- Failure return -1 +- Success return int +*/ +int lmodelsLoadModel( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadModel( string fileName )" ); + lua_pushinteger( L, -1 ); + return 1; + } + int i = 0; + + for ( i = 0; i < state->modelCount; i++ ) { + if ( state->models[i] == NULL ) { + break; + } + } + state->models[i] = malloc( sizeof( Model ) ); + *state->models[i] = LoadModel( lua_tostring( L, -1 ) ); + lua_pushinteger( L, i ); + checkModelRealloc( i ); + + return 1; +} + +/* +> model = RL_LoadModelFromMesh( Mesh mesh ) + +Load model from generated mesh ( Default material ) + +- Failure return -1 +- Success return int +*/ +int lmodelsLoadModelFromMesh( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadModelFromMesh( Mesh mesh )" ); + lua_pushinteger( L, -1 ); + return 1; + } + size_t meshId = lua_tointeger( L, -1 ); + + if ( !validMesh( meshId ) ) { + lua_pushinteger( L, -1 ); + return 1; + } + int i = 0; + + for ( i = 0; i < state->modelCount; i++ ) { + if ( state->models[i] == NULL ) { + break; + } + } + state->models[i] = malloc( sizeof( Model ) ); + *state->models[i] = LoadModelFromMesh( *state->meshes[ meshId ] ); + lua_pushinteger( L, i ); + checkModelRealloc( i ); + + return 1; +} + +/* +> success = RL_UnloadModel( Model model ) + +Unload model ( Including meshes ) from memory ( RAM and/or VRAM ) + +- Failure return false +- Success return true +*/ +int lmodelsUnloadModel( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_UnloadModel( Model model )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t modelId = lua_tointeger( L, -1 ); + + if ( !validModel( modelId ) ) { + lua_pushboolean( L, false ); + return 1; + } + UnloadModel( *state->models[ modelId ] ); + state->models[ modelId ] = NULL; + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawModel( Model model, Vector3 position, float scale, Color tint ) + +Draw a model ( With texture if set ) + +- Failure return false +- Success return true +*/ +int lmodelsDrawModel( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawModel( Model model, Vector3 position, float scale, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color tint = uluaGetColor( L ); + lua_pop( L, 1 ); + float scale = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 position = uluaGetVector3( L ); + lua_pop( L, 1 ); + size_t modelId = lua_tointeger( L, -1 ); + + if ( !validModel( modelId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + DrawModel( *state->models[ modelId ], position, scale, tint ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawModelEx( Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint ) + +Draw a model with extended parameters + +- Failure return false +- Success return true +*/ +int lmodelsDrawModelEx( lua_State *L ) { + if ( !lua_isnumber( L, -6 ) || !lua_istable( L, -5 ) || !lua_istable( L, -4 ) + || !lua_isnumber( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawModelEx( Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color tint = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector3 scale = uluaGetVector3( L ); + lua_pop( L, 1 ); + float rotationAngle = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 rotationAxis = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 position = uluaGetVector3( L ); + lua_pop( L, 1 ); + size_t modelId = lua_tointeger( L, -1 ); + + if ( !validModel( modelId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + DrawModelEx( *state->models[ modelId ], position, rotationAxis, rotationAngle, scale, tint ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetModelMaterial( Model model, Material modelMaterial, Material material ) + +Copies material to model material. ( Model material is the material id in models. Material can be deleted if not used elsewhere ) + +- Failure return false +- Success return true +*/ +int lmodelsSetModelMaterial( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetModelMaterial( Model model, Material modelMaterial, Material material )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t materialId = lua_tointeger( L, -1 ); + size_t modelId = lua_tointeger( L, -3 ); + + if ( !validModel( modelId ) || !validMaterial( materialId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + Model *model = state->models[ modelId ]; + int modelMaterialId = lua_tointeger( L, -2 ); + Material *material = state->materials[ materialId ]; + + /* Copy material data instead of using pointer. Pointer would result in double free error. */ + model->materials[ modelMaterialId ].shader = material->shader; + model->materials[ modelMaterialId ].maps[ MATERIAL_MAP_DIFFUSE ] = material->maps[ MATERIAL_MAP_DIFFUSE ]; + model->materials[ modelMaterialId ].maps[ MATERIAL_MAP_METALNESS ] = material->maps[ MATERIAL_MAP_METALNESS ]; + model->materials[ modelMaterialId ].maps[ MATERIAL_MAP_NORMAL ] = material->maps[ MATERIAL_MAP_NORMAL ]; + model->materials[ modelMaterialId ].maps[ MATERIAL_MAP_ROUGHNESS ] = material->maps[ MATERIAL_MAP_ROUGHNESS ]; + model->materials[ modelMaterialId ].maps[ MATERIAL_MAP_OCCLUSION ] = material->maps[ MATERIAL_MAP_OCCLUSION ]; + model->materials[ modelMaterialId ].maps[ MATERIAL_MAP_EMISSION ] = material->maps[ MATERIAL_MAP_EMISSION ]; + model->materials[ modelMaterialId ].maps[ MATERIAL_MAP_HEIGHT ] = material->maps[ MATERIAL_MAP_HEIGHT ]; + model->materials[ modelMaterialId ].maps[ MATERIAL_MAP_CUBEMAP ] = material->maps[ MATERIAL_MAP_CUBEMAP ]; + model->materials[ modelMaterialId ].maps[ MATERIAL_MAP_IRRADIANCE ] = material->maps[ MATERIAL_MAP_IRRADIANCE ]; + model->materials[ modelMaterialId ].maps[ MATERIAL_MAP_PREFILTER ] = material->maps[ MATERIAL_MAP_PREFILTER ]; + model->materials[ modelMaterialId ].maps[ MATERIAL_MAP_BRDF ] = material->maps[ MATERIAL_MAP_BRDF ]; + + for ( int i = 0; i < 4; i++ ) { + model->materials[ modelMaterialId ].params[i] = material->params[i]; + } + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetModelMaterial( Model model, Material modelMaterial, Material material ) + +Set material for a mesh ( Mesh and material on this model ) + +- Failure return false +- Success return true +*/ +int lmodelsSetModelMeshMaterial( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetModelMeshMaterial( Model model, int meshId, int materialId )" ); + lua_pushboolean( L, false ); + return 1; + } + int materialId = lua_tointeger( L, -1 ); + int meshId = lua_tointeger( L, -2 ); + size_t modelId = lua_tointeger( L, -3 ); + + if ( !validModel( modelId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + SetModelMeshMaterial( state->models[ modelId ], meshId, materialId ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawBillboard( Camera camera, Texture2D texture, Vector3 position, float size, Color tint ) + +Draw a billboard texture + +- Failure return false +- Success return true +*/ +int lmodelsDrawBillboard( lua_State *L ) { + if ( !lua_isnumber( L, -5 ) || !lua_isnumber( L, -4 ) || !lua_istable( L, -3 ) + || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawBillboard( Camera camera, Texture2D texture, Vector3 position, float size, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color tint = uluaGetColor( L ); + lua_pop( L, 1 ); + float size = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 position = uluaGetVector3( L ); + lua_pop( L, 1 ); + size_t texId = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + size_t cameraId = lua_tointeger( L, -1 ); + + if ( !validSourceTexture( texId ) || !validCamera3D( cameraId ) ) { + lua_pushboolean( L, false ); + return 1; + } + DrawBillboard( *state->camera3Ds[ cameraId ], *texturesGetSourceTexture( texId ), position, size, tint ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawBillboardRec( Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint ) + +Draw a billboard texture defined by source + +- Failure return false +- Success return true +*/ +int lmodelsDrawBillboardRec( lua_State *L ) { + if ( !lua_isnumber( L, -6 ) || !lua_isnumber( L, -5 ) || !lua_istable( L, -4 ) + || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawBillboardRec( Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color tint = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector2 size = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector3 position = uluaGetVector3( L ); + lua_pop( L, 1 ); + Rectangle source = uluaGetRectangle( L ); + lua_pop( L, 1 ); + size_t texId = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + size_t cameraId = lua_tointeger( L, -1 ); + + if ( !validSourceTexture( texId ) || !validCamera3D( cameraId ) ) { + lua_pushboolean( L, false ); + return 1; + } + DrawBillboardRec( *state->camera3Ds[ cameraId ], *texturesGetSourceTexture( texId ), source, position, size, tint ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Model - Animations +*/ + +/* +> animations, animationCount = RL_LoadModelAnimations( string fileName ) + +Load model animations from file + +- Failure return -1 +- Success return int, int +*/ +int lmodelsLoadModelAnimations( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadModelAnimations( string fileName )" ); + lua_pushinteger( L, -1 ); + return 1; + } + int i = 0; + + for ( i = 0; i < state->animationCount; i++ ) { + if ( state->animations[i] == NULL ) { + break; + } + } + state->animations[i] = malloc( sizeof( ModelAnimations ) ); + state->animations[i]->animations = LoadModelAnimations( lua_tostring( L, -1 ), &state->animations[i]->animCount ); + checkAnimationRealloc( i ); + + lua_pushinteger( L, i ); + lua_pushinteger( L, state->animations[i]->animCount ); + + return 2; +} + +/* +> success = RL_UpdateModelAnimation( Model model, ModelAnimations animations, int animation, int frame ) + +Update model animation pose + +- Failure return false +- Success return true +*/ +int lmodelsUpdateModelAnimation( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_UpdateModelAnimation( Model model, ModelAnimations animations, int animation, int frame )" ); + lua_pushboolean( L, false ); + return 1; + } + int frame = imax( 0, lua_tointeger( L, -1 ) ); + size_t animId = lua_tointeger( L, -3 ); + size_t modelId = lua_tointeger( L, -4 ); + + if ( !validModel( modelId ) || !validAnimation( animId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + UpdateModelAnimation( *state->models[ modelId ], state->animations[ animId ]->animations[ lua_tointeger( L, -2 ) ], frame ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> boneCount = RL_GetModelAnimationBoneCount( ModelAnimations animations, int animation ) + +Return modelAnimation bone count + +- Failure return false +- Success return int +*/ +int lmodelsGetModelAnimationBoneCount( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetModelAnimationBoneCount( ModelAnimations animations, int animation )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t animId = lua_tointeger( L, -2 ); + + if ( !validAnimation( animId ) ) { + lua_pushboolean( L, false ); + return 1; + } + lua_pushinteger( L, state->animations[ animId ]->animations[ lua_tointeger( L, -1 ) ].boneCount ); + + return 1; +} + +/* +> frameCount = RL_GetModelAnimationFrameCount( ModelAnimations animations, int animation ) + +Return modelAnimation frame count + +- Failure return false +- Success return int +*/ +int lmodelsGetModelAnimationFrameCount( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetModelAnimationFrameCount( ModelAnimations animations, int animation )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t animId = lua_tointeger( L, -2 ); + + if ( !validAnimation( animId ) ) { + lua_pushboolean( L, false ); + return 1; + } + lua_pushinteger( L, state->animations[ animId ]->animations[ lua_tointeger( L, -1 ) ].frameCount ); + + return 1; +} + +/* +## Model - Collision +*/ + +/* +> collision = RL_CheckCollisionSpheres( Vector3 center1, float radius1, Vector3 center2, float radius2 ) + +Check collision between two spheres + +- Failure return nil +- Success return bool +*/ +int lmodelsCheckCollisionSpheres( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CheckCollisionSpheres( Vector3 center1, float radius1, Vector3 center2, float radius2 )" ); + lua_pushnil( L ); + return 1; + } + float radius2 = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 center2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + float radius1 = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 center1 = uluaGetVector3( L ); + + lua_pushboolean( L, CheckCollisionSpheres( center1, radius1, center2, radius2 ) ); + + return 1; +} + +/* +> collision = RL_CheckCollisionBoxes( BoundingBox box1, BoundingBox box2 ) + +Check collision between two bounding boxes + +- Failure return nil +- Success return bool +*/ +int lmodelsCheckCollisionBoxes( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CheckCollisionBoxes( BoundingBox box1, BoundingBox box2 )" ); + lua_pushnil( L ); + return 1; + } + BoundingBox box2 = uluaGetBoundingBox( L ); + lua_pop( L, 1 ); + BoundingBox box1 = uluaGetBoundingBox( L ); + + lua_pushboolean( L, CheckCollisionBoxes( box1, box2 ) ); + + return 1; +} + +/* +> collision = RL_CheckCollisionBoxSphere( BoundingBox box, Vector3 center, float radius ) + +Check collision between box and sphere + +- Failure return nil +- Success return bool +*/ +int lmodelsCheckCollisionBoxSphere( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CheckCollisionBoxSphere( BoundingBox box, Vector3 center, float radius )" ); + lua_pushnil( L ); + return 1; + } + float radius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 center = uluaGetVector3( L ); + lua_pop( L, 1 ); + BoundingBox box = uluaGetBoundingBox( L ); + + lua_pushboolean( L, CheckCollisionBoxSphere( box, center, radius ) ); + + return 1; +} + +/* +> rayCollision = RL_GetRayCollisionSphere( Ray ray, Vector3 center, float radius ) + +Get collision info between ray and sphere. ( RayCollision is Lua table of { hit, distance, point, normal } ) + +- Failure return nil +- Success return RayCollision +*/ +int lmodelsGetRayCollisionSphere( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetRayCollisionSphere( Ray ray, Vector3 center, float radius )" ); + lua_pushnil( L ); + return 1; + } + float radius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 center = uluaGetVector3( L ); + lua_pop( L, 1 ); + Ray ray = uluaGetRay( L ); + + uluaPushRayCollision( L, GetRayCollisionSphere( ray, center, radius ) ); + + return 1; +} + +/* +> rayCollision = RL_GetRayCollisionBox( Ray ray, BoundingBox box ) + +Get collision info between ray and box + +- Failure return nil +- Success return RayCollision +*/ +int lmodelsGetRayCollisionBox( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetRayCollisionBox( Ray ray, BoundingBox box )" ); + lua_pushnil( L ); + return 1; + } + BoundingBox box = uluaGetBoundingBox( L ); + lua_pop( L, 1 ); + Ray ray = uluaGetRay( L ); + + uluaPushRayCollision( L, GetRayCollisionBox( ray, box ) ); + + return 1; +} + +/* +> rayCollision = RL_GetRayCollisionModel( Ray ray, Model model ) + +Get collision info between ray and model + +- Failure return nil +- Success return RayCollision +*/ +int lmodelsGetRayCollisionModel( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetRayCollisionModel( Ray ray, Model model )" ); + lua_pushnil( L ); + return 1; + } + size_t modelId = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + Ray ray = uluaGetRay( L ); + + if ( !validModel( modelId ) ) { + lua_pushnil( L ); + return 1; + } + uluaPushRayCollision( L, GetRayCollisionModel( ray, *state->models[ modelId ] ) ); + + return 1; +} + +/* +> rayCollision = RL_GetRayCollisionMesh( Ray ray, Mesh mesh, Matrix transform ) + +Get collision info between ray and mesh + +- Failure return nil +- Success return RayCollision +*/ +int lmodelsGetRayCollisionMesh( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetRayCollisionMesh( Ray ray, Mesh mesh, Matrix transform )" ); + lua_pushnil( L ); + return 1; + } + Matrix transform = uluaGetMatrix( L ); + lua_pop( L, 1 ); + size_t meshId = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + Ray ray = uluaGetRay( L ); + + if ( !validMesh( meshId ) ) { + lua_pushnil( L ); + return 1; + } + uluaPushRayCollision( L, GetRayCollisionMesh( ray, *state->meshes[ meshId ], transform ) ); + + return 1; +} + +/* +> rayCollision = RL_GetRayCollisionTriangle( Ray ray, Vector3 p1, Vector3 p2, Vector3 p3 ) + +Get collision info between ray and triangle + +- Failure return nil +- Success return RayCollision +*/ +int lmodelsGetRayCollisionTriangle( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetRayCollisionTriangle( Ray ray, Vector3 p1, Vector3 p2, Vector3 p3 )" ); + lua_pushnil( L ); + return 1; + } + Vector3 p3 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 p2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 p1 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Ray ray = uluaGetRay( L ); + + uluaPushRayCollision( L, GetRayCollisionTriangle( ray, p1, p2, p3 ) ); + + return 1; +} + +/* +> rayCollision = RL_GetRayCollisionQuad( Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4 ) + +Get collision info between ray and quad + +- Failure return nil +- Success return RayCollision +*/ +int lmodelsGetRayCollisionQuad( lua_State *L ) { + if ( !lua_istable( L, -5 ) || !lua_istable( L, -4 ) || !lua_istable( L, -3 ) + || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetRayCollisionQuad( Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4 )" ); + lua_pushnil( L ); + return 1; + } + Vector3 p4 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 p3 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 p2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 p1 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Ray ray = uluaGetRay( L ); + + uluaPushRayCollision( L, GetRayCollisionQuad( ray, p1, p2, p3, p4 ) ); + + return 1; +} + diff --git a/src/rgui.c b/src/rgui.c new file mode 100644 index 0000000..2a967da --- /dev/null +++ b/src/rgui.c @@ -0,0 +1,544 @@ +#include "main.h" +#include "state.h" +#include "rgui.h" +#include "lua_core.h" + +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" + +/* +## Gui - Global +*/ + +/* +> RL_GuiEnable() + +Enable gui controls ( Global state ) +*/ +int lguiGuiEnable( lua_State *L ) { + GuiEnable(); + + return 1; +} + +/* +> RL_GuiDisable() + +Disable gui controls ( Global state ) +*/ +int lguiGuiDisable( lua_State *L ) { + GuiDisable(); + + return 1; +} + +/* +> RL_GuiLock() + +Lock gui controls ( Global state ) +*/ +int lguiGuiLock( lua_State *L ) { + GuiLock(); + + return 1; +} + +/* +> RL_GuiUnlock() + +Unlock gui controls ( Global state ) +*/ +int lguiGuiUnlock( lua_State *L ) { + GuiUnlock(); + + return 1; +} + +/* +## Gui - Font +*/ + +/* +> success = RL_GuiSetFont( Font font ) + +Set gui custom font ( Global state ) + +- Failure return false +- Success return true +*/ +int lguiGuiSetFont( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiSetFont( Font font )" ); + lua_pushboolean( L, false ); + return 1; + } + GuiSetFont( *state->fonts[ lua_tointeger( L, -1 ) ] ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Gui - Container +*/ + +/* +> state = RL_GuiWindowBox( Rectangle bounds, string title ) + +Window Box control, shows a window that can be closed + +- Failure return nil +- Success return bool +*/ +int lguiGuiWindowBox( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiWindowBox( Rectangle bounds, string title )" ); + lua_pushnil( L ); + return 1; + } + char text[ STRING_LEN ] = { '\0' }; + strcpy( text, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushboolean( L, GuiWindowBox( rect, text ) ); + + return 1; +} + +/* +> success = RL_GuiPanel( Rectangle bounds ) + +Panel control, useful to group controls + +- Failure return false +- Success return true +*/ +int lguiGuiPanel( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiPanel( Rectangle bounds )" ); + lua_pushboolean( L, false ); + return 1; + } + Rectangle rect = uluaGetRectangle( L ); + + GuiPanel( rect ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> view, scroll = RL_GuiScrollPanel( Rectangle bounds, Rectangle content, Vector2 scroll ) + +Scroll Panel control + +- Failure return false +- Success return Rectangle, Vector2 +*/ +int lguiGuiScrollPanel( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiScrollPanel( Rectangle bounds, Rectangle content, Vector2 scroll )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 scroll = uluaGetVector2( L ); + lua_pop( L, 1 ); + Rectangle content = uluaGetRectangle( L ); + lua_pop( L, 1 ); + Rectangle bounds = uluaGetRectangle( L ); + + uluaPushRectangle( L, GuiScrollPanel( bounds, content, &scroll ) ); + uluaPushVector2( L, scroll ); + + return 2; +} + +/* +## Gui - Basic +*/ + +/* +> success = RL_GuiLabel( Rectangle bounds, string text ) + +Label control, shows text + +- Failure return false +- Success return true +*/ +int lguiGuiLabel( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiLabel( Rectangle bounds, string text )" ); + lua_pushboolean( L, false ); + return 1; + } + char text[ STRING_LEN ] = { '\0' }; + strcpy( text, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + GuiLabel( rect, text ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> clicked = RL_GuiButton( Rectangle bounds, string text ) + +Button control, returns true when clicked + +- Failure return nil +- Success return boolean +*/ +int lguiGuiButton( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiButton( Rectangle bounds, string text )" ); + lua_pushnil( L ); + return 1; + } + char text[ STRING_LEN ] = { '\0' }; + strcpy( text, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushboolean( L, GuiButton( rect, text ) ); + + return 1; +} + +/* +> active = RL_GuiToggle( Rectangle bounds, string text, bool active ) + +Toggle Button control, returns true when active + +- Failure return nil +- Success return boolean +*/ +int lguiGuiToggle( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_isstring( L, -2 ) || !lua_isboolean( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiToggle( Rectangle bounds, string text, bool active )" ); + lua_pushnil( L ); + return 1; + } + bool checked = lua_toboolean( L, -1 ); + lua_pop( L, 1 ); + char text[ STRING_LEN ] = { '\0' }; + strcpy( text, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushboolean( L, GuiToggle( rect, text, checked ) ); + + return 1; +} + +/* +> active = RL_GuiCheckBox( Rectangle bounds, string text, bool checked ) + +Check Box control, returns true when active + +- Failure return nil +- Success return boolean +*/ +int lguiGuiCheckBox( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_isstring( L, -2 ) || !lua_isboolean( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiCheckBox( Rectangle bounds, string text, bool checked )" ); + lua_pushnil( L ); + return 1; + } + bool checked = lua_toboolean( L, -1 ); + lua_pop( L, 1 ); + char text[ STRING_LEN ] = { '\0' }; + strcpy( text, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushboolean( L, GuiCheckBox( rect, text, checked ) ); + + return 1; +} + +/* +> pressed, text = RL_GuiTextBox( Rectangle bounds, string text, int textSize, bool editMode ) + +Text Box control, updates input text + +- Failure return nil +- Success return boolean, string +*/ +int lguiGuiTextBox( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_isstring( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isboolean( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiTextBox( Rectangle bounds, string text, int textSize, bool editMode )" ); + lua_pushnil( L ); + return 1; + } + bool editMode = lua_toboolean( L, -1 ); + lua_pop( L, 1 ); + int textSize = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + char text[ STRING_LEN ] = { '\0' }; + strcpy( text, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushboolean( L, GuiTextBox( rect, text, textSize, editMode ) ); + lua_pushstring( L, text ); + + return 2; +} + +/* +> pressed, text = RL_GuiTextBoxMulti( Rectangle bounds, string text, int textSize, bool editMode ) + +Text Box control with multiple lines + +- Failure return nil +- Success return boolean, string +*/ +int lguiGuiTextBoxMulti( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_isstring( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isboolean( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiTextBoxMulti( Rectangle bounds, string text, int textSize, bool editMode )" ); + lua_pushnil( L ); + return 1; + } + bool editMode = lua_toboolean( L, -1 ); + lua_pop( L, 1 ); + int textSize = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + char text[ STRING_LEN ] = { '\0' }; + strcpy( text, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushboolean( L, GuiTextBoxMulti( rect, text, textSize, editMode ) ); + lua_pushstring( L, text ); + + return 2; +} + +/* +> pressed, value = RL_GuiSpinner( Rectangle bounds, string text, int value, int minValue, int maxValue, bool editMode ) + +Spinner control, returns selected value + +- Failure return nil +- Success return boolean, int +*/ +int lguiGuiSpinner( lua_State *L ) { + if ( !lua_istable( L, -6 ) || !lua_isstring( L, -5 ) || !lua_isnumber( L, -4 ) + || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isboolean( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiSpinner( Rectangle bounds, string text, int value, int minValue, int maxValue, bool editMode )" ); + lua_pushnil( L ); + return 1; + } + bool editMode = lua_toboolean( L, -1 ); + lua_pop( L, 1 ); + int maxValue = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + int minValue = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + int value = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + char text[ STRING_LEN ] = { '\0' }; + strcpy( text, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushboolean( L, GuiSpinner( rect, text, &value, minValue, maxValue, editMode ) ); + lua_pushinteger( L, value ); + + return 2; +} + +/* +> pressed, value = RL_GuiValueBox( Rectangle bounds, string text, int value, int minValue, int maxValue, bool editMode ) + +Value Box control, updates input text with numbers + +- Failure return nil +- Success return boolean, int +*/ +int lguiGuiValueBox( lua_State *L ) { + if ( !lua_istable( L, -6 ) || !lua_isstring( L, -5 ) || !lua_isnumber( L, -4 ) + || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isboolean( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiValueBox( Rectangle bounds, string text, int value, int minValue, int maxValue, bool editMode )" ); + lua_pushnil( L ); + return 1; + } + bool editMode = lua_toboolean( L, -1 ); + lua_pop( L, 1 ); + int maxValue = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + int minValue = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + int value = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + char text[ STRING_LEN ] = { '\0' }; + strcpy( text, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushboolean( L, GuiValueBox( rect, text, &value, minValue, maxValue, editMode ) ); + lua_pushinteger( L, value ); + + return 2; +} + +/* +> value = RL_GuiSlider( Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue ) + +Slider control, returns selected value + +- Failure return nil +- Success return float +*/ +int lguiGuiSlider( lua_State *L ) { + if ( !lua_istable( L, -6 ) || !lua_isstring( L, -5 ) || !lua_isstring( L, -4 ) + || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiSlider( Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue )" ); + lua_pushnil( L ); + return 1; + } + float maxValue = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float minValue = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float value = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + char textRight[ STRING_LEN ] = { '\0' }; + strcpy( textRight, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + char textLeft[ STRING_LEN ] = { '\0' }; + strcpy( textLeft, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushnumber( L, GuiSlider( rect, textLeft, textRight, value, minValue, maxValue ) ); + + return 1; +} + +/* +> value = RL_GuiSliderBar( Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue ) + +Slider Bar control, returns selected value + +- Failure return nil +- Success return float +*/ +int lguiGuiSliderBar( lua_State *L ) { + if ( !lua_istable( L, -6 ) || !lua_isstring( L, -5 ) || !lua_isstring( L, -4 ) + || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiSliderBar( Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue )" ); + lua_pushnil( L ); + return 1; + } + float maxValue = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float minValue = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float value = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + char textRight[ STRING_LEN ] = { '\0' }; + strcpy( textRight, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + char textLeft[ STRING_LEN ] = { '\0' }; + strcpy( textLeft, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushnumber( L, GuiSliderBar( rect, textLeft, textRight, value, minValue, maxValue ) ); + + return 1; +} + +/* +> value = RL_GuiProgressBar( Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue ) + +Progress Bar control, shows current progress value + +- Failure return nil +- Success return float +*/ +int lguiGuiProgressBar( lua_State *L ) { + if ( !lua_istable( L, -6 ) || !lua_isstring( L, -5 ) || !lua_isstring( L, -4 ) + || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiProgressBar( Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue )" ); + lua_pushnil( L ); + return 1; + } + float maxValue = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float minValue = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float value = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + char textRight[ STRING_LEN ] = { '\0' }; + strcpy( textRight, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + char textLeft[ STRING_LEN ] = { '\0' }; + strcpy( textLeft, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushnumber( L, GuiProgressBar( rect, textLeft, textRight, value, minValue, maxValue ) ); + + return 1; +} + +/* +> value = RL_GuiScrollBar( Rectangle bounds, int value, int minValue, int maxValue ) + +Scroll Bar control + +- Failure return nil +- Success return int +*/ +int lguiGuiScrollBar( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiScrollBar( Rectangle bounds, int value, int minValue, int maxValue )" ); + lua_pushnil( L ); + return 1; + } + int maxValue = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + int minValue = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + int value = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushinteger( L, GuiScrollBar( rect, value, minValue, maxValue ) ); + + return 1; +} + +/* +> pressed, item = RL_GuiDropdownBox( Rectangle bounds, string text, int active, bool editMode ) + +Dropdown Box control, returns selected item + +- Failure return nil +- Success return bool, int +*/ +int lguiGuiDropdownBox( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_isstring( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isboolean( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GuiDropdownBox( Rectangle bounds, string text, int active, bool editMode )" ); + lua_pushnil( L ); + return 1; + } + bool editMode = lua_toboolean( L, -1 ); + lua_pop( L, 1 ); + int active = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + char text[ STRING_LEN ] = { '\0' }; + strcpy( text, lua_tostring( L, -1 ) ); + lua_pop( L, 1 ); + Rectangle rect = uluaGetRectangle( L ); + + lua_pushboolean( L, GuiDropdownBox( rect, text, &active, editMode ) ); + lua_pushinteger( L, active ); + + return 2; +} diff --git a/src/rmath.c b/src/rmath.c new file mode 100644 index 0000000..7ad9bdb --- /dev/null +++ b/src/rmath.c @@ -0,0 +1,978 @@ +#include "main.h" +#include "state.h" +#include "rmath.h" +#include "lua_core.h" + +inline int imin( int a, int b ) { + return a < b ? a : b; +} + +inline int imax( int a, int b ) { + return a > b ? a : b; +} + +/* +## Math - Vector2 +*/ + +/* +> result = RL_Vector2Add( Vector2 v1, Vector2 v2 ) + +Add two vectors (v1 + v2) + +- Failure return false +- Success return Vector2 +*/ +int lmathVector2Add( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2Add( Vector2 v1, Vector2 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 v2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v1 = uluaGetVector2( L ); + + uluaPushVector2( L, Vector2Add( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector2Subtract( Vector2 v1, Vector2 v2 ) + +Subtract two vectors (v1 - v2) + +- Failure return false +- Success return Vector2 +*/ +int lmathVector2Subtract( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2Subtract( Vector2 v1, Vector2 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 v2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v1 = uluaGetVector2( L ); + + uluaPushVector2( L, Vector2Subtract( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector2Multiply( Vector2 v1, Vector2 v2 ) + +Multiply vector by vector + +- Failure return false +- Success return Vector2 +*/ +int lmathVector2Multiply( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2Multiply( Vector2 v1, Vector2 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 v2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v1 = uluaGetVector2( L ); + + uluaPushVector2( L, Vector2Multiply( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector2Length( vector2 vector ) + +Calculate vector length + +- Failure return false +- Success return float +*/ +int lmathVector2Length( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2Length( vector2 v )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 v = uluaGetVector2( L ); + + lua_pushnumber( L, Vector2Length( v ) ); + + return 1; +} + +/* +> result = RL_Vector2DotProduct( Vector2 v1, Vector2 v2 ) + +Calculate two vectors dot product + +- Failure return false +- Success return float +*/ +int lmathVector2DotProduct( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2DotProduct( Vector2 v1, Vector2 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 v2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v1 = uluaGetVector2( L ); + + lua_pushnumber( L, Vector2DotProduct( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector2Distance( Vector2 v1, Vector2 v2 ) + +Calculate distance between two vectors + +- Failure return false +- Success return float +*/ +int lmathVector2Distance( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2Distance( Vector2 v1, Vector2 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 v2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v1 = uluaGetVector2( L ); + + lua_pushnumber( L, Vector2Distance( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector2Angle( Vector2 v1, Vector2 v2 ) + +Calculate angle from two vectors + +- Failure return false +- Success return float +*/ +int lmathVector2Angle( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2Angle( Vector2 v1, Vector2 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 v2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v1 = uluaGetVector2( L ); + + lua_pushnumber( L, Vector2Angle( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector2Normalize( Vector2 v ) + +Normalize provided vector + +- Failure return false +- Success return Vector2 +*/ +int lmathVector2Normalize( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2Normalize( Vector2 v )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 v = Vector2Normalize( uluaGetVector2( L ) ); + + uluaPushVector2( L, v ); + + return 1; +} + +/* +> result = RL_Vector2Lerp( Vector2 v1, Vector2 v2, float amount ) + +Calculate linear interpolation between two vectors + +- Failure return false +- Success return Vector2 +*/ +int lmathVector2Lerp( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2Lerp( Vector2 v1, Vector2 v2, float amount )" ); + lua_pushboolean( L, false ); + return 1; + } + float amount = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 v2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v1 = uluaGetVector2( L ); + + uluaPushVector2( L, Vector2Lerp( v1, v2, amount ) ); + + return 1; +} + +/* +> result = RL_Vector2Reflect( Vector2 v, Vector2 normal ) + +Calculate reflected vector to normal + +- Failure return false +- Success return Vector2 +*/ +int lmathVector2Reflect( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2Reflect( Vector2 v, Vector2 normal )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector2 v2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v1 = uluaGetVector2( L ); + + uluaPushVector2( L, Vector2Reflect( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector2Rotate( Vector2 v, float angle ) + +Rotate vector by angle + +- Failure return false +- Success return Vector2 +*/ +int lmathVector2Rotate( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2Rotate( Vector2 v, float angle )" ); + lua_pushboolean( L, false ); + return 1; + } + float degs = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 v = uluaGetVector2( L ); + + uluaPushVector2( L, Vector2Rotate( v, degs ) ); + + return 1; +} + +/* +> result = RL_Vector2MoveTowards( Vector2 v, Vector2 target, float maxDistance ) + +Move Vector towards target + +- Failure return false +- Success return Vector2 +*/ +int lmathVector2MoveTowards( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector2MoveTowards( Vector2 v, Vector2 target, float maxDistance )" ); + lua_pushboolean( L, false ); + return 1; + } + float maxDistance = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 v2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v1 = uluaGetVector2( L ); + + uluaPushVector2( L, Vector2MoveTowards( v1, v2, maxDistance ) ); + + return 1; +} + +/* +## Math - Vector 3 +*/ + +/* +> result = RL_Vector3CrossProduct( Vector3 v1, Vector3 v2 ) + +Add two vectors + +- Failure return false +- Success return Vector3 +*/ +int lmathVector3Add( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3Add( Vector3 v1, Vector3 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 v1 = uluaGetVector3( L ); + + uluaPushVector3( L, Vector3Add( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector3Subtract( Vector3 v1, Vector3 v2 ) + +Subtract two vectors + +- Failure return false +- Success return Vector3 +*/ +int lmathVector3Subtract( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3Subtract( Vector3 v1, Vector3 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 v1 = uluaGetVector3( L ); + + uluaPushVector3( L, Vector3Subtract( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector3Subtract( Vector3 v1, Vector3 v2 ) + +Multiply vector by vector + +- Failure return false +- Success return Vector3 +*/ +int lmathVector3Multiply( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3Multiply( Vector3 v1, Vector3 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 v1 = uluaGetVector3( L ); + + uluaPushVector3( L, Vector3Multiply( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector3CrossProduct( Vector3 v1, Vector3 v2 ) + +Calculate two vectors cross product + +- Failure return false +- Success return Vector3 +*/ +int lmathVector3CrossProduct( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3CrossProduct( Vector3 v1, Vector3 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 v1 = uluaGetVector3( L ); + + uluaPushVector3( L, Vector3CrossProduct( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector3Perpendicular( Vector3 v ) + +Calculate one vector perpendicular vector + +- Failure return false +- Success return Vector3 +*/ +int lmathVector3Perpendicular( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3Perpendicular( Vector3 v )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v = uluaGetVector3( L ); + + uluaPushVector3( L, Vector3Perpendicular( v ) ); + + return 1; +} + +/* +> result = RL_Vector3Length( Vector3 v ) + +Calculate one vector perpendicular vector + +- Failure return false +- Success return float +*/ +int lmathVector3Length( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3Length( Vector3 v )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v = uluaGetVector3( L ); + + lua_pushnumber( L, Vector3Length( v ) ); + + return 1; +} + +/* +> result = RL_Vector3LengthSqr( Vector3 v ) + +Calculate vector square length + +- Failure return false +- Success return float +*/ +int lmathVector3LengthSqr( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3LengthSqr( Vector3 v )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v = uluaGetVector3( L ); + + lua_pushnumber( L, Vector3LengthSqr( v ) ); + + return 1; +} + +/* +> result = RL_Vector3DotProduct( Vector3 v1, Vector3 v2 ) + +Calculate two vectors dot product + +- Failure return false +- Success return float +*/ +int lmathVector3DotProduct( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3DotProduct( Vector3 v1, Vector3 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 v1 = uluaGetVector3( L ); + + lua_pushnumber( L, Vector3DotProduct( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector3Distance( Vector3 v1, Vector3 v2 ) + +Calculate distance between two vectors + +- Failure return false +- Success return float +*/ +int lmathVector3Distance( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3Distance( Vector3 v1, Vector3 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 v1 = uluaGetVector3( L ); + + lua_pushnumber( L, Vector3Distance( v1, v2 ) ); + + return 1; +} + +/* +> result = RL_Vector3Normalize( Vector3 v ) + +Normalize provided vector + +- Failure return false +- Success return Vector3 +*/ +int lmathVector3Normalize( lua_State *L ) { + /* Vector. */ + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3Normalize( Vector3 v )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v = uluaGetVector3( L ); + + uluaPushVector3( L, Vector3Normalize( v ) ); + + return 1; +} + +/* +> v1, v2 = RL_Vector3OrthoNormalize( Vector3 v1, Vector3 v2 ) + +Orthonormalize provided vectors. Makes vectors normalized and orthogonal to each other. +Gram-Schmidt function implementation + +- Failure return false +- Success return Vector3, Vector3 +*/ +int lmathVector3OrthoNormalize( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3OrthoNormalize( Vector3 v1, Vector3 v2 )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 v1 = uluaGetVector3( L ); + + Vector3OrthoNormalize( &v1, &v2 ); + + uluaPushVector3( L, v1 ); + uluaPushVector3( L, v2 ); + + return 2; +} + +/* +> result = RL_Vector3Transform( Vector3 v, Matrix mat ) + +Transforms a Vector3 by a given Matrix + +- Failure return false +- Success return Vector3 +*/ +int lmathVector3Transform( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3Transform( Vector3 v, Matrix mat )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v1 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Matrix mat = uluaGetMatrix( L ); + + uluaPushVector3( L, Vector3Transform( v1, mat ) ); + + return 1; +} + +/* +> result = RL_Vector3RotateByQuaternion( Vector3 v, Quaternion q ) + +Transform a vector by quaternion rotation + +- Failure return false +- Success return Vector3 +*/ +int lmathVector3RotateByQuaternion( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3RotateByQuaternion( Vector3 v, Quaternion q )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v1 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Quaternion q = uluaGetQuaternion( L ); + + uluaPushVector3( L, Vector3RotateByQuaternion( v1, q ) ); + + return 1; +} + +/* +> result = RL_Vector3Lerp( Vector3 v1, Vector3 v2, float amount ) + +Calculate linear interpolation between two vectors + +- Failure return false +- Success return Vector3 +*/ +int lmathVector3Lerp( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3Lerp( Vector3 v1, Vector3 v2, float amount )" ); + lua_pushboolean( L, false ); + return 1; + } + float amount = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 v2 = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 v1 = uluaGetVector3( L ); + + uluaPushVector3( L, Vector3Lerp( v1, v2, amount ) ); + + return 1; +} + +/* +> result = RL_Vector3Reflect( Vector3 v, Vector3 normal ) + +Calculate reflected vector to normal + +- Failure return false +- Success return Vector3 +*/ +int lmathVector3Reflect( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_Vector3Reflect( Vector3 v, Vector3 normal )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 normal = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 v1 = uluaGetVector3( L ); + + uluaPushVector3( L, Vector3Reflect( v1, normal ) ); + + return 1; +} + +/* +## Math - Matrix +*/ + +/* +> result = RL_MatrixDeterminant( Matrix mat ) + +Compute matrix determinant + +- Failure return false +- Success return float +*/ +int lmathMatrixDeterminant( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixDeterminant( Matrix mat )" ); + lua_pushboolean( L, false ); + return 1; + } + Matrix mat = uluaGetMatrix( L ); + + lua_pushnumber( L, MatrixDeterminant( mat ) ); + + return 1; +} + +/* +> result = RL_MatrixTranspose( Matrix mat ) + +Transposes provided matrix + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixTranspose( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixTranspose( Matrix mat )" ); + lua_pushboolean( L, false ); + return 1; + } + Matrix mat = uluaGetMatrix( L ); + + uluaPushMatrix( L, MatrixTranspose( mat ) ); + + return 1; +} + +/* +> result = RL_MatrixInvert( Matrix mat ) + +Invert provided matrix + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixInvert( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixInvert( Matrix mat )" ); + lua_pushboolean( L, false ); + return 1; + } + Matrix mat = uluaGetMatrix( L ); + + uluaPushMatrix( L, MatrixInvert( mat ) ); + + return 1; +} + +/* +> result = RL_MatrixNormalize( Matrix mat ) + +Normalize provided matrix + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixNormalize( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixNormalize( Matrix mat )" ); + lua_pushboolean( L, false ); + return 1; + } + Matrix mat = uluaGetMatrix( L ); + + uluaPushMatrix( L, MatrixNormalize( mat ) ); + + return 1; +} + +/* +> result = MatrixIdentity() + +Get identity matrix + +- Success return Matrix +*/ +int lmathMatrixIdentity( lua_State *L ) { + uluaPushMatrix( L, MatrixIdentity() ); + + return 1; +} + +/* +> result = RL_MatrixAdd( Matrix left, Matrix right ) + +Add two matrices + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixAdd( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixAdd( Matrix left, Matrix right )" ); + lua_pushboolean( L, false ); + return 1; + } + Matrix mat2 = uluaGetMatrix( L ); + lua_pop( L, 1 ); + Matrix mat1 = uluaGetMatrix( L ); + + uluaPushMatrix( L, MatrixAdd( mat1, mat2 ) ); + + return 1; +} + +/* +> result = RL_MatrixAdd( Matrix left, Matrix right ) + +Subtract two matrices (left - right) + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixSubtract( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixSubtract( Matrix left, Matrix right )" ); + lua_pushboolean( L, false ); + return 1; + } + Matrix mat2 = uluaGetMatrix( L ); + lua_pop( L, 1 ); + Matrix mat1 = uluaGetMatrix( L ); + + uluaPushMatrix( L, MatrixSubtract( mat1, mat2 ) ); + + return 1; +} + +/* +> result = RL_MatrixMultiply( Matrix left, Matrix right ) + +Get two matrix multiplication + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixMultiply( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixMultiply( Matrix left, Matrix right )" ); + lua_pushboolean( L, false ); + return 1; + } + Matrix mat2 = uluaGetMatrix( L ); + lua_pop( L, 1 ); + Matrix mat1 = uluaGetMatrix( L ); + + uluaPushMatrix( L, MatrixMultiply( mat1, mat2 ) ); + + return 1; +} + +/* +> result = RL_MatrixTranslate( Vector3 translate ) + +Get translation matrix + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixTranslate( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixTranslate( Vector3 translate )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v = uluaGetVector3( L ); + + uluaPushMatrix( L, MatrixTranslate( v.x, v.y, v.z ) ); + + return 1; +} + +/* +> result = RL_MatrixRotate( Vector3 axis, float angle ) + +Create rotation matrix from axis and angle. NOTE: Angle should be provided in radians + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixRotate( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixRotate( Vector3 axis, float angle )" ); + lua_pushboolean( L, false ); + return 1; + } + float angle = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector3 axis = uluaGetVector3( L ); + + uluaPushMatrix( L, MatrixRotate( axis, angle ) ); + + return 1; +} + +/* +> result = RL_MatrixScale( Vector3 scale ) + +Get scaling matrix + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixScale( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixScale( Vector3 scale )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 v = uluaGetVector3( L ); + + uluaPushMatrix( L, MatrixScale( v.x, v.y, v.z ) ); + + return 1; +} + +/* +> result = RL_MatrixFrustum( double left, double right, double bottom, double top, double near, double far ) + +Get perspective projection matrix + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixFrustum( lua_State *L ) { + if ( !lua_isnumber( L, -6 ) || !lua_isnumber( L, -5 ) || !lua_isnumber( L, -4 ) + || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixFrustum( double left, double right, double bottom, double top, double near, double far )" ); + lua_pushboolean( L, false ); + return 1; + } + float far = lua_tonumber( L, -1 ); + float near = lua_tonumber( L, -2 ); + float top = lua_tonumber( L, -3 ); + float bottom = lua_tonumber( L, -4 ); + float right = lua_tonumber( L, -5 ); + float left = lua_tonumber( L, -6 ); + + uluaPushMatrix( L, MatrixFrustum( left, right, bottom, top, near, far ) ); + + return 1; +} + +/* +> result = RL_MatrixPerspective( double fovy, double aspect, double near, double far ) + +Get perspective projection matrix + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixPerspective( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixPerspective( double fovy, double aspect, double near, double far )" ); + lua_pushboolean( L, false ); + return 1; + } + float far = lua_tonumber( L, -1 ); + float near = lua_tonumber( L, -2 ); + float aspect = lua_tonumber( L, -3 ); + float fovy = lua_tonumber( L, -4 ); + + uluaPushMatrix( L, MatrixPerspective( fovy, aspect, near, far ) ); + + return 1; +} + +/* +> result = RL_MatrixOrtho( double left, double right, double bottom, double top, double near, double far ) + +Get orthographic projection matrix + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixOrtho( lua_State *L ) { + if ( !lua_isnumber( L, -6 ) || !lua_isnumber( L, -5 ) || !lua_isnumber( L, -4 ) + || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixOrtho( double left, double right, double bottom, double top, double near, double far )" ); + lua_pushboolean( L, false ); + return 1; + } + float far = lua_tonumber( L, -1 ); + float near = lua_tonumber( L, -2 ); + float top = lua_tonumber( L, -3 ); + float bottom = lua_tonumber( L, -4 ); + float right = lua_tonumber( L, -5 ); + float left = lua_tonumber( L, -6 ); + + uluaPushMatrix( L, MatrixOrtho( left, right, bottom, top, near, far ) ); + + return 1; +} + +/* +> result = RL_MatrixLookAt( Vector3 eye, Vector3 target, Vector3 up ) + +Get camera look-at matrix ( View matrix ) + +- Failure return false +- Success return Matrix +*/ +int lmathMatrixLookAt( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_MatrixLookAt( Vector3 eye, Vector3 target, Vector3 up )" ); + lua_pushboolean( L, false ); + return 1; + } + Vector3 up = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 target = uluaGetVector3( L ); + lua_pop( L, 1 ); + Vector3 eye = uluaGetVector3( L ); + + uluaPushMatrix( L, MatrixLookAt( eye, target, up ) ); + + return 1; +} diff --git a/src/shapes.c b/src/shapes.c new file mode 100644 index 0000000..cb489f0 --- /dev/null +++ b/src/shapes.c @@ -0,0 +1,457 @@ +#include "main.h" +#include "shapes.h" +#include "lua_core.h" + +/* +## Shapes - Drawing +*/ + +/* +> success = RL_DrawPixel( Vector2 pos, Color color ) + +Draw a pixel + +- Failure return false +- Success return true +*/ +int lshapesDrawPixel( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawPixel( Vector2 pos, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector2 pos = uluaGetVector2( L ); + + DrawPixelV( pos, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawLine( Vector2 startPos, Vector2 endPos, float thickness, Color color ) + +Draw a line defining thickness + +- Failure return false +- Success return true +*/ +int lshapesDrawLine( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawLine( Vector2 startPos, Vector2 endPos, float thickness, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + float thickness = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 endPos = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 startPos = uluaGetVector2( L ); + + DrawLineEx( startPos, endPos, thickness, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawCircle( Vector2 center, float radius, Color color ) + +Draw a color-filled circle + +- Failure return false +- Success return true +*/ +int lshapesDrawCircle( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawCircle( Vector2 center, float radius, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + float radius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 center = uluaGetVector2( L ); + + DrawCircleV( center, radius, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawCircleLines( Vector2 center, float radius, Color color ) + +Draw circle outline + +- Failure return false +- Success return true +*/ +int lshapesDrawCircleLines( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawCircleLines( Vector2 center, float radius, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + float radius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 center = uluaGetVector2( L ); + + DrawCircleLines( center.x, center.y, radius, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawRectangle( Rectangle rec, Color color ) + +Draw a color-filled rectangle + +- Failure return false +- Success return true +*/ +int lshapesDrawRectangle( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawRectangle( Rectangle rec, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Rectangle rect = { 0, 0, 0, 0 }; + Color color = { 0, 0, 0, 0 }; + + color = uluaGetColor( L ); + lua_pop( L, 1 ); + rect = uluaGetRectangle( L ); + + DrawRectangleRec( rect, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawRectanglePro( Rectangle rec, Vector2 origin, float rotation, Color color ) + +Draw a color-filled rectangle with pro parameters + +- Failure return false +- Success return true +*/ +int lshapesDrawRectanglePro( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawRectanglePro( Rectangle rec, Vector2 origin, float rotation, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + float rotation = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 origin = uluaGetVector2( L ); + lua_pop( L, 1 ); + Rectangle rec = uluaGetRectangle( L ); + + DrawRectanglePro( rec, origin, rotation, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawTriangle( Vector2 v1, Vector2 v2, Vector2 v3, Color color ) + +Draw a color-filled triangle ( Vertex in counter-clockwise order! ) + +- Failure return false +- Success return true +*/ +int lshapesDrawTriangle( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawTriangle( Vector2 v1, Vector2 v2, Vector2 v3, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector2 v3 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v1 = uluaGetVector2( L ); + + DrawTriangle( v1, v2, v3, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawTriangleLines( Vector2 v1, Vector2 v2, Vector2 v3, Color color ) + +Draw triangle outline ( Vertex in counter-clockwise order! ) + +- Failure return false +- Success return true +*/ +int lshapesDrawTriangleLines( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawTriangleLines( Vector2 v1, Vector2 v2, Vector2 v3, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector2 v3 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 v1 = uluaGetVector2( L ); + + DrawTriangleLines( v1, v2, v3, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Shapes - Collision +*/ + +/* +> collision = RL_CheckCollisionRecs( Rectangle rec1, Rectangle rec2 ) + +Check collision between two rectangles + +- Failure return nil +- Success return bool +*/ +int lshapesCheckCollisionRecs( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CheckCollisionRecs( Rectangle rec1, Rectangle rec2 )" ); + lua_pushnil( L ); + return 1; + } + Rectangle rect1 = uluaGetRectangle( L ); + lua_pop( L, 1 ); + Rectangle rect2 = uluaGetRectangle( L ); + + lua_pushboolean( L, CheckCollisionRecs( rect1, rect2 ) ); + + return 1; +} + +/* +> collision = RL_CheckCollisionCircles( Vector2 center1, float radius1, Vector2 center2, float radius2 ) + +Check collision between two circles + +- Failure return nil +- Success return bool +*/ +int lshapesCheckCollisionCircles( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_isnumber( L, -3 ) || !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CheckCollisionCircles( Vector2 center1, float radius1, Vector2 center2, float radius2 )" ); + lua_pushnil( L ); + return 1; + } + float radius2 = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 center2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + float radius1 = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 center1 = uluaGetVector2( L ); + + lua_pushboolean( L, CheckCollisionCircles( center1, radius1, center2, radius2 ) ); + + return 1; +} + +/* +> collision = RL_CheckCollisionCircleRec( Vector2 center, float radius, Rectangle rec ) + +Check collision between circle and rectangle + +- Failure return nil +- Success return bool +*/ +int lshapesCheckCollisionCircleRec( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CheckCollisionCircleRec( Vector2 center, float radius, Rectangle rec )" ); + lua_pushnil( L ); + return 1; + } + Rectangle rec = uluaGetRectangle( L ); + lua_pop( L, 1 ); + float radius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 center = uluaGetVector2( L ); + + lua_pushboolean( L, CheckCollisionCircleRec( center, radius, rec ) ); + + return 1; +} + +/* +> collision = RL_CheckCollisionPointRec( Vector2 point, Rectangle rec ) + +Check if point is inside rectangle + +- Failure return nil +- Success return bool +*/ +int lshapesCheckCollisionPointRec( lua_State *L ) { + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CheckCollisionPointRec( Vector2 point, Rectangle rec )" ); + lua_pushnil( L ); + return 1; + } + Rectangle rec = uluaGetRectangle( L ); + lua_pop( L, 1 ); + Vector2 point = uluaGetVector2( L ); + + lua_pushboolean( L, CheckCollisionPointRec( point, rec ) ); + + return 1; +} + +/* +> collision = RL_CheckCollisionPointCircle( Vector2 point, Vector2 center, float radius ) + +Check if point is inside circle + +- Failure return nil +- Success return bool +*/ +int lshapesCheckCollisionPointCircle( lua_State *L ) { + if ( !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CheckCollisionPointCircle( Vector2 point, Vector2 center, float radius )" ); + lua_pushnil( L ); + return 1; + } + float radius = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 center = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 point = uluaGetVector2( L ); + + lua_pushboolean( L, CheckCollisionPointCircle( point, center, radius ) ); + + return 1; +} + +/* +> collision = RL_CheckCollisionPointTriangle( Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3 ) + +Check if point is inside a triangle + +- Failure return nil +- Success return bool +*/ +int lshapesCheckCollisionPointTriangle( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CheckCollisionPointTriangle( Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3 )" ); + lua_pushnil( L ); + return 1; + } + Vector2 p3 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 p2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 p1 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 point = uluaGetVector2( L ); + + lua_pushboolean( L, CheckCollisionPointTriangle( point, p1, p2, p3 ) ); + + return 1; +} + +/* +> collision, position = RL_CheckCollisionLines( Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2 ) + +Check the collision between two lines defined by two points each, returns collision point by reference + +- Failure return nil +- Success return bool, Vector2 +*/ +int lshapesCheckCollisionLines( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CheckCollisionLines( Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2 )" ); + lua_pushnil( L ); + return 1; + } + Vector2 endPos2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 startPos2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 endPos1 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 startPos1 = uluaGetVector2( L ); + + Vector2 colPoint = { 0, 0 }; + + lua_pushboolean( L, CheckCollisionLines( startPos1, endPos1, startPos2, endPos2, &colPoint ) ); + uluaPushVector2( L, colPoint ); + + return 2; +} + +/* +> collision = RL_CheckCollisionPointLine( Vector2 point, Vector2 p1, Vector2 p2, int threshold ) + +Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] + +- Failure return nil +- Success return bool +*/ +int lshapesCheckCollisionPointLine( lua_State *L ) { + if ( !lua_istable( L, -4 ) || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_CheckCollisionPointLine( Vector2 point, Vector2 p1, Vector2 p2, int threshold )" ); + lua_pushnil( L ); + return 1; + } + int threshold = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + Vector2 p2 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 p1 = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 point = uluaGetVector2( L ); + + lua_pushboolean( L, CheckCollisionPointLine( point, p1, p2, threshold ) ); + + return 1; +} + +/* +> rectangle = RL_GetCollisionRec( Rectangle rec1, Rectangle rec2 ) + +Get collision rectangle for two rectangles collision + +- Failure return nil +- Success return Rectangle +*/ +int lshapesGetCollisionRec( lua_State *L ) { + /* Rectangle rec1, Rectangle rec2 */ + if ( !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetCollisionRec( Rectangle rec1, Rectangle rec2 )" ); + lua_pushnil( L ); + return 1; + } + Rectangle rec2 = uluaGetRectangle( L ); + lua_pop( L, 1 ); + Rectangle rec1 = uluaGetRectangle( L ); + + uluaPushRectangle( L, GetCollisionRec( rec1, rec2 ) ); + + return 1; +} diff --git a/src/state.c b/src/state.c new file mode 100644 index 0000000..d9b2ad4 --- /dev/null +++ b/src/state.c @@ -0,0 +1,194 @@ +#include "main.h" +#include "state.h" +#include "lua_core.h" +#include "textures.h" + +State *state; + +bool stateInit( const char *exePath ) { + state = malloc( sizeof( State ) ); + + state->exePath = malloc( STRING_LEN * sizeof( char ) ); + strncpy( state->exePath, exePath, STRING_LEN - 1 ); + + state->hasWindow = true; + state->run = true; + state->resolution = (Vector2){ 1024, 720 }; + state->luaState = NULL; + state->targetFPS = 60; + state->textureSource = TEXTURE_SOURCE_TEXTURE; + /* Images. */ + state->imageAlloc = ALLOC_PAGE_SIZE; + state->imageCount = 0; + state->images = malloc( state->imageAlloc * sizeof( Image* ) ); + /* Textures. */ + state->textureAlloc = ALLOC_PAGE_SIZE; + state->textureCount = 0; + state->textures = malloc( state->textureAlloc * sizeof( Texture2D* ) ); + /* RenderTextures. */ + state->renderTextureAlloc = ALLOC_PAGE_SIZE; + state->renderTextureCount = 0; + state->renderTextures = malloc( state->renderTextureAlloc * sizeof( RenderTexture2D* ) ); + /* Fonts. */ + state->fontAlloc = ALLOC_PAGE_SIZE; + state->fontCount = 1; + state->fonts = malloc( state->fontAlloc * sizeof( Font* ) ); + /* Sounds. */ + state->soundAlloc = ALLOC_PAGE_SIZE; + state->soundCount = 0; + state->sounds = malloc( state->soundAlloc * sizeof( Sound* ) ); + /* Camera3D's. */ + state->camera3DAlloc = ALLOC_PAGE_SIZE; + state->camera3DCount = 0; + state->camera3Ds = malloc( state->camera3DAlloc * sizeof( Camera3D* ) ); + /* Meshes. */ + state->meshAlloc = ALLOC_PAGE_SIZE; + state->meshCount = 0; + state->meshes = malloc( state->meshAlloc * sizeof( Mesh* ) ); + /* Materials. */ + state->materialAlloc = ALLOC_PAGE_SIZE; + state->materialCount = 1; + state->materials = malloc( state->materialAlloc * sizeof( Material* ) ); + /* Models. */ + state->modelAlloc = ALLOC_PAGE_SIZE; + state->modelCount = 0; + state->models = malloc( state->modelAlloc * sizeof( Model* ) ); + /* ModelsAnimations. */ + state->animationAlloc = ALLOC_PAGE_SIZE; + state->animationCount = 0; + state->animations = malloc( state->animationAlloc * sizeof( ModelAnimations* ) ); + /* Shaders. */ + state->shaderAlloc = ALLOC_PAGE_SIZE; + state->shaderCount = 0; + state->shaders = malloc( state->shaderAlloc * sizeof( Shader* ) ); + + for ( int i = 0; i < ALLOC_PAGE_SIZE; i++ ) { + state->images[i] = NULL; + state->textures[i] = NULL; + state->renderTextures[i] = NULL; + state->sounds[i] = NULL; + state->camera3Ds[i] = NULL; + state->meshes[i] = NULL; + state->models[i] = NULL; + state->animations[i] = NULL; + state->shaders[i] = NULL; + + /* The ones we want to save the first. */ + if ( 0 < i ) { + state->fonts[i] = NULL; + state->materials[i] = NULL; + } + } + + InitWindow( state->resolution.x, state->resolution.y, "ReiLua" ); + /* Has to be after InitWindod where opengl context is created. */ + state->materials[0] = malloc( sizeof( Material ) ); + *state->materials[0] = LoadMaterialDefault(); + state->fonts[0] = malloc( sizeof( Font ) ); + *state->fonts[0] = GetFontDefault(); + + if ( !IsWindowReady() ) { + state->hasWindow = false; + state->run = false; + } + else { + SetTargetFPS( state->targetFPS ); + } + + InitAudioDevice(); + state->run = luaInit(); + + return state->run; +} + +void stateFree() { + for ( int i = 0; i < state->imageCount; ++i ) { + if ( state->images[i] != NULL ) { + UnloadImage( *state->images[i] ); + free( state->images[i] ); + } + } + for ( int i = 0; i < state->textureCount; ++i ) { + if ( state->textures[i] != NULL ) { + UnloadTexture( *state->textures[i] ); + free( state->textures[i] ); + } + } + for ( int i = 0; i < state->renderTextureCount; ++i ) { + if ( state->renderTextures[i] != NULL ) { + UnloadRenderTexture( *state->renderTextures[i] ); + free( state->renderTextures[i] ); + } + } + for ( int i = 0; i < state->fontCount; ++i ) { + if ( state->fonts[i] != NULL ) { + UnloadFont( *state->fonts[i] ); + free( state->fonts[i] ); + } + } + for ( int i = 0; i < state->soundCount; ++i ) { + if ( state->sounds[i] != NULL ) { + UnloadSound( *state->sounds[i] ); + free( state->sounds[i] ); + } + } + for ( int i = 0; i < state->camera3DCount; ++i ) { + if ( state->camera3Ds[i] != NULL ) { + free( state->camera3Ds[i] ); + } + } + for ( int i = 0; i < state->modelCount; ++i ) { + if ( state->models[i] != NULL ) { + // UnloadModel( *state->models[i] ); + UnloadModelKeepMeshes( *state->models[i] ); + free( state->models[i] ); + } + } + for ( int i = 0; i < state->meshCount; ++i ) { + if ( state->meshes[i] != NULL ) { + UnloadMesh( *state->meshes[i] ); + free( state->meshes[i] ); + } + } + for ( int i = 0; i < state->materialCount; ++i ) { + if ( state->materials[i] != NULL ) { + UnloadMaterial( *state->materials[i] ); + free( state->materials[i] ); + } + } + for ( int i = 0; i < state->animationCount; ++i ) { + if ( state->animations[i] != NULL ) { + UnloadModelAnimations( state->animations[i]->animations, state->animations[i]->animCount ); + free( state->animations[i] ); + } + } + for ( int i = 0; i < state->shaderCount; ++i ) { + if ( state->shaders[i] != NULL ) { + UnloadShader( *state->shaders[i] ); + free( state->shaders[i] ); + } + } + + if ( IsAudioDeviceReady() ) { + CloseAudioDevice(); + UnloadMusicStream( state->music ); + } + if ( state->hasWindow ) { + CloseWindow(); + } + if ( state->luaState != NULL ) { + lua_close( state->luaState ); + } + free( state->images ); + free( state->textures ); + free( state->renderTextures ); + free( state->fonts ); + free( state->sounds ); + free( state->camera3Ds ); + free( state->meshes ); + free( state->materials ); + free( state->models ); + free( state->animations ); + free( state->shaders ); + free( state ); +} diff --git a/src/text.c b/src/text.c new file mode 100644 index 0000000..0637971 --- /dev/null +++ b/src/text.c @@ -0,0 +1,102 @@ +#include "main.h" +#include "state.h" +#include "text.h" +#include "lua_core.h" + +static void checkFontRealloc( int i ) { + if ( i == state->fontCount ) { + state->fontCount++; + } + + if ( state->fontCount == state->fontAlloc ) { + state->fontAlloc += ALLOC_PAGE_SIZE; + state->fonts = realloc( state->fonts, state->fontAlloc * sizeof( Font* ) ); + + for ( i = state->fontCount; i < state->fontAlloc; i++ ) { + state->fonts[i] = NULL; + } + } +} + +bool validFont( size_t id ) { + if ( id < 0 || state->fontCount < id || state->fonts[ id ] == NULL ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid font", id ); + return false; + } + else { + return true; + } +} + +/* +## Text - Loading +*/ + +/* +> font = RL_LoadFont( string fileName ) + +Load font from file into GPU memory ( VRAM ) + +- Failure return -1 +- Success return int +*/ +int lmodelsLoadFont( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadFont( string fileName )" ); + lua_pushinteger( L, -1 ); + return 1; + } + int i = 0; + + for ( i = 0; i < state->fontCount; i++ ) { + if ( state->fonts[i] == NULL ) { + break; + } + } + state->fonts[i] = malloc( sizeof( Font ) ); + *state->fonts[i] = LoadFont( lua_tostring( L, -1 ) ); + lua_pushinteger( L, i ); + checkFontRealloc( i ); + + return 1; +} + +/* +## Text - Draw +*/ + +/* +> success = RL_DrawText( Font font, string text, Vector2 position, float fontSize, float spacing, Color tint ) + +Draw text using font and additional parameters + +- Failure return false +- Success return true +*/ +int ltextDrawText( lua_State *L ) { + if ( !lua_isnumber( L, -6 ) || !lua_isstring( L, -5 ) || !lua_istable( L, -4 ) + || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawText( Font font, string text, Vector2 position, float fontSize, float spacing, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + float spacing = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float fontSize = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 position = uluaGetVector2( L ); + lua_pop( L, 1 ); + size_t fontId = lua_tointeger( L, -2 ); + + if ( !validFont( fontId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + DrawTextEx( *state->fonts[ fontId ], lua_tostring( L, -1 ), position, fontSize, spacing, color ); + lua_pushboolean( L, true ); + + return 1; +} diff --git a/src/textures.c b/src/textures.c new file mode 100644 index 0000000..b147fff --- /dev/null +++ b/src/textures.c @@ -0,0 +1,1057 @@ +#include "main.h" +#include "state.h" +#include "textures.h" +#include "text.h" +#include "lua_core.h" + +static void checkImageRealloc( int i ) { + if ( i == state->imageCount ) { + state->imageCount++; + } + + if ( state->imageCount == state->imageAlloc ) { + state->imageAlloc += ALLOC_PAGE_SIZE; + state->images = realloc( state->images, state->imageAlloc * sizeof( Image* ) ); + + for ( i = state->imageCount; i < state->imageAlloc; i++ ) { + state->images[i] = NULL; + } + } +} + +static void checkTextureRealloc( int i ) { + if ( i == state->textureCount ) { + state->textureCount++; + } + + if ( state->textureCount == state->textureAlloc ) { + state->textureAlloc += ALLOC_PAGE_SIZE; + state->textures = realloc( state->textures, state->textureAlloc * sizeof( Texture2D* ) ); + + for ( i = state->textureCount; i < state->textureAlloc; i++ ) { + state->textures[i] = NULL; + } + } +} + +static void checkRenderTextureRealloc( int i ) { + if ( i == state->renderTextureCount ) { + state->renderTextureCount++; + } + + if ( state->renderTextureCount == state->renderTextureAlloc ) { + state->renderTextureAlloc += ALLOC_PAGE_SIZE; + state->renderTextures = realloc( state->renderTextures, state->renderTextureAlloc * sizeof( RenderTexture2D* ) ); + + for ( i = state->renderTextureCount; i < state->renderTextureAlloc; i++ ) { + state->renderTextures[i] = NULL; + } + } +} + +bool validImage( size_t id ) { + if ( id < 0 || state->imageCount < id || state->images[ id ] == NULL ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid image", id ); + return false; + } + else { + return true; + } +} + +bool validTexture( size_t id ) { + if ( id < 0 || state->textureCount < id || state->textures[ id ] == NULL ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid texture", id ); + return false; + } + else { + return true; + } +} + +bool validRenderTexture( size_t id ) { + if ( id < 0 || state->renderTextureCount < id || state->renderTextures[ id ] == NULL ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid renderTexture", id ); + return false; + } + else { + return true; + } +} + +bool validSourceTexture( size_t id ) { + switch ( state->textureSource ) { + case TEXTURE_SOURCE_TEXTURE: + return validTexture( id ); + case TEXTURE_SOURCE_RENDER_TEXTURE: + return validRenderTexture( id ); + default: + return validTexture( id ); + break; + } +} + +Texture2D* texturesGetSourceTexture( size_t index ) { + switch ( state->textureSource ) { + case TEXTURE_SOURCE_TEXTURE: + return state->textures[ index ]; + case TEXTURE_SOURCE_RENDER_TEXTURE: + return &state->renderTextures[ index ]->texture; + default: + return state->textures[ index ]; + break; + } +} + +/* +## Textures - Load +*/ + +/* +> image = RL_LoadImage( string fileName ) + +Load image from file into CPU memory ( RAM ) + +- Failure return -1 +- Success return int +*/ +int ltexturesLoadImage( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadImage( string fileName )" ); + lua_pushinteger( L, -1 ); + return 1; + } + + if ( FileExists( lua_tostring( L, -1 ) ) ) { + int i = 0; + + for ( i = 0; i < state->imageCount; i++ ) { + if ( state->images[i] == NULL ) { + break; + } + } + state->images[i] = malloc( sizeof( Image ) ); + *state->images[i] = LoadImage( lua_tostring( L, -1 ) ); + lua_pushinteger( L, i ); + checkImageRealloc( i ); + return 1; + } + else { + lua_pushinteger( L, -1 ); + return 1; + } +} + +/* +> image = RL_GenImageColor( int width, int height, Color color ) + +Generate image: plain color + +- Failure return -1 +- Success return int +*/ +int ltexturesGenImageColor( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenImageColor( int width, int height, Color color )" ); + lua_pushinteger( L, -1 ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + int height = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + int width = lua_tointeger( L, -1 ); + int i = 0; + + for ( i = 0; i < state->imageCount; i++ ) { + if ( state->images[i] == NULL ) { + break; + } + } + state->images[i] = malloc( sizeof( Image ) ); + *state->images[i] = GenImageColor( width, height, color ); + lua_pushinteger( L, i ); + checkImageRealloc( i ); + + return 1; +} + +/* +> success = RL_UnloadImage( Image image ) + +Unload image from CPU memory ( RAM ) + +- Failure return false +- Success return true +*/ +int ltexturesUnloadImage( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_UnloadImage( Image image )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t id = lua_tointeger( L, -1 ); + + if ( !validImage( id ) ) { + lua_pushboolean( L, false ); + return 1; + } + UnloadImage( *state->images[ id ] ); + state->images[ id ] = NULL; + lua_pushboolean( L, true ); + + return 1; +} + +/* +> texture = RL_LoadTexture( string fileName ) + +Load texture from file into GPU memory ( VRAM ) + +- Failure return -1 +- Success return int +*/ +int ltexturesLoadTexture( lua_State *L ) { + if ( !lua_isstring( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadTexture( string fileName )" ); + lua_pushinteger( L, -1 ); + return 1; + } + + if ( FileExists( lua_tostring( L, -1 ) ) ) { + int i = 0; + + for ( i = 0; i < state->textureCount; i++ ) { + if ( state->textures[i] == NULL ) { + break; + } + } + state->textures[i] = malloc( sizeof( Texture2D ) ); + *state->textures[i] = LoadTexture( lua_tostring( L, -1 ) ); + lua_pushinteger( L, i ); + checkTextureRealloc( i ); + return 1; + } + else { + lua_pushinteger( L, -1 ); + return 1; + } +} + +/* +> texture = RL_LoadTextureFromImage( Image image ) + +Load texture from image data + +- Failure return -1 +- Success return int +*/ +int ltexturesLoadTextureFromImage( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadTextureFromImage( Image image )" ); + lua_pushinteger( L, -1 ); + return 1; + } + size_t imageId = lua_tointeger( L, -1 ); + + if ( !validImage( imageId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + int i = 0; + + for ( i = 0; i < state->textureCount; i++ ) { + if ( state->textures[i] == NULL ) { + break; + } + } + state->textures[i] = malloc( sizeof( Texture2D ) ); + *state->textures[i] = LoadTextureFromImage( *state->images[ imageId ] ); + lua_pushinteger( L, i ); + checkTextureRealloc( i ); + + return 1; +} + +/* +> success = RL_UnloadTexture( Texture2D texture ) + +Unload texture from GPU memory ( VRAM ) + +- Failure return false +- Success return true +*/ +int ltexturesUnloadTexture( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_UnloadTexture( Texture2D texture )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t id = lua_tointeger( L, -1 ); + + if ( !validTexture( id ) ) { + lua_pushboolean( L, false ); + return 1; + } + UnloadTexture( *state->textures[ id ] ); + state->textures[ id ] = NULL; + lua_pushboolean( L, true ); + + return 1; +} + +/* +> renderTexture = RL_LoadRenderTexture( Vector2 size ) + +Load texture for rendering ( framebuffer ) + +- Failure return -1 +- Success return int +*/ +int ltexturesLoadRenderTexture( lua_State *L ) { + if ( !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_LoadRenderTexture( Vector2 size )" ); + lua_pushinteger( L, -1 ); + return 1; + } + Vector2 size = uluaGetVector2( L ); + int i = 0; + + for ( i = 0; i < state->renderTextureCount; i++ ) { + if ( state->renderTextures[i] == NULL ) { + break; + } + } + state->renderTextures[i] = malloc( sizeof( RenderTexture2D ) ); + *state->renderTextures[i] = LoadRenderTexture( (int)size.x, (int)size.y ); + lua_pushinteger( L, i ); + checkRenderTextureRealloc( i ); + + return 1; +} + +/* +> success = RL_UnloadRenderTexture( RenderTexture2D target ) + +Unload render texture from GPU memory ( VRAM ) + +- Failure return false +- Success return true +*/ +int ltexturesUnloadRenderTexture( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_UnloadRenderTexture( RenderTexture2D target )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t id = lua_tointeger( L, -1 ); + + if ( !validRenderTexture( id ) ) { + lua_pushboolean( L, false ); + return 1; + } + UnloadRenderTexture( *state->renderTextures[ id ] ); + state->renderTextures[ id ] = NULL; + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Textures - Image Drawing +*/ + +/* +> success = RL_ImageClearBackground( Image dst, Color color ) + +Clear image background with given color + +- Failure return false +- Success return true +*/ +int ltexturesImageClearBackground( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_ImageClearBackground( Image dst, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + size_t imageId = lua_tointeger( L, -1 ); + + if ( !validImage( imageId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + ImageClearBackground( state->images[ imageId ], color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_ImageDrawPixel( Image dst, Vector2 position, Color color ) + +Draw pixel within an image + +- Failure return false +- Success return true +*/ +int ltexturesImageDrawPixel( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_ImageDrawPixel( Image dst, Vector2 position, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector2 position = uluaGetVector2( L ); + lua_pop( L, 1 ); + size_t imageId = lua_tointeger( L, -1 ); + + if ( !validImage( imageId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + ImageDrawPixelV( state->images[ imageId ], position, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_ImageDrawLine( Image dst, Vector2 start, Vector2 end, Color color ) + +Draw line within an image + +- Failure return false +- Success return true +*/ +int ltexturesImageDrawLine( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_ImageDrawLine( Image dst, Vector2 start, Vector2 end, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector2 end = uluaGetVector2( L ); + lua_pop( L, 1 ); + Vector2 start = uluaGetVector2( L ); + lua_pop( L, 1 ); + size_t imageId = lua_tointeger( L, -1 ); + + if ( !validImage( imageId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + ImageDrawLineV( state->images[ imageId ], start, end, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_ImageDrawCircle( Image dst, Vector2 center, int radius, Color color ) + +Draw circle within an image + +- Failure return false +- Success return true +*/ +int ltexturesImageDrawCircle( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_ImageDrawCircle( Image dst, Vector2 center, int radius, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + int radius = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + Vector2 center = uluaGetVector2( L ); + lua_pop( L, 1 ); + size_t imageId = lua_tointeger( L, -1 ); + + if ( !validImage( imageId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + ImageDrawCircleV( state->images[ imageId ], center, radius, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_ImageDrawRectangle( Image dst, Rectangle rec, Color color ) + +Draw rectangle within an image + +- Failure return false +- Success return true +*/ +int ltexturesImageDrawRectangle( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_ImageDrawRectangle( Image dst, Rectangle rec, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Rectangle rec = uluaGetRectangle( L ); + lua_pop( L, 1 ); + size_t imageId = lua_tointeger( L, -1 ); + + if ( !validImage( imageId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + ImageDrawRectangleRec( state->images[ imageId ], rec, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawRectangleLines( Image dst, Rectangle rec, int thick, Color color ) + +Draw rectangle lines within an image + +- Failure return false +- Success return true +*/ +int ltexturesImageDrawRectangleLines( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawRectangleLines( Image dst, Rectangle rec, int thick, Color color )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + int thick = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + Rectangle rec = uluaGetRectangle( L ); + lua_pop( L, 1 ); + size_t imageId = lua_tointeger( L, -1 ); + + if ( !validImage( imageId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + ImageDrawRectangleLines( state->images[ imageId ], rec, thick, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_ImageDraw( Image dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint ) + +Draw a source image within a destination image ( Tint applied to source ) + +- Failure return false +- Success return true +*/ +int ltexturesImageDraw( lua_State *L ) { + if ( !lua_isnumber( L, -5 ) || !lua_isnumber( L, -4 ) || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_ImageDraw( Image dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color tint = uluaGetColor( L ); + lua_pop( L, 1 ); + Rectangle dstRec = uluaGetRectangle( L ); + lua_pop( L, 1 ); + Rectangle srcRec = uluaGetRectangle( L ); + lua_pop( L, 1 ); + size_t imageSrcId = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + size_t imageDstId = lua_tointeger( L, -1 ); + + if ( !validImage( imageDstId ) || !validImage( imageSrcId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + ImageDraw( state->images[ imageDstId ], *state->images[ imageSrcId ], srcRec, dstRec, tint ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_ImageDrawTextEx( Image dst, Font font, string text, Vector2 position, float fontSize, float spacing, Color tint ) + +Draw text ( Custom sprite font ) within an image ( Destination ) + +- Failure return false +- Success return true +*/ +int ltexturesImageDrawTextEx( lua_State *L ) { + if ( !lua_isnumber( L, -7 ) || !lua_isnumber( L, -6 ) || !lua_isstring( L, -5 ) || !lua_istable( L, -4 ) + || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_ImageDrawTextEx( Image dst, Font font, string text, Vector2 position, float fontSize, float spacing, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color tint = uluaGetColor( L ); + lua_pop( L, 1 ); + float spacing = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float fontSize = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 position = uluaGetVector2( L ); + lua_pop( L, 1 ); + size_t fontId = lua_tointeger( L, -2 ); + size_t imageId = lua_tointeger( L, -3 ); + + if ( !validImage( imageId ) || !validFont( fontId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + ImageDrawTextEx( state->images[ imageId ], *state->fonts[ fontId ], lua_tostring( L, -1 ), position, fontSize, spacing, tint ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +## Textures - Texture Drawing +*/ + +/* +> success = RL_DrawTexture( Texture2D texture, Vector2 position, Color tint ) + +Draw a Texture2D + +- Failure return false +- Success return true +*/ +int ltexturesDrawTexture( lua_State *L ) { + if ( !lua_isnumber( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawTexture( Texture2D texture, Vector2 position, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector2 pos = uluaGetVector2( L ); + lua_pop( L, 1 ); + size_t texId = lua_tointeger( L, -1 ); + + if ( !validSourceTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + DrawTexture( *texturesGetSourceTexture( texId ), pos.x, pos.y, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawTextureRec( Texture2D texture, Rectangle source, Vector2 position, Color tint ) + +Draw a part of a texture defined by a rectangle + +- Failure return false +- Success return true +*/ +int ltexturesDrawTextureRec( lua_State *L ) { + if ( !lua_isnumber( L, -4 ) || !lua_istable( L, -3 ) || !lua_istable( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawTextureRec( Texture2D texture, Rectangle source, Vector2 position, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + Vector2 pos = uluaGetVector2( L ); + lua_pop( L, 1 ); + Rectangle srcRect = uluaGetRectangle( L ); + lua_pop( L, 1 ); + size_t texId = lua_tointeger( L, -1 ); + + if ( !validSourceTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + DrawTextureRec( *texturesGetSourceTexture( texId ), srcRect, pos, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawTextureTiled( Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint ) + +Draw part of a texture ( defined by a rectangle ) with rotation and scale tiled into dest + +- Failure return false +- Success return true +*/ +int ltexturesDrawTextureTiled( lua_State *L ) { + if ( !lua_isnumber( L, -7 ) || !lua_istable( L, -6 ) || !lua_istable( L, -5 ) || !lua_istable( L, -4 ) + || !lua_isnumber( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawTextureTiled( Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + float scale = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + float rot = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 origin = uluaGetVector2( L ); + lua_pop( L, 1 ); + Rectangle dstRect = uluaGetRectangle( L ); + lua_pop( L, 1 ); + Rectangle srcRect = uluaGetRectangle( L ); + lua_pop( L, 1 ); + size_t texId = lua_tointeger( L, -1 ); + + if ( !validSourceTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + DrawTextureTiled( *texturesGetSourceTexture( texId ), srcRect, dstRect, origin, rot, scale, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawTexturePro( Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint ) + +Draw a part of a texture defined by a rectangle with "pro" parameters + +- Failure return false +- Success return true +*/ +int ltexturesDrawTexturePro( lua_State *L ) { + if ( !lua_isnumber( L, -6 ) || !lua_istable( L, -5 ) || !lua_istable( L, -4 ) + || !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawTexturePro( Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + float rot = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 origin = uluaGetVector2( L ); + lua_pop( L, 1 ); + Rectangle dstRect = uluaGetRectangle( L ); + lua_pop( L, 1 ); + Rectangle srcRect = uluaGetRectangle( L ); + lua_pop( L, 1 ); + size_t texId = lua_tointeger( L, -1 ); + + if ( !validSourceTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + DrawTexturePro( *texturesGetSourceTexture( texId ), srcRect, dstRect, origin, rot, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawTextureNPatch( Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint ) + +Draws a texture ( or part of it ) that stretches or shrinks nicely + +- Failure return false +- Success return true +*/ +int ltexturesDrawTextureNPatch( lua_State *L ) { + if ( !lua_isnumber( L, -6 ) || !lua_istable( L, -5 ) || !lua_istable( L, -4 ) + || !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawTextureNPatch( Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color tint = uluaGetColor( L ); + lua_pop( L, 1 ); + float rotation = lua_tonumber( L, -1 ); + lua_pop( L, 1 ); + Vector2 origin = uluaGetVector2( L ); + lua_pop( L, 1 ); + Rectangle dest = uluaGetRectangle( L ); + lua_pop( L, 1 ); + NPatchInfo nPatchInfo = uluaGetNPatchInfo( L ); + lua_pop( L, 1 ); + size_t texId = lua_tointeger( L, -1 ); + + if ( !validSourceTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + DrawTextureNPatch( *texturesGetSourceTexture( texId ), nPatchInfo, dest, origin, rotation, tint ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_DrawTexturePoly( Texture2D texture, Vector2 center, Vector2{} points, Vector2{} texcoords, int pointsCount, Color tint ) + +Draw a textured polygon ( Convex ) + +- Failure return false +- Success return true +*/ +int ltexturesDrawTexturePoly( lua_State *L ) { + if ( !lua_isnumber( L, -6 ) || !lua_istable( L, -5 ) || !lua_istable( L, -4 ) + || !lua_istable( L, -3 ) || !lua_isnumber( L, -2 ) || !lua_istable( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_DrawTexturePoly( Texture2D texture, Vector2 center, Vector2 points{}, Vector2 texcoords{}, int pointsCount, Color tint )" ); + lua_pushboolean( L, false ); + return 1; + } + Color color = uluaGetColor( L ); + lua_pop( L, 1 ); + int pointsCount = lua_tointeger( L, -1 ); + lua_pop( L, 1 ); + Vector2 texCoords[ pointsCount ]; + + int t = lua_gettop( L ), i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + texCoords[i] = uluaGetVector2( L ); + i++; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + + Vector2 points[ pointsCount ]; + + t = lua_gettop( L ); + i = 0; + lua_pushnil( L ); + + while ( lua_next( L, t ) != 0 ) { + points[i] = uluaGetVector2( L ); + i++; + lua_pop( L, 1 ); + } + lua_pop( L, 1 ); + Vector2 center = uluaGetVector2( L ); + lua_pop( L, 1 ); + size_t texId = lua_tointeger( L, -1 ); + + if ( !validSourceTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + DrawTexturePoly( *texturesGetSourceTexture( texId ), center, points, texCoords, pointsCount, color ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_BeginTextureMode( RenderTexture2D target ) + +Begin drawing to render texture + +- Failure return false +- Success return true +*/ +int ltexturesBeginTextureMode( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_BeginTextureMode( RenderTexture2D target )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t texId = lua_tointeger( L, -1 ); + + if ( !validRenderTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + BeginTextureMode( *state->renderTextures[ texId ] ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> RL_EndTextureMode() + +Ends drawing to render texture +*/ +int ltexturesEndTextureMode( lua_State *L ) { + EndTextureMode(); + + return 1; +} + +/* +> success = RL_SetTextureSource( int textureSource ) + +Set what texture source to use ( TEXTURE_SOURCE_TEXTURE or TEXTURE_SOURCE_RENDER_TEXTURE ) + +- Failure return false +- Success return true +*/ +int ltexturesSetTextureSource( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetTextureSource( int textureSource )" ); + lua_pushboolean( L, false ); + return 1; + } + int texSource = lua_tointeger( L, -1 ); + + if ( texSource != TEXTURE_SOURCE_TEXTURE && texSource != TEXTURE_SOURCE_RENDER_TEXTURE ) { + TraceLog( LOG_WARNING, "%s %d", "Invalid source texture", texSource ); + lua_pushboolean( L, false ); + return 1; + } + + state->textureSource = texSource; + lua_pushboolean( L, true ); + + return 1; +} + +/* +> textureSource = RL_GetTextureSource() + +Get current texture source type ( TEXTURE_SOURCE_TEXTURE or TEXTURE_SOURCE_RENDER_TEXTURE ) + +- Success return int +*/ +int ltexturesGetTextureSource( lua_State *L ) { + lua_pushinteger( L, state->textureSource ); + + return 1; +} + +/* +## Textures - Configure +*/ + +/* +> success = RL_GenTextureMipmaps( Texture2D texture ) + +Generate GPU mipmaps for a texture + +- Failure return false +- Success return true +*/ +int ltexturesGenTextureMipmaps( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GenTextureMipmaps( Texture2D texture )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t texId = lua_tointeger( L, -1 ); + + if ( !validTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + GenTextureMipmaps( texturesGetSourceTexture( texId ) ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetTextureFilter( Texture2D texture, int filter ) + +Set texture scaling filter mode ( TEXTURE_FILTER_POINT, TEXTURE_FILTER_BILINEAR... ) + +- Failure return false +- Success return true +*/ +int ltexturesSetTextureFilter( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetTextureFilter( Texture2D texture, int filter )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t texId = lua_tointeger( L, -2 ); + + if ( !validTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + SetTextureFilter( *texturesGetSourceTexture( texId ), lua_tointeger( L, -1 ) ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> success = RL_SetTextureWrap( Texture2D texture, int wrap ) + +Set texture wrapping mode ( TEXTURE_WRAP_REPEAT, TEXTURE_WRAP_CLAMP... ) + +- Failure return false +- Success return true +*/ +int ltexturesSetTextureWrap( lua_State *L ) { + if ( !lua_isnumber( L, -2 ) || !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_SetTextureWrap( Texture2D texture, int wrap )" ); + lua_pushboolean( L, false ); + return 1; + } + size_t texId = lua_tointeger( L, -2 ); + + if ( !validTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + SetTextureWrap( *state->textures[ texId ], lua_tointeger( L, -1 ) ); + lua_pushboolean( L, true ); + + return 1; +} + +/* +> size = RL_GetTextureSize( Texture2D texture ) + +Get texture size + +- Failure return nil +- Success return Vector2 +*/ +int ltexturesGetTextureSize( lua_State *L ) { + if ( !lua_isnumber( L, -1 ) ) { + TraceLog( LOG_WARNING, "%s", "Bad call of function. RL_GetTextureSize( Texture2D texture )" ); + lua_pushnil( L ); + return 1; + } + size_t texId = lua_tointeger( L, -1 ); + + if ( !validSourceTexture( texId ) ) { + lua_pushboolean( L, false ); + return 1; + } + + Texture2D texture = *texturesGetSourceTexture( texId ); + uluaPushVector2( L, (Vector2){ texture.width, texture.height } ); + + return 1; +}