3 Commits

Author SHA1 Message Date
Ray
3d671a6dd4 Merge branch 'master' of https://github.com/raysan5/raylib 2026-08-04 21:16:17 +02:00
Ray
7711de62fb Code gardening 2026-08-04 21:16:08 +02:00
37712a8fbe [rshapes] Fix Draw*LinesEx() functions' inconsistent handling of thick parameter. (#6035)
* [rshapes] Update `DrawRectangleLinesEx()`

Add support for negative `thick` values. When `thick` is negative, lines are drawn growing outside of the rectangle.

* [rshapes] Update `DrawPolyLinesEx()`

Add support for negative `thick` values. When `thick` is negative, lines are drawn growing outside of the polygon. Also refactored to explicitly use an `outerRadius` variable instead of using `radius` as the outer radius.

* [rshapes] Update `DrawRectangleRoundedLinesEx()`

Make the outline vertices easier to work with.

* [rshapes] Updated `DrawRectangleRoundedLinesEx()`.

Lines used to draw outside when `thick` was positive. Added support for negative `thick` values. When `thick` is negative, lines are drawn growing outside of the rectangle.

* [rshapes] Update `DrawRectangleRoundedLines()`.

Make the formatting of the points' x and y coordinates in DrawRectangleRoundedLines() match DrawRectangleRoundedLinesEx().
2026-08-04 18:04:56 +02:00
15 changed files with 224 additions and 110 deletions

2
src/external/rlsw.h vendored
View File

@ -774,7 +774,7 @@ SWAPI void swGetFramebufferAttachmentParameteriv(SWattachment attachment, SWatta
// Simple log system to avoid printf() calls if required // Simple log system to avoid printf() calls if required
// NOTE: Avoiding those calls, also avoids const strings memory usage // NOTE: Avoiding those calls, also avoids const strings memory usage
#define RLSW_SUPPORT_LOG_INFO #define RLSW_SUPPORT_LOG_INFO
#if defined(RLSW_SUPPORT_LOG_INFO) //&& defined(_DEBUG) // WARNING: LOG() output required for this tool #if defined(RLSW_SUPPORT_LOG_INFO)
#include <stdio.h> #include <stdio.h>
#define SW_LOG(...) printf(__VA_ARGS__) #define SW_LOG(...) printf(__VA_ARGS__)
#else #else

View File

@ -757,12 +757,12 @@ void PollInputEvents(void)
for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
// Reset last gamepad button/axis registered state // Reset last gamepad button/axis registered state
CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
//CORE.Input.Gamepad.axisCount = 0; //CORE.Input.Gamepad.axisCount = 0;
for (int i = 0; i < MAX_GAMEPADS; i++) for (int i = 0; i < MAX_GAMEPADS; i++)
{ {
if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available
{ {
// Register previous gamepad states // Register previous gamepad states
for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++)

View File

@ -1613,11 +1613,11 @@ void PollInputEvents(void)
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// using RGFW callbacks instead of polling // Using RGFW callbacks instead of polling
RGFW_pollEvents(); RGFW_pollEvents();
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
mg_event gamepad_event; mg_event gamepad_event = { 0 };
while (mg_gamepads_check_event(&platform.minigamepad, &gamepad_event)) while (mg_gamepads_check_event(&platform.minigamepad, &gamepad_event))
{ {
int gamepadIndex = gamepad_event.gamepad->index; int gamepadIndex = gamepad_event.gamepad->index;

View File

@ -1652,7 +1652,7 @@ void PollInputEvents(void)
} break; } break;
#endif #endif
// Keyboard events // Check keyboard events
case SDL_KEYDOWN: case SDL_KEYDOWN:
{ {
#if defined(USING_VERSION_SDL3) #if defined(USING_VERSION_SDL3)
@ -1680,10 +1680,8 @@ void PollInputEvents(void)
if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey]) CORE.Window.shouldClose = true; if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey]) CORE.Window.shouldClose = true;
} break; } break;
case SDL_KEYUP: case SDL_KEYUP:
{ {
#if defined(USING_VERSION_SDL3) #if defined(USING_VERSION_SDL3)
KeyboardKey key = ConvertScancodeToKey(event.key.scancode); KeyboardKey key = ConvertScancodeToKey(event.key.scancode);
#else #else
@ -1691,7 +1689,6 @@ void PollInputEvents(void)
#endif #endif
if (key != KEY_NULL) CORE.Input.Keyboard.currentKeyState[key] = 0; if (key != KEY_NULL) CORE.Input.Keyboard.currentKeyState[key] = 0;
} break; } break;
case SDL_TEXTINPUT: case SDL_TEXTINPUT:
{ {
// NOTE: event.text.text data comes an UTF-8 text sequence but register codepoints (int) // NOTE: event.text.text data comes an UTF-8 text sequence but register codepoints (int)
@ -1700,7 +1697,6 @@ void PollInputEvents(void)
if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE)
{ {
// Add character (codepoint) to the queue // Add character (codepoint) to the queue
#if defined(USING_VERSION_SDL3) #if defined(USING_VERSION_SDL3)
size_t textLen = strlen(event.text.text); size_t textLen = strlen(event.text.text);
unsigned int codepoint = (unsigned int)SDL_StepUTF8(&event.text.text, &textLen); unsigned int codepoint = (unsigned int)SDL_StepUTF8(&event.text.text, &textLen);
@ -1769,6 +1765,7 @@ void PollInputEvents(void)
touchAction = 2; touchAction = 2;
} break; } break;
// Check Touch events
case SDL_FINGERDOWN: case SDL_FINGERDOWN:
{ {
UpdateTouchPointsSDL(event.tfinger); UpdateTouchPointsSDL(event.tfinger);
@ -1788,24 +1785,21 @@ void PollInputEvents(void)
realTouch = true; realTouch = true;
} break; } break;
// Check gamepad events // Check Gamepad events
case SDL_JOYDEVICEADDED: case SDL_JOYDEVICEADDED:
{ {
int jid = event.jdevice.which; // Joystick device index int jid = event.jdevice.which; // Joystick device index
// check if already added at InitPlatform // Check if already added at InitPlatform
for (int i = 0; i < MAX_GAMEPADS; i++) for (int i = 0; i < MAX_GAMEPADS; i++)
{ {
if (jid == platform.gamepadId[i]) if (jid == platform.gamepadId[i]) return;
{
return;
}
} }
int nextAvailableSlot = 0; int nextAvailableSlot = 0;
while (nextAvailableSlot < MAX_GAMEPADS && CORE.Input.Gamepad.ready[nextAvailableSlot]) while (nextAvailableSlot < MAX_GAMEPADS && CORE.Input.Gamepad.ready[nextAvailableSlot])
{ {
++nextAvailableSlot; nextAvailableSlot++;
} }
if ((nextAvailableSlot < MAX_GAMEPADS) && !CORE.Input.Gamepad.ready[nextAvailableSlot]) if ((nextAvailableSlot < MAX_GAMEPADS) && !CORE.Input.Gamepad.ready[nextAvailableSlot])

View File

@ -1071,7 +1071,7 @@ void PollInputEvents(void)
CORE.Input.Keyboard.charPressedQueueCount = 0; CORE.Input.Keyboard.charPressedQueueCount = 0;
// Reset last gamepad button/axis registered state // Reset last gamepad button/axis registered state
CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
//CORE.Input.Gamepad.axisCount = 0; //CORE.Input.Gamepad.axisCount = 0;
// Register previous keys states // Register previous keys states
@ -1118,7 +1118,7 @@ void PollInputEvents(void)
// NOTE: For DRM touchscreen devices, this mapping is disabled to avoid false touch detection // NOTE: For DRM touchscreen devices, this mapping is disabled to avoid false touch detection
// CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; // CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition;
// Handle the mouse/touch/gestures events: // Handle the mouse/touch/gestures events
PollMouseEvents(); PollMouseEvents();
} }
@ -1821,7 +1821,7 @@ static void ProcessKeyboard(void)
if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1; if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1;
else else
{ {
if (keysBuffer[i + 1] == 0x5b) // Special function key if (keysBuffer[i + 1] == 0x5b) // Special function key
{ {
if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32))
{ {
@ -1938,7 +1938,8 @@ static void InitEvdevInput(void)
(strncmp("mouse", entity->d_name, strlen("mouse")) == 0)) // Search for devices named "mouse*" (strncmp("mouse", entity->d_name, strlen("mouse")) == 0)) // Search for devices named "mouse*"
{ {
snprintf(path, MAX_FILEPATH_LENGTH, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); snprintf(path, MAX_FILEPATH_LENGTH, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name);
ConfigureEvdevDevice(path); // Configure the device if appropriate
ConfigureEvdevDevice(path); // Configure the device if appropriate
} }
} }

View File

@ -477,7 +477,7 @@ void PollInputEvents(void)
if (kbhit()) if (kbhit())
{ {
int key = getch(); int key = getch();
if (key == 27) CORE.Window.shouldClose = true; // KEY_SCAPE if (key == 27) CORE.Window.shouldClose = true; // KEY_ESCAPE
} }
} }

View File

@ -1086,7 +1086,7 @@ void PollInputEvents(void)
CORE.Input.Keyboard.charPressedQueueCount = 0; CORE.Input.Keyboard.charPressedQueueCount = 0;
// Reset last gamepad button/axis registered state // Reset last gamepad button/axis registered state
CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
//CORE.Input.Gamepad.axisCount = 0; //CORE.Input.Gamepad.axisCount = 0;
// Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback)
@ -1128,7 +1128,7 @@ void PollInputEvents(void)
// Register previous gamepad button states // Register previous gamepad button states
for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k];
EmscriptenGamepadEvent gamepadState; EmscriptenGamepadEvent gamepadState = { 0 };
int result = emscripten_get_gamepad_status(i, &gamepadState); int result = emscripten_get_gamepad_status(i, &gamepadState);
@ -1161,7 +1161,7 @@ void PollInputEvents(void)
default: break; default: break;
} }
if (button + 1 != 0) // Check for valid button if (button + 1 != 0) // Check for valid button
{ {
if (gamepadState.digitalButton[j] == 1) if (gamepadState.digitalButton[j] == 1)
{ {

View File

@ -1063,7 +1063,7 @@ void PollInputEvents(void)
CORE.Input.Keyboard.charPressedQueueCount = 0; CORE.Input.Keyboard.charPressedQueueCount = 0;
// Reset last gamepad button/axis registered state // Reset last gamepad button/axis registered state
CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
//CORE.Input.Gamepad.axisCount = 0; //CORE.Input.Gamepad.axisCount = 0;
// Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback)
@ -1134,7 +1134,7 @@ void PollInputEvents(void)
default: break; default: break;
} }
if (button + 1 != 0) // Check for valid button if (button + 1 != 0) // Check for valid button
{ {
if (gamepadState.digitalButton[j] == 1) if (gamepadState.digitalButton[j] == 1)
{ {

View File

@ -2660,7 +2660,7 @@ static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 fr
const float localVolume = buffer->volume; const float localVolume = buffer->volume;
const ma_uint32 channels = AUDIO.System.device.playback.channels; const ma_uint32 channels = AUDIO.System.device.playback.channels;
if (channels == 2) // Consider panning if (channels == 2) // Consider panning
{ {
const float right = (buffer->pan + 1.0f)/2.0f; // Normalize: [-1..1] -> [0..1] const float right = (buffer->pan + 1.0f)/2.0f; // Normalize: [-1..1] -> [0..1]
const float left = 1.0f - right; const float left = 1.0f - right;

View File

@ -353,7 +353,7 @@ void ProcessGestureEvent(GestureEvent event)
GESTURES.Drag.vector.y = GESTURES.Touch.moveDownPositionA.y - GESTURES.Touch.downDragPosition.y; GESTURES.Drag.vector.y = GESTURES.Touch.moveDownPositionA.y - GESTURES.Touch.downDragPosition.y;
} }
} }
else if (GESTURES.Touch.pointCount == 2) // Two touch points else if (GESTURES.Touch.pointCount == 2)
{ {
if (event.touchAction == TOUCH_ACTION_DOWN) if (event.touchAction == TOUCH_ACTION_DOWN)
{ {

View File

@ -2377,7 +2377,7 @@ void rlglClose(void)
// NOTE: External loader function must be provided // NOTE: External loader function must be provided
void rlLoadExtensions(void *loader) void rlLoadExtensions(void *loader)
{ {
#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 #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) // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions)
if (gladLoadGL((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); if (gladLoadGL((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL extensions");
else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL extensions loaded successfully");
@ -2508,7 +2508,7 @@ void rlLoadExtensions(void *loader)
} }
// Check instanced rendering support // Check instanced rendering support
if (strstr(extList[i], (const char *)"instanced_arrays") != NULL) // Broad check for instanced_arrays if (strstr(extList[i], (const char *)"instanced_arrays") != NULL) // Broad check for instanced_arrays
{ {
// Specific check // Specific check
if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // ANGLE if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // ANGLE

View File

@ -5348,7 +5348,7 @@ static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPat
image = LoadImage(TextFormat("%s/%s", texPath, cgltfImage->uri)); image = LoadImage(TextFormat("%s/%s", texPath, cgltfImage->uri));
} }
} }
else if ((cgltfImage->buffer_view != NULL) && (cgltfImage->buffer_view->buffer->data != NULL)) // Check if image is provided as data buffer else if ((cgltfImage->buffer_view != NULL) && (cgltfImage->buffer_view->buffer->data != NULL)) // Check if image is provided as data buffer
{ {
unsigned char *data = (unsigned char *)RL_MALLOC(cgltfImage->buffer_view->size); unsigned char *data = (unsigned char *)RL_MALLOC(cgltfImage->buffer_view->size);
int offset = (int)cgltfImage->buffer_view->offset; int offset = (int)cgltfImage->buffer_view->offset;
@ -5776,7 +5776,7 @@ static Model LoadGLTF(const char *fileName)
else TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data format not supported, use vec3 float", fileName); else TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data format not supported, use vec3 float", fileName);
} }
} }
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_normal) // NORMAL, vec3, float else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_normal) // NORMAL, vec3, float
{ {
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
@ -5876,7 +5876,7 @@ static Model LoadGLTF(const char *fileName)
else TRACELOG(LOG_WARNING, "MODEL: [%s] Normals attribute data format not supported, use vec3 float", fileName); else TRACELOG(LOG_WARNING, "MODEL: [%s] Normals attribute data format not supported, use vec3 float", fileName);
} }
} }
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_tangent) // TANGENT, vec4, float, w is tangent basis sign else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_tangent) // TANGENT, vec4, float, w is tangent basis sign
{ {
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
@ -5913,7 +5913,7 @@ static Model LoadGLTF(const char *fileName)
if (attribute->type == cgltf_type_vec2) if (attribute->type == cgltf_type_vec2)
{ {
if (attribute->component_type == cgltf_component_type_r_32f) // vec2, float if (attribute->component_type == cgltf_component_type_r_32f) // vec2, float
{ {
// Init raylib mesh texcoords to copy glTF attribute data // Init raylib mesh texcoords to copy glTF attribute data
texcoordPtr = (float *)RL_MALLOC(attribute->count*2*sizeof(float)); texcoordPtr = (float *)RL_MALLOC(attribute->count*2*sizeof(float));
@ -5962,7 +5962,7 @@ static Model LoadGLTF(const char *fileName)
if (texcoordPtr != NULL) RL_FREE(texcoordPtr); if (texcoordPtr != NULL) RL_FREE(texcoordPtr);
} }
} }
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_color) // COLOR_n, vec3/vec4, float/u8n/u16n else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_color) // COLOR_n, vec3/vec4, float/u8n/u16n
{ {
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;
@ -5971,7 +5971,7 @@ static Model LoadGLTF(const char *fileName)
if (model.meshes[meshIndex].colors != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Colors attribute data already loaded", fileName); if (model.meshes[meshIndex].colors != NULL) TRACELOG(LOG_WARNING, "MODEL: [%s] Colors attribute data already loaded", fileName);
else else
{ {
if (attribute->type == cgltf_type_vec3) // RGB if (attribute->type == cgltf_type_vec3) // RGB
{ {
if (attribute->component_type == cgltf_component_type_r_8u) if (attribute->component_type == cgltf_component_type_r_8u)
{ {
@ -6264,7 +6264,7 @@ static Model LoadGLTF(const char *fileName)
} }
else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported", fileName); else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported", fileName);
} }
else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_weights) // WEIGHTS_n (vec4, u8n/u16n/f32) else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_weights) // WEIGHTS_n (vec4, u8n/u16n/f32)
{ {
cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data;

View File

@ -637,24 +637,53 @@ void DrawRectangleLinesEx(Rectangle rec, float thick, Color color)
else if (rec.width <= rec.height) thick = rec.width/2; else if (rec.width <= rec.height) thick = rec.width/2;
} }
// When rec = { x, y, 8.0f, 6.0f } and thick = 2, the following
// four rectangles are drawn ([T]op, [B]ottom, [L]eft, [R]ight):
//
// TTTTTTTT
// TTTTTTTT
// LL RR
// LL RR
// BBBBBBBB
// BBBBBBBB
//
if (thick > 0.0f) if (thick > 0.0f)
{ {
// When rec = { x, y, 8.0f, 6.0f } and thick = 2, the following
// four rectangles are drawn ([T]op, [B]ottom, [L]eft, [R]ight):
//
// TTTTTTTT
// TTTTTTTT
// LL RR
// LL RR
// BBBBBBBB
// BBBBBBBB
//
Rectangle top = { rec.x, rec.y, rec.width, thick }; Rectangle top = { rec.x, rec.y, rec.width, thick };
Rectangle bottom = { rec.x, rec.y - thick + rec.height, rec.width, thick }; Rectangle bottom = { rec.x, rec.y - thick + rec.height, rec.width, thick };
Rectangle left = { rec.x, rec.y + thick, thick, rec.height - thick*2.0f }; Rectangle left = { rec.x, rec.y + thick, thick, rec.height - thick*2.0f };
Rectangle right = { rec.x - thick + rec.width, rec.y + thick, thick, rec.height - thick*2.0f }; Rectangle right = { rec.x - thick + rec.width, rec.y + thick, thick, rec.height - thick*2.0f };
DrawRectangleRec(top, color);
DrawRectangleRec(bottom, color);
DrawRectangleRec(left, color);
DrawRectangleRec(right, color);
}
else
{
// When rec = { x, y, 8.0f, 6.0f } and thick = -2, the following
// four rectangles are drawn ([T]op, [B]ottom, [L]eft, [R]ight):
//
// TTTTTTTTTTTT
// TTTTTTTTTTTT
// LL RR
// LL RR
// LL RR
// LL RR
// LL RR
// LL RR
// BBBBBBBBBBBB
// BBBBBBBBBBBB
//
thick *= -1.0f;
Rectangle top = { rec.x - thick, rec.y - thick, rec.width + thick*2.0f, thick };
Rectangle bottom = { rec.x - thick, rec.y + rec.height, rec.width + thick*2.0f, thick};
Rectangle left = { rec.x - thick, rec.y, thick, rec.height };
Rectangle right = { rec.x + rec.width, rec.y, thick, rec.height };
DrawRectangleRec(top, color); DrawRectangleRec(top, color);
DrawRectangleRec(bottom, color); DrawRectangleRec(bottom, color);
DrawRectangleRec(left, color); DrawRectangleRec(left, color);
@ -931,22 +960,35 @@ void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Col
\ / \ /
P5 ------------------ P4 P5 ------------------ P4
*/ */
// The x-coordinates used for the outline
const float x0 = rec.x + radius + 0.5f;
const float x1 = (rec.x + rec.width) - radius - 0.5f;
const float x2 = rec.x + rec.width - 0.5f;
const float x3 = rec.x + 0.5f;
// The y-coordinates used for the outline
const float y0 = rec.y + 0.5f;
const float y1 = rec.y + radius + 0.5f;
const float y2 = (rec.y + rec.height) - radius - 0.5f;
const float y3 = rec.y + rec.height - 0.5f;
const Vector2 point[8] = { const Vector2 point[8] = {
{(float)rec.x + radius + 0.5f, rec.y + 0.5f}, {x0, y0}, // P0
{(float)(rec.x + rec.width) - radius - 0.5f, rec.y + 0.5f}, {x1, y0}, // P1
{rec.x + rec.width - 0.5f, (float)rec.y + radius + 0.5f}, // PO, P1, P2 {x2, y1}, // P2
{rec.x + rec.width - 0.5f, (float)(rec.y + rec.height) - radius - 0.5f}, {x2, y2}, // P3
{(float)(rec.x + rec.width) - radius - 0.5f, rec.y + rec.height - 0.5f}, // P3, P4 {x1, y3}, // P4
{(float)rec.x + radius + 0.5f, rec.y + rec.height - 0.5f}, {x0, y3}, // P5
{rec.x + 0.5f, (float)(rec.y + rec.height) - radius - 0.5f}, {x3, y2}, // P6
{rec.x + 0.5f, (float)rec.y + radius + 0.5f}, // P5, P6, P7 {x3, y1}, // P7
}; };
const Vector2 centers[4] = { const Vector2 centers[4] = {
{(float)rec.x + radius + 0.5f, (float)rec.y + radius + 0.5f}, {x0, y1}, // P16
{(float)(rec.x + rec.width) - radius - 0.5f, (float)rec.y + radius + 0.5f}, // P16, P17 {x1, y1}, // P17
{(float)(rec.x + rec.width) - radius - 0.5f, (float)(rec.y + rec.height) - radius - 0.5f}, {x1, y2}, // P18
{(float)rec.x + radius + 0.5f, (float)(rec.y + rec.height) - radius - 0.5f} // P18, P19 {x0, y2} // P19
}; };
const float angles[4] = { 180.0f, 270.0f, 0.0f, 90.0f }; const float angles[4] = { 180.0f, 270.0f, 0.0f, 90.0f };
@ -980,32 +1022,81 @@ void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Col
// Draw rectangle with rounded edges outline with line thickness // Draw rectangle with rounded edges outline with line thickness
void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float thick, Color color) void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float thick, Color color)
{ {
if (thick <= 0) return;
// Not a rounded rectangle // Not a rounded rectangle
if (roundness <= 0.0f) if (roundness <= 0.0f)
{ {
DrawRectangleLinesEx((Rectangle){rec.x - thick, rec.y - thick, rec.width + 2*thick, rec.height + 2*thick}, thick, color); DrawRectangleLinesEx(rec, thick, color);
return; return;
} }
if (roundness >= 1.0f) roundness = 1.0f; if (roundness >= 1.0f) roundness = 1.0f;
// Calculate corner radius float radius = 0.0f;
float radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; float roundedOutlineThick = 0.0f;
if (radius <= 0.0f) return; float outerRadius = 0.0f;
float innerRadius = 0.0f;
// Calculate number of segments to use for the corners if (thick >= 0.0f)
if (segments < 4)
{ {
// Calculate the maximum angle between segments based on the error rate (usually 0.5f) // Calculate corner radius
float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2;
segments = (int)ceilf((2*PI/th)/4.0f); if (radius <= 0.0f) return;
if (segments <= 0) segments = 4;
outerRadius = radius;
innerRadius = outerRadius - thick;
// The maximum thickness the outline can have and still be rounded on the interior edge is equal to the corner radius
// Put another way, when `innerRadius <= 0`, the interior of the outline is just a normal rectangle with no rounding
if (innerRadius <= 0.0f)
{
innerRadius = 0.0f;
roundedOutlineThick = outerRadius;
// Draw the not-rounded portion of the outline
DrawRectangleLinesEx((Rectangle){ rec.x + outerRadius, rec.y + outerRadius, rec.width - outerRadius*2.0f, rec.height - outerRadius*2.0f }, thick - outerRadius, color);
}
else
{
roundedOutlineThick = thick;
}
// Calculate number of segments to use for the corners
if (segments < 4)
{
// Calculate the maximum angle between segments based on the error rate (usually 0.5f)
float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/outerRadius, 2) - 1);
segments = (int)ceilf((2*PI/th)/4.0f);
if (segments <= 0) segments = 4;
}
}
else
{
thick *= -1.0f;
// Calculate corner radius
radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2;
if (radius <= 0.0f) return; // Only possible if the rectangle has 0 width or height
// Expand the rectangle
rec.x -= thick;
rec.y -= thick;
rec.width += thick*2.0f;
rec.height += thick*2.0f;
innerRadius = radius;
outerRadius = innerRadius + thick;
roundedOutlineThick = thick;
// Calculate number of segments to use for the corners
if (segments < 4)
{
// Calculate the maximum angle between segments based on the error rate (usually 0.5f)
float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/innerRadius, 2) - 1);
segments = (int)ceilf((2*PI/th)/4.0f);
if (segments <= 0) segments = 4;
}
} }
float stepLength = 90.0f/(float)segments; float stepLength = 90.0f/(float)segments;
const float outerRadius = radius + thick, innerRadius = radius;
/* /*
Quick sketch to make sense of all of this, Quick sketch to make sense of all of this,
@ -1023,30 +1114,47 @@ void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, f
\\ P13 P12 // \\ P13 P12 //
P5 ================== P4 P5 ================== P4
*/ */
// The x-coordinates used for the outline
const float x0 = rec.x + outerRadius;
const float x1 = (rec.x + rec.width) - outerRadius;
const float x2 = rec.x + rec.width;
const float x3 = rec.x;
const float x4 = rec.x + rec.width - roundedOutlineThick;
const float x5 = rec.x + roundedOutlineThick;
// The y-coordinates used for the outline
const float y0 = rec.y;
const float y1 = rec.y + outerRadius;
const float y2 = (rec.y + rec.height) - outerRadius;
const float y3 = rec.y + rec.height;
const float y4 = rec.y + roundedOutlineThick;
const float y5 = rec.y + rec.height - roundedOutlineThick;
const Vector2 point[16] = { const Vector2 point[16] = {
{(float)rec.x + innerRadius + 0.5f, rec.y - thick + 0.5f}, {x0, y0}, // P0
{(float)(rec.x + rec.width) - innerRadius - 0.5f, rec.y - thick + 0.5f}, {x1, y0}, // P1
{rec.x + rec.width + thick - 0.5f, (float)rec.y + innerRadius + 0.5f}, // PO, P1, P2 {x2, y1}, // P2
{rec.x + rec.width + thick - 0.5f, (float)(rec.y + rec.height) - innerRadius - 0.5f}, {x2, y2}, // P3
{(float)(rec.x + rec.width) - innerRadius - 0.5f, rec.y + rec.height + thick - 0.5f}, // P3, P4 {x1, y3}, // P4
{(float)rec.x + innerRadius + 0.5f, rec.y + rec.height + thick - 0.5f}, {x0, y3}, // P5
{rec.x - thick + 0.5f, (float)(rec.y + rec.height) - innerRadius - 0.5f}, {x3, y2}, // P6
{rec.x - thick + 0.5f, (float)rec.y + innerRadius + 0.5f}, // P5, P6, P7 {x3, y1}, // P7
{(float)rec.x + innerRadius + 0.5f, rec.y + 0.5f}, {x0, y4}, // P8
{(float)(rec.x + rec.width) - innerRadius - 0.5f, rec.y + 0.5f}, // P8, P9 {x1, y4}, // P9
{rec.x + rec.width - 0.5f, (float)rec.y + innerRadius + 0.5f}, {x4, y1}, // P10
{rec.x + rec.width - 0.5f, (float)(rec.y + rec.height) - innerRadius - 0.5f}, // P10, P11 {x4, y2}, // P11
{(float)(rec.x + rec.width) - innerRadius - 0.5f, rec.y + rec.height - 0.5f}, {x1, y5}, // P12
{(float)rec.x + innerRadius + 0.5f, rec.y + rec.height - 0.5f}, // P12, P13 {x0, y5}, // P13
{rec.x + 0.5f, (float)(rec.y + rec.height) - innerRadius - 0.5f}, {x5, y2}, // P14
{rec.x + 0.5f, (float)rec.y + innerRadius + 0.5f} // P14, P15 {x5, y1} // P15
}; };
const Vector2 centers[4] = { const Vector2 centers[4] = {
{(float)rec.x + innerRadius + 0.5f, (float)rec.y + innerRadius + 0.5f}, {x0, y1}, // P16
{(float)(rec.x + rec.width) - innerRadius - 0.5f, (float)rec.y + innerRadius + 0.5f}, // P16, P17 {x1, y1}, // P17
{(float)(rec.x + rec.width) - innerRadius - 0.5f, (float)(rec.y + rec.height) - innerRadius - 0.5f}, {x1, y2}, // P18
{(float)rec.x + innerRadius + 0.5f, (float)(rec.y + rec.height) - innerRadius - 0.5f} // P18, P19 {x0, y2} // P19
}; };
const float angles[4] = { 180.0f, 270.0f, 0.0f, 90.0f }; const float angles[4] = { 180.0f, 270.0f, 0.0f, 90.0f };
@ -1263,13 +1371,24 @@ void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Colo
void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float thick, Color color) void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float thick, Color color)
{ {
if (thick <= 0.0f) return;
if (sides < 3) sides = 3; if (sides < 3) sides = 3;
float centralAngle = rotation*DEG2RAD; float centralAngle = rotation*DEG2RAD;
float exteriorAngle = 360.0f/(float)sides*DEG2RAD; float exteriorAngle = 360.0f/(float)sides*DEG2RAD;
float apothem = radius*cosf(DEG2RAD*180.0f/(float)sides); float apothem = radius*cosf(DEG2RAD*180.0f/(float)sides);
float innerRadius = fmaxf(0.0f, radius - thick*(radius/apothem));
float outerRadius = 0.0f;
float innerRadius = 0.0f;
if (thick >= 0.0f)
{
outerRadius = radius;
innerRadius = fmaxf(0.0f, radius - thick*(radius/apothem));
}
else
{
thick *= -1.0f;
outerRadius = radius + thick*(radius/apothem);
innerRadius = radius;
}
#if SUPPORT_QUADS_DRAW_MODE #if SUPPORT_QUADS_DRAW_MODE
rlSetTexture(GetShapesTexture().id); rlSetTexture(GetShapesTexture().id);
@ -1282,7 +1401,7 @@ void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, fl
float nextAngle = centralAngle + exteriorAngle; float nextAngle = centralAngle + exteriorAngle;
rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height);
rlVertex2f(center.x + cosf(centralAngle)*radius, center.y + sinf(centralAngle)*radius); rlVertex2f(center.x + cosf(centralAngle)*outerRadius, center.y + sinf(centralAngle)*outerRadius);
rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height);
rlVertex2f(center.x + cosf(centralAngle)*innerRadius, center.y + sinf(centralAngle)*innerRadius); rlVertex2f(center.x + cosf(centralAngle)*innerRadius, center.y + sinf(centralAngle)*innerRadius);
@ -1291,7 +1410,7 @@ void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, fl
rlVertex2f(center.x + cosf(nextAngle)*innerRadius, center.y + sinf(nextAngle)*innerRadius); rlVertex2f(center.x + cosf(nextAngle)*innerRadius, center.y + sinf(nextAngle)*innerRadius);
rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height);
rlVertex2f(center.x + cosf(nextAngle)*radius, center.y + sinf(nextAngle)*radius); rlVertex2f(center.x + cosf(nextAngle)*outerRadius, center.y + sinf(nextAngle)*outerRadius);
centralAngle = nextAngle; centralAngle = nextAngle;
} }
@ -1304,13 +1423,13 @@ void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, fl
rlColor4ub(color.r, color.g, color.b, color.a); rlColor4ub(color.r, color.g, color.b, color.a);
float nextAngle = centralAngle + exteriorAngle; float nextAngle = centralAngle + exteriorAngle;
rlVertex2f(center.x + cosf(nextAngle)*radius, center.y + sinf(nextAngle)*radius); rlVertex2f(center.x + cosf(nextAngle)*outerRadius, center.y + sinf(nextAngle)*outerRadius);
rlVertex2f(center.x + cosf(centralAngle)*radius, center.y + sinf(centralAngle)*radius); rlVertex2f(center.x + cosf(centralAngle)*outerRadius, center.y + sinf(centralAngle)*outerRadius);
rlVertex2f(center.x + cosf(centralAngle)*innerRadius, center.y + sinf(centralAngle)*innerRadius); rlVertex2f(center.x + cosf(centralAngle)*innerRadius, center.y + sinf(centralAngle)*innerRadius);
rlVertex2f(center.x + cosf(centralAngle)*innerRadius, center.y + sinf(centralAngle)*innerRadius); rlVertex2f(center.x + cosf(centralAngle)*innerRadius, center.y + sinf(centralAngle)*innerRadius);
rlVertex2f(center.x + cosf(nextAngle)*innerRadius, center.y + sinf(nextAngle)*innerRadius); rlVertex2f(center.x + cosf(nextAngle)*innerRadius, center.y + sinf(nextAngle)*innerRadius);
rlVertex2f(center.x + cosf(nextAngle)*radius, center.y + sinf(nextAngle)*radius); rlVertex2f(center.x + cosf(nextAngle)*outerRadius, center.y + sinf(nextAngle)*outerRadius);
centralAngle = nextAngle; centralAngle = nextAngle;
} }

View File

@ -713,7 +713,7 @@ GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSiz
default: break; default: break;
} }
if (glyphs[k].image.data != NULL) // Glyph data has been found in the font if (glyphs[k].image.data != NULL) // Glyph data has been found in the font
{ {
stbtt_GetCodepointHMetrics(&fontInfo, cp, &glyphs[k].advanceX, NULL); stbtt_GetCodepointHMetrics(&fontInfo, cp, &glyphs[k].advanceX, NULL);
glyphs[k].advanceX = (int)((float)glyphs[k].advanceX*scaleFactor); glyphs[k].advanceX = (int)((float)glyphs[k].advanceX*scaleFactor);
@ -845,7 +845,7 @@ Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyp
// DEBUG: View padding in the generated image setting a gray background... // DEBUG: View padding in the generated image setting a gray background...
//for (int i = 0; i < atlas.width*atlas.height; i++) ((unsigned char *)atlas.data)[i] = 100; //for (int i = 0; i < atlas.width*atlas.height; i++) ((unsigned char *)atlas.data)[i] = 100;
if (packMethod == 0) // Use basic packing algorithm if (packMethod == 0) // Use basic packing algorithm
{ {
int offsetX = padding; int offsetX = padding;
int offsetY = padding; int offsetY = padding;
@ -907,7 +907,7 @@ Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyp
offsetX += (glyphs[i].image.width + 2*padding); offsetX += (glyphs[i].image.width + 2*padding);
} }
} }
else if (packMethod == 1) // Use Skyline rect packing algorithm (stb_pack_rect) else if (packMethod == 1) // Use Skyline rect packing algorithm (stb_pack_rect)
{ {
stbrp_context *context = (stbrp_context *)RL_MALLOC(sizeof(*context)); stbrp_context *context = (stbrp_context *)RL_MALLOC(sizeof(*context));
stbrp_node *nodes = (stbrp_node *)RL_MALLOC(glyphCount*sizeof(*nodes)); stbrp_node *nodes = (stbrp_node *)RL_MALLOC(glyphCount*sizeof(*nodes));
@ -1860,7 +1860,7 @@ char *TextReplaceAlloc(const char *text, const char *search, const char *replace
int tempLen = textLen + (replaceLen - searchLen)*count + 1; int tempLen = textLen + (replaceLen - searchLen)*count + 1;
temp = result = (char *)RL_CALLOC(tempLen, sizeof(char)); temp = result = (char *)RL_CALLOC(tempLen, sizeof(char));
if (result != NULL) // Memory was allocated if (result != NULL) // Memory was allocated
{ {
// First time through the loop, all the variable are set correctly from here on, // First time through the loop, all the variable are set correctly from here on,
// - 'temp' points to the end of the result string // - 'temp' points to the end of the result string
@ -2736,7 +2736,7 @@ static Font LoadBMFont(const char *fileName)
&charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX, &pageID); &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX, &pageID);
fileTextPtr += (readBytes + 1); fileTextPtr += (readBytes + 1);
if (readVars == 9) // Make sure all char data has been properly read if (readVars == 9) // Make sure all char data has been properly read
{ {
// Get character rectangle in the font atlas texture // Get character rectangle in the font atlas texture
font.recs[i] = (Rectangle){ (float)charX, (float)charY + (float)imHeight*pageID, (float)charWidth, (float)charHeight }; font.recs[i] = (Rectangle){ (float)charX, (float)charY + (float)imHeight*pageID, (float)charWidth, (float)charHeight };

View File

@ -315,7 +315,7 @@ Image LoadImageRaw(const char *fileName, int width, int height, int format, int
unsigned char *dataPtr = fileData; unsigned char *dataPtr = fileData;
int size = GetPixelDataSize(width, height, format); int size = GetPixelDataSize(width, height, format);
if (size <= dataSize) // Security check if (size <= dataSize) // Security check
{ {
// Offset file data to expected raw image by header size // Offset file data to expected raw image by header size
if ((headerSize > 0) && ((headerSize + size) <= dataSize)) dataPtr += headerSize; if ((headerSize > 0) && ((headerSize + size) <= dataSize)) dataPtr += headerSize;
@ -4288,7 +4288,7 @@ TextureCubemap LoadTextureCubemap(Image image, int layout)
{ {
TextureCubemap cubemap = { 0 }; TextureCubemap cubemap = { 0 };
if (layout == CUBEMAP_LAYOUT_AUTO_DETECT) // Try to automatically guess layout type if (layout == CUBEMAP_LAYOUT_AUTO_DETECT) // Try to automatically guess layout type
{ {
// Check image width/height to determine the type of cubemap provided // Check image width/height to determine the type of cubemap provided
if (image.width > image.height) if (image.width > image.height)