diff --git a/examples/shapes/shapes_outlines_testbed.c b/examples/shapes/shapes_outlines_testbed.c new file mode 100644 index 000000000..22dcc0b5d --- /dev/null +++ b/examples/shapes/shapes_outlines_testbed.c @@ -0,0 +1,407 @@ +/******************************************************************************************* +* +* raylib [shapes] example - outlines testbed +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 6.1, last time updated with raylib 6.1 +* +* Example contributed by Matthew Roush (@MatthewRoush) and reviewed by Ramon Santamaria (@raysan5) +* +* Example 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) 2026 Matthew Roush (@MatthewRoush) +* +********************************************************************************************/ + +#include "raylib.h" + +#include "raymath.h" + +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" // Required for GUI controls + +#include + +#define COLOR_FILLED DARKBLUE +#define COLOR_OUTLINE YELLOW + +#define SHAPE_SPACING 14.0f +#define SHAPE_SIZE 46.0f +#define BOX_SPACING 10.0f + +#define MOUSE_CAMERA_ZOOM_SPEED 0.3f +#define KEYBOARD_CAMERA_MOVE_SPEED 10.0f +#define KEYBOARD_CAMERA_ZOOM_SPEED 0.1f + +#define CAMERA_ZOOM_MIN 0.1f +#define CAMERA_ZOOM_MAX 1000.0f + +// The shapes are ordered according to their order in this enum, left to right +enum { + ORDER_RECTANGLE = 0, + ORDER_RECTANGLE_ROUNDED, + ORDER_CIRCLE, + ORDER_ELLIPSE, + ORDER_CIRCLE_SECTOR, + ORDER_RING, + ORDER_TRIANGLE, + ORDER_POLYGON, + COUNT_SHAPES +}; + +// The line style groups are ordered according to their order in this enum, top to bottom +enum { + ORDER_LINES = 0, + ORDER_LINES_EX_WORLD, + ORDER_LINES_EX_SCREEN, +}; + +#define BOX_WIDTH (SHAPE_SIZE*COUNT_SHAPES + SHAPE_SPACING*(COUNT_SHAPES - 1) + BOX_SPACING*2.0f) +#define BOX_HEIGHT (SHAPE_SIZE + BOX_SPACING*2.0f) + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - outlines testbed"); + + Camera2D camera = { .zoom = 1 }; + + // User configurable options while running the program. + float lineOpacity = 130; + float lineThickness = 4.0f; + float rectangleRoundness = 0.4f; + float rectangleSegments = 9; + float ellipseRadiusY = 0.5f; + float circleStartAngle = 20.0f; + float circleEndAngle = 270.0f; + float circleSegments = 36; + float ringInnerRadiusScale = 0.3f; + float polygonSides = 6; + + bool disableMouseControl = false; + + const Rectangle optionsBackground = { 510, 0, 290, 450 }; + + const Vector2 zoomPoint = {((float)screenWidth - optionsBackground.width)/2.0f, (float)screenHeight/2.0f}; + + GuiSetStyle(LABEL, TEXT_COLOR_NORMAL, ColorToInt(WHITE)); + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + const Vector2 mousePosScreen = GetMousePosition(); + const Vector2 mouseWheelVec = GetMouseWheelMoveV(); + + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && CheckCollisionPointRec(mousePosScreen, optionsBackground)) disableMouseControl = true; + + if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) disableMouseControl = false; + + if (!disableMouseControl) { + if (mouseWheelVec.y != 0.0f) { + const Vector2 prevWorldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + + camera.zoom *= exp2f(MOUSE_CAMERA_ZOOM_SPEED*mouseWheelVec.y); // Constant zoom rate + camera.zoom = Clamp(camera.zoom, CAMERA_ZOOM_MIN, CAMERA_ZOOM_MAX); + + const Vector2 worldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + camera.target = Vector2Add(camera.target, Vector2Subtract(prevWorldZoomPoint, worldZoomPoint)); + } + + if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { + const Vector2 mouseDelta = GetMouseDelta(); + camera.target.x -= mouseDelta.x/camera.zoom; + camera.target.y -= mouseDelta.y/camera.zoom; + + const int mouseMaxX = (int)optionsBackground.x; + const int mouseMaxY = screenHeight; + + int newX = (int)mousePosScreen.x; + int newY = (int)mousePosScreen.y; + + // In C, the '%' operator computes the remainder, we want the modulus + newX = (newX%mouseMaxX + mouseMaxX)%mouseMaxX; + newY = (newY%mouseMaxY + mouseMaxY)%mouseMaxY; + + if ((newX != (int)mousePosScreen.x) || (newY != (int)mousePosScreen.y)) SetMousePosition(newX, newY); + } + } + + if (IsKeyDown(KEY_A)) camera.target.x -= KEYBOARD_CAMERA_MOVE_SPEED/camera.zoom; + if (IsKeyDown(KEY_D)) camera.target.x += KEYBOARD_CAMERA_MOVE_SPEED/camera.zoom; + if (IsKeyDown(KEY_W)) camera.target.y -= KEYBOARD_CAMERA_MOVE_SPEED/camera.zoom; + if (IsKeyDown(KEY_S)) camera.target.y += KEYBOARD_CAMERA_MOVE_SPEED/camera.zoom; + + if (IsKeyDown(KEY_UP)) + { + const Vector2 prevWorldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + + camera.zoom *= exp2f(KEYBOARD_CAMERA_ZOOM_SPEED); + camera.zoom = Clamp(camera.zoom, CAMERA_ZOOM_MIN, CAMERA_ZOOM_MAX); + + const Vector2 worldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + camera.target = Vector2Add(camera.target, Vector2Subtract(prevWorldZoomPoint, worldZoomPoint)); + } + if (IsKeyDown(KEY_DOWN)) + { + const Vector2 prevWorldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + + camera.zoom *= exp2f(-KEYBOARD_CAMERA_ZOOM_SPEED); + camera.zoom = Clamp(camera.zoom, CAMERA_ZOOM_MIN, CAMERA_ZOOM_MAX); + + const Vector2 worldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + camera.target = Vector2Add(camera.target, Vector2Subtract(prevWorldZoomPoint, worldZoomPoint)); + } + + if (IsKeyPressed(KEY_Z)) + { + const Vector2 zoomPoint = {((float)screenWidth - optionsBackground.width)/2.0f, (float)screenHeight/2.0f}; + const Vector2 prevWorldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + + camera.zoom = 1.0f; + + const Vector2 worldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + camera.target = Vector2Add(camera.target, Vector2Subtract(prevWorldZoomPoint, worldZoomPoint)); + } + + if (IsKeyPressed(KEY_C)) camera.target = (Vector2){ 0 }; + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground((Color){ 50, 50, 55, 255 }); + + Color colorOutline = COLOR_OUTLINE; + colorOutline.a = (unsigned char)lineOpacity; + + BeginMode2D(camera); + + const float shapeOffset = BOX_SPACING*2.0f; + const float shapePaddingX = SHAPE_SIZE + SHAPE_SPACING; + const float shapePaddingY = SHAPE_SIZE + SHAPE_SPACING + BOX_SPACING*2.0f; + + const float radiusX = SHAPE_SIZE/2.0f; + const float radiusY = radiusX*ellipseRadiusY; + + const float ringOuterRadius = SHAPE_SIZE/2.0f; + const float ringInnerRadius = ringOuterRadius*ringInnerRadiusScale; + + const Vector2 triangleVertex0Offset = { 0.0f, SHAPE_SIZE*0.8f }; + const Vector2 triangleVertex1Offset = { SHAPE_SIZE*0.6f, SHAPE_SIZE }; + const Vector2 triangleVertex2Offset = { SHAPE_SIZE, 0.0f }; + + // ---------------------------------------- + // Draw*Lines() + float posY = shapeOffset + shapePaddingY*ORDER_LINES; + + // Rectangle + float posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE; + DrawRectangleRec((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, COLOR_FILLED); + DrawRectangleLines(posX, posY, SHAPE_SIZE, SHAPE_SIZE, colorOutline); + + // Rectangle Rounded + posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE_ROUNDED; + DrawRectangleRounded((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, COLOR_FILLED); + DrawRectangleRoundedLines((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, colorOutline); + + // Circle + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE; + DrawCircleV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, COLOR_FILLED); + DrawCircleLinesV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, colorOutline); + + // Ellipse + posX = shapeOffset + shapePaddingX*ORDER_ELLIPSE; + DrawEllipseV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, COLOR_FILLED); + DrawEllipseLinesV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, colorOutline); + + // Circle Sector + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE_SECTOR; + DrawCircleSector((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawCircleSectorLines((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, colorOutline); + + // Ring + posX = shapeOffset + shapePaddingX*ORDER_RING; + DrawRing((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawRingLines((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, colorOutline); + + // Triangle + posX = shapeOffset + shapePaddingX*ORDER_TRIANGLE; + DrawTriangle((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + COLOR_FILLED); + DrawTriangleLines((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + colorOutline); + + // Polygon + posX = shapeOffset + shapePaddingX*ORDER_POLYGON; + DrawPoly((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, COLOR_FILLED); + DrawPolyLines((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, colorOutline); + + // Group the shapes + GuiGroupBox((Rectangle){ shapeOffset - BOX_SPACING, posY - BOX_SPACING, BOX_WIDTH, BOX_HEIGHT }, "Draw*Lines()"); + // ---------------------------------------- + + // ---------------------------------------- + // Draw*LinesEx() with world space pixel thickness + posY = shapeOffset + shapePaddingY*ORDER_LINES_EX_WORLD; + + // Rectangle + posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE; + DrawRectangleRec((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, COLOR_FILLED); + DrawRectangleLinesEx((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, lineThickness, colorOutline); + + // Rectangle Rounded + posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE_ROUNDED; + DrawRectangleRounded((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, COLOR_FILLED); + DrawRectangleRoundedLinesEx((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, lineThickness, colorOutline); + + // Circle + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE; + DrawCircleV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, COLOR_FILLED); + DrawCircleLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, lineThickness, colorOutline); + + // Ellipse + posX = shapeOffset + shapePaddingX*ORDER_ELLIPSE; + DrawEllipseV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, COLOR_FILLED); + DrawEllipseLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, lineThickness, colorOutline); + + // Circle Sector + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE_SECTOR; + DrawCircleSector((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawCircleSectorLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, lineThickness, colorOutline); + + // Ring + posX = shapeOffset + shapePaddingX*ORDER_RING; + DrawRing((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawRingLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, lineThickness, colorOutline); + + // Triangle + posX = shapeOffset + shapePaddingX*ORDER_TRIANGLE; + DrawTriangle((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + COLOR_FILLED); + DrawTriangleLinesEx((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + lineThickness, colorOutline); + + // Polygon + posX = shapeOffset + shapePaddingX*ORDER_POLYGON; + DrawPoly((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, COLOR_FILLED); + DrawPolyLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, lineThickness, colorOutline); + + // Group the shapes + GuiGroupBox((Rectangle){ shapeOffset - BOX_SPACING, posY - BOX_SPACING, BOX_WIDTH, BOX_HEIGHT }, "Draw*LinesEx() with *world space* pixel thickness"); + // ---------------------------------------- + + // ---------------------------------------- + // Draw*LinesEx() with screen space pixel thickness + posY = shapeOffset + shapePaddingY*ORDER_LINES_EX_SCREEN; + + float constantThickness = lineThickness/camera.zoom; + + // Rectangle + posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE; + DrawRectangleRec((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, COLOR_FILLED); + DrawRectangleLinesEx((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, constantThickness, colorOutline); + + // Rectangle Rounded + posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE_ROUNDED; + DrawRectangleRounded((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, COLOR_FILLED); + DrawRectangleRoundedLinesEx((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, constantThickness, colorOutline); + + // Circle + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE; + DrawCircleV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, COLOR_FILLED); + DrawCircleLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, constantThickness, colorOutline); + + // Ellipse + posX = shapeOffset + shapePaddingX*ORDER_ELLIPSE; + DrawEllipseV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, COLOR_FILLED); + DrawEllipseLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, constantThickness, colorOutline); + + // Circle Sector + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE_SECTOR; + DrawCircleSector((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawCircleSectorLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, constantThickness, colorOutline); + + // Ring + posX = shapeOffset + shapePaddingX*ORDER_RING; + DrawRing((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawRingLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, constantThickness, colorOutline); + + // Triangle + posX = shapeOffset + shapePaddingX*ORDER_TRIANGLE; + DrawTriangle((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + COLOR_FILLED); + DrawTriangleLinesEx((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + constantThickness, colorOutline); + + // Polygon + posX = shapeOffset + shapePaddingX*ORDER_POLYGON; + DrawPoly((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, COLOR_FILLED); + DrawPolyLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, constantThickness, colorOutline); + + // Group the shapes + GuiGroupBox((Rectangle){ shapeOffset - BOX_SPACING, posY - BOX_SPACING, BOX_WIDTH, BOX_HEIGHT }, "Draw*LinesEx() with *screen space* pixel thickness"); + // ---------------------------------------- + + EndMode2D(); + + DrawRectangleRec(optionsBackground, Fade(DARKGRAY, 0.75f)); + + DrawRectangleRec((Rectangle){ optionsBackground.x, 360, optionsBackground.width, 90 }, Fade(BLACK, 0.4f)); + DrawLineEx((Vector2){ optionsBackground.x, 360 }, (Vector2){ optionsBackground.x + optionsBackground.width, 360 }, 1.0f, BLACK); + + DrawText("Move with the mouse or WASD keys", (int)optionsBackground.x + 10, 370, 10, ORANGE); + DrawText("Zoom with the mouse or UP and DOWN keys", (int)optionsBackground.x + 10, 390, 10, ORANGE); + DrawText("Press C to reset position", (int)optionsBackground.x + 10, 410, 10, ORANGE); + DrawText("Press Z to reset zoom", (int)optionsBackground.x + 10, 430, 10, ORANGE); + + DrawLineEx((Vector2){ optionsBackground.x, optionsBackground.y }, (Vector2){ optionsBackground.x, optionsBackground.y + optionsBackground.height }, 1.0f, BLACK); + + GuiSliderBar((Rectangle){ 605, 10, 150, 25 }, "Line Opacity" , TextFormat("%d" , (int)lineOpacity) , &lineOpacity , 0 , 255); + GuiSliderBar((Rectangle){ 605, 45, 150, 25 }, "Thickness" , TextFormat("%.2f", lineThickness) , &lineThickness , -20.0f , 40.0f); + GuiSliderBar((Rectangle){ 605, 80, 150, 25 }, "Rect Roundness" , TextFormat("%.2f", rectangleRoundness) , &rectangleRoundness , -1.0f , 2.0f); + GuiSliderBar((Rectangle){ 605, 115, 150, 25 }, "Rect Segments" , TextFormat("%d" , (int)rectangleSegments), &rectangleSegments , -1 , 100); + GuiSliderBar((Rectangle){ 605, 150, 150, 25 }, "Ellipse Radius Y", TextFormat("%.2f", ellipseRadiusY) , &ellipseRadiusY , 0.0f , 1.25f); + GuiSliderBar((Rectangle){ 605, 185, 150, 25 }, "Start Angle" , TextFormat("%.2f", circleStartAngle) , &circleStartAngle , -360.0f, 360.0f); + GuiSliderBar((Rectangle){ 605, 220, 150, 25 }, "End Angle" , TextFormat("%.2f", circleEndAngle) , &circleEndAngle , -360.0f, 360.0f); + GuiSliderBar((Rectangle){ 605, 255, 150, 25 }, "Circle Segments" , TextFormat("%d" , (int)circleSegments) , &circleSegments , -1 , 100); + GuiSliderBar((Rectangle){ 605, 290, 150, 25 }, "Ring Radius" , TextFormat("%.2f", ringInnerRadiusScale) , &ringInnerRadiusScale, -1.0f , 2.0f); + GuiSliderBar((Rectangle){ 605, 325, 150, 25 }, "Poly Sides" , TextFormat("%d" , (int)polygonSides) , &polygonSides , -1 , 100); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/shapes/shapes_outlines_testbed.png b/examples/shapes/shapes_outlines_testbed.png new file mode 100644 index 000000000..02d58449e Binary files /dev/null and b/examples/shapes/shapes_outlines_testbed.png differ diff --git a/src/raylib.h b/src/raylib.h index 154158131..d2a52420c 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1284,6 +1284,7 @@ RLAPI void DrawLineDashed(Vector2 startPos, Vector2 endPos, int dashSize, int sp RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle, counter-clockwise vertex order RLAPI void DrawTriangleGradient(Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors, counter-clockwise vertex/color order RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline, counter-clockwise vertex order +RLAPI void DrawTriangleLinesEx(Vector2 v1, Vector2 v2, Vector2 v3, float thick, Color color); // Draw triangle outline with line thickness, counter-clockwise vertex order RLAPI void DrawTriangleFan(const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) RLAPI void DrawTriangleStrip(const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle @@ -1306,6 +1307,7 @@ RLAPI void DrawCircleV(Vector2 center, float radius, Color color); RLAPI void DrawCircleGradient(Vector2 center, float radius, Color inner, Color outer); // Draw a gradient-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 DrawCircleSectorLinesEx(Vector2 center, float radius, float startAngle, float endAngle, int segments, float thick, Color color); // Draw circle sector outline with thickness RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) RLAPI void DrawCircleLinesEx(Vector2 center, float radius, float thick, Color color); // Draw circle outline with line thickness @@ -1313,8 +1315,10 @@ RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, C RLAPI void DrawEllipseV(Vector2 center, float radiusH, float radiusV, Color color); // Draw ellipse (Vector version) RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline RLAPI void DrawEllipseLinesV(Vector2 center, float radiusH, float radiusV, Color color); // Draw ellipse outline (Vector version) +RLAPI void DrawEllipseLinesEx(Vector2 center, float radiusH, float radiusV, float thick, Color color); // Draw ellipse outline with line thickness 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 DrawRingLinesEx(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, float thick, Color color); // Draw ring outline with line thickness // Splines drawing functions RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points diff --git a/src/rshapes.c b/src/rshapes.c index 65040f452..466cff72d 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -375,6 +375,166 @@ void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color) rlEnd(); } +// Draw a triangle using lines with thickness +// NOTE: Vertex must be provided in counter-clockwise order +void DrawTriangleLinesEx(Vector2 v1, Vector2 v2, Vector2 v3, float thick, Color color) +{ + /* + A sketch to make things simpler + + The exterior points are v1-3, the interior points are v4-6, and the exterior edges are e1-3 + + v1 + /\ + /v4\ + // \\ + // \\ + e3 // \\ e2 + // \\ + // \\ + //v5 v6\\ + v2==============v3 + e1 + */ + + Vector2 e1 = {v2.x - v3.x, v2.y - v3.y}; + Vector2 e2 = {v3.x - v1.x, v3.y - v1.y}; + Vector2 e3 = {v1.x - v2.x, v1.y - v2.y}; + + float e1Length = sqrtf(e1.x*e1.x + e1.y*e1.y); + float e2Length = sqrtf(e2.x*e2.x + e2.y*e2.y); + float e3Length = sqrtf(e3.x*e3.x + e3.y*e3.y); + + float perimeter = e1Length + e2Length + e3Length; + float semiperimeter = perimeter/2.0f; + + // The incenter of a triangle is equidistant from each edge, which is useful for drawing a nice looking outline + Vector2 incenter = { + (e1Length*v1.x + e2Length*v2.x + e3Length*v3.x)/perimeter, + (e1Length*v1.y + e2Length*v2.y + e3Length*v3.y)/perimeter + }; + + // The inradius of a triangle is the radius of the biggest circle that can fit inside of said triangle + // That circle is also centered on the incenter + float inradius = sqrtf(((semiperimeter - e1Length)*(semiperimeter - e2Length)*(semiperimeter - e3Length))/semiperimeter); + + // The triangle (v1, v2, v3) will be scaled by this to get (v4, v5, v6) + float scale = 1.0f - thick/inradius; + + // Just a filled-in triangle + if (scale <= 0.0f) + { + DrawTriangle(v1, v2, v3, color); + return; + } + + // In order for the scaling to be correct, the incenter has to be at the origin (0, 0) when scaling + Vector2 v4 = {incenter.x + (v1.x - incenter.x)*scale, incenter.y + (v1.y - incenter.y)*scale}; + Vector2 v5 = {incenter.x + (v2.x - incenter.x)*scale, incenter.y + (v2.y - incenter.y)*scale}; + Vector2 v6 = {incenter.x + (v3.x - incenter.x)*scale, incenter.y + (v3.y - incenter.y)*scale}; + + // Swap the vertices so the winding order is correct + if (thick < 0.0f) + { + Vector2 temp = v1; + v1 = v4; + v4 = temp; + + temp = v2; + v2 = v5; + v5 = temp; + + temp = v3; + v3 = v6; + v6 = temp; + } + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + rlColor4ub(color.r, color.g, color.b, color.a); + + // Edge 3 + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v1.x, v1.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v2.x, v2.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v5.x, v5.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v4.x, v4.y); + + // Edge 1 + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v2.x, v2.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v3.x, v3.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v6.x, v6.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v5.x, v5.y); + + // Edge 2 + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v3.x, v3.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v1.x, v1.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(v4.x, v4.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(v6.x, v6.y); + + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + + rlColor4ub(color.r, color.g, color.b, color.a); + + // Edge 3 + rlVertex2f(v1.x, v1.y); + rlVertex2f(v2.x, v2.y); + rlVertex2f(v4.x, v4.y); + + rlVertex2f(v2.x, v2.y); + rlVertex2f(v5.x, v5.y); + rlVertex2f(v4.x, v4.y); + + // Edge 1 + rlVertex2f(v2.x, v2.y); + rlVertex2f(v3.x, v3.y); + rlVertex2f(v5.x, v5.y); + + rlVertex2f(v3.x, v3.y); + rlVertex2f(v6.x, v6.y); + rlVertex2f(v5.x, v5.y); + + // Edge 2 + rlVertex2f(v3.x, v3.y); + rlVertex2f(v1.x, v1.y); + rlVertex2f(v4.x, v4.y); + + rlVertex2f(v3.x, v3.y); + rlVertex2f(v4.x, v4.y); + rlVertex2f(v6.x, v6.y); + + rlEnd(); +#endif +} + // Draw a triangle fan defined by points // NOTE: First vertex provided is the center, shared by all triangles // By default, following vertex should be provided in counter-clockwise order @@ -1625,6 +1785,738 @@ void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float rlEnd(); } +// Draw a piece of a circle outlines with thickness +void DrawCircleSectorLinesEx(Vector2 center, float radius, float startAngle, float endAngle, int segments, float thick, Color color) +{ + if (startAngle == endAngle) return; + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero issue + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + bool showCapLines = true; + // Drawing a whole circle, things get weird without limiting the circle to 360 degrees + if (endAngle - startAngle >= 360.0f) + { + showCapLines = thick >= 0.0f; + endAngle = startAngle + 360.0f; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (int)ceilf((endAngle - startAngle)*(2*PI/th)/360.0f); + + if (segments <= 0) segments = minSegments; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; + + /* + A sketch to help make things clearer + + NOTE: Some considerations are different when `thick` is negative + The vertices used here are still relevant, but would instead be outside of the circle + S0 is always the center + + The circle sector outline is drawn in 3 main pieces, the circle outline, cap 1, and cap 2 + The circle outline is self explanatory + Cap 1 covers the `startAngle` edge and cap 2 covers the `endAngle` edge + S0 is the first shared point between the caps, and also the circle's center + S1 is the second shared point (sometimes not shared) between the caps + S1 is also C0 and C3 in this sketch. In certain cases, S1 goes outside of + the circle and C0 and C3 become different points + C1 is one of cap 1's vertices that is on the inside edge of the circle outline + C2 is like C1, but is also on the `startAngle` edge + C4 is cap 2's vertex that corresponds with C1 + C5 is cap 2's vertex that corresponds with C2, except on the `endAngle` edge + + [][][][][] + [][] [] + [] []C4[]C5 + [] [][] {} {} + [] [] {} {} + [] [] {}C {} <- endAngle + [] [] {}a {} + [] [] {}p {} + [] [] {}2 {} + [] [] {} {} + [] [] {} {} startAngle + [] [] {} S0{}{}{}{}{}{}{}{}C2[][] + [] [] {}{} Cap1 [] [] + [] [] S1{}{}{}{}{}{}{}{}{}{}C1 [] + [] [] [] [] + [] [] [] [] + [] [] Not filled in [] [] + [] [] [] [] + [] [] [] [] + [] [][] [][] [] + [] [][][][][][][] [] + [][] Circle outline [][] + [][][][][][][][][] + + [] = Circle outline edge pixel + {} = Cap outline edge pixel + */ + + // We are not drawing a circle, we are drawing an n-sided polygon + // So, we need to adjust the outline thickness of the "circle" for it to look correct with fewer segments + float apothem = radius*cosf(DEG2RAD*((endAngle - startAngle)/2.0f)/(float)segments); + float radiusThick = thick*(radius/apothem); + + float outerRadius = radius; + float innerRadius = radius - radiusThick; + + if (thick >= 0.0f) + { + if (thick >= innerRadius) + { + DrawCircleSector(center, radius, startAngle, endAngle, segments, color); + return; + } + } + else + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + } + + // Cap 1 vertices + Vector2 c0 = { 0 }; + Vector2 c1 = { 0 }; + Vector2 c2 = { 0 }; + + // Cap 2 vertices + Vector2 c3 = { 0 }; + Vector2 c4 = { 0 }; + Vector2 c5 = { 0 }; + + // The number of angle steps that come before C1 (from `startAngle`, counter clockwise) + int stepsBeforeC1 = 0; + bool s1OutsideOfCircle = false; + // The number of angle steps that come before C0 (from `startAngle`, counter clockwise) + // Only used if S1 is outside of the circle + int stepsBeforeC0 = 0; + + if (showCapLines) + { + if (thick >= 0.0f) + { + c2 = (Vector2){ center.x + cosf(DEG2RAD*startAngle)*innerRadius, center.y + sinf(DEG2RAD*startAngle)*innerRadius }; + c5 = (Vector2){ center.x + cosf(DEG2RAD*endAngle)*innerRadius, center.y + sinf(DEG2RAD*endAngle)*innerRadius }; + + // For C1 and C4, we need to find the point that lies on the circle (n-sided polygon, actually) + // We want C1 and C4 to be `thick` pixels perpendicularly from the `startAngle` and `endAngle` edges + // and to be on the `innerRadius` edge + + float c1Angle = RAD2DEG*asinf(thick/innerRadius); + + // There are more segments before C1 than there are segments being drawn, + // so the whole circle sector must be covered + if (c1Angle/stepLength >= (float)segments) + { + DrawCircleSector(center, radius, startAngle, endAngle, segments, color); + return; + } + + // Do this after the previous check just in case `stepLength` is really small and + // dividing by it produces a very large number + stepsBeforeC1 = (int)(c1Angle/stepLength); + + // The angles of the vertices on the circle outline before and after C1 + float vertexAngleBeforeC1 = stepLength*(float)stepsBeforeC1; + float vertexAngleAfterC1 = stepLength*((float)(stepsBeforeC1 + 1)); + + /* + Here is another sketch + + We know which outline line segment C1 is on (`vertexAngleBeforeC1` and `vertexAngleAfterC1`) + Now we just need to know where on that line segment C1 is + + We can change our frame of reference so that `startAngle` is 0 degrees and `center` is at the origin (0, 0) + This makes the math much simpler because now we can just go straight down by `thick` pixels and + use the horizontal line that passes through that point to determine where C1 is on our line segment + + The line segment is defined by p1 and p2, we need C1, which is on that edge + The 'y' axis of C1 is equal to `thick` (within this modified frame of reference) + + p1 + /| + / | + / | + / | + / | + / | + / | + / | + C1--------- <-- y axis = `thick` + / + / + p2 + */ + + Vector2 p1 = { cosf(DEG2RAD*vertexAngleBeforeC1)*innerRadius, sinf(DEG2RAD*vertexAngleBeforeC1)*innerRadius }; + Vector2 p2 = { cosf(DEG2RAD*vertexAngleAfterC1)*innerRadius, sinf(DEG2RAD*vertexAngleAfterC1)*innerRadius }; + + // Find the `t` of C1 between p1 and p2 ('t' as in `Lerp(start, end, t)`) + // This is used to lerp between the actual vertices (outside of our modified frame of reference) + // before and after C1 + float t = (p1.y - thick)/(p1.y - p2.y); + + Vector2 vertexBeforeCap1Vertex = { center.x + cosf(DEG2RAD*(startAngle + vertexAngleBeforeC1))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + vertexAngleBeforeC1))*innerRadius }; + Vector2 vertexAfterCap1Vertex = { center.x + cosf(DEG2RAD*(startAngle + vertexAngleAfterC1))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + vertexAngleAfterC1))*innerRadius }; + + c1.x = vertexBeforeCap1Vertex.x + (vertexAfterCap1Vertex.x - vertexBeforeCap1Vertex.x)*t; + c1.y = vertexBeforeCap1Vertex.y + (vertexAfterCap1Vertex.y - vertexBeforeCap1Vertex.y)*t; + + Vector2 vertexBeforeCap2Vertex = { center.x + cosf(DEG2RAD*(endAngle - vertexAngleBeforeC1))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - vertexAngleBeforeC1))*innerRadius }; + Vector2 vertexAfterCap2Vertex = { center.x + cosf(DEG2RAD*(endAngle - vertexAngleAfterC1))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - vertexAngleAfterC1))*innerRadius }; + + c4.x = vertexBeforeCap2Vertex.x + (vertexAfterCap2Vertex.x - vertexBeforeCap2Vertex.x)*t; + c4.y = vertexBeforeCap2Vertex.y + (vertexAfterCap2Vertex.y - vertexBeforeCap2Vertex.y)*t; + + /* + Another sketch couldn't hurt + + This is a "zoomed in" view of the center of the circle sector + You can see where S0 is, and where we want S1 to be + `innerAngleBetweenCapEnds` is the angle of the diagonal line ('//') between the cap ends + `S1Length` is the length of that line + + Since the caps are always parallel to `startAngle` and `endAngle`, + we always have a right triangle we can use to determine where S1 is + + [] [] + [] [] + [] C [] <- endAngle + [] a [] + [] p [] + [] 2 [] startAngle + [][][][]S0[][][][][][] + //[] + // [] Cap 1 + // [] + S1 [][][][][][][] + */ + + float innerAngleBetweenCapEnds = ((endAngle - 90.0f) - (startAngle + 90.0f))/2.0f; + float s1Length = thick/cosf(DEG2RAD*innerAngleBetweenCapEnds); + + // As `startAngle` and `endAngle` draw more of a circle, S1 goes further out from the center + // It can go so far that it is outside of the circle, by a lot + // This case needs to be detected and handled + // If S1 is within the circle, nothing special needs to happen + // But, if S1 is outside of the circle, we need to find the two points (C0 and C3) where + // the line segments C0->C1 and C0->C3 intersect the circle outline, + // using the same method we used to find C1 and C4 + if ((innerAngleBetweenCapEnds < 90.0f) && (s1Length <= innerRadius)) + { + // S1 is inside of the circle + + float betweenStartAndEndAngle = (endAngle + startAngle)/2.0f; + c0 = (Vector2){ center.x + cosf(DEG2RAD*betweenStartAndEndAngle)*s1Length, center.y + sinf(DEG2RAD*betweenStartAndEndAngle)*s1Length }; + c3 = c0; + + p1 = (Vector2){ c1.x - center.x, c1.y - center.y }; + p2 = (Vector2){ c0.x - center.x, c0.y - center.y }; + + // Copied from "raymath.h" Vector2Angle() + float dot = p1.x*p2.x + p1.y*p2.y; + float det = p1.x*p2.y - p1.y*p2.x; + float c1ToS1Angle = atan2f(det, dot); + + // If C1 and C4 are on the wrong side of S1, the whole circle sector is covered + if (c1ToS1Angle < 0.0f) + { + DrawCircleSector(center, radius, startAngle, endAngle, segments, color); + return; + } + } + else + { + // S1 is outside of the circle + + if (endAngle - startAngle <= 180.0f) + { + DrawCircleSector(center, radius, startAngle, endAngle, segments, color); + return; + } + + s1OutsideOfCircle = true; + + stepsBeforeC0 = (int)((180.0f + RAD2DEG*asinf(thick/-innerRadius))/stepLength); + + // Reuse the code for finding C1 and C4 to find C0 and C3 + + float vertexAngleBeforeC0 = stepLength*(float)stepsBeforeC0; + float vertexAngleAfterC0 = stepLength*((float)(stepsBeforeC0 + 1)); + + p1 = (Vector2){ cosf(DEG2RAD*vertexAngleBeforeC0)*innerRadius, sinf(DEG2RAD*vertexAngleBeforeC0)*innerRadius }; + p2 = (Vector2){ cosf(DEG2RAD*vertexAngleAfterC0)*innerRadius, sinf(DEG2RAD*vertexAngleAfterC0)*innerRadius }; + + t = (p1.y - thick)/(p1.y - p2.y); + + vertexBeforeCap1Vertex = (Vector2){ center.x + cosf(DEG2RAD*(startAngle + vertexAngleBeforeC0))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + vertexAngleBeforeC0))*innerRadius }; + vertexAfterCap1Vertex = (Vector2){ center.x + cosf(DEG2RAD*(startAngle + vertexAngleAfterC0))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + vertexAngleAfterC0))*innerRadius }; + + c0.x = vertexBeforeCap1Vertex.x + (vertexAfterCap1Vertex.x - vertexBeforeCap1Vertex.x)*t; + c0.y = vertexBeforeCap1Vertex.y + (vertexAfterCap1Vertex.y - vertexBeforeCap1Vertex.y)*t; + + vertexBeforeCap2Vertex = (Vector2){ center.x + cosf(DEG2RAD*(endAngle - vertexAngleBeforeC0))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - vertexAngleBeforeC0))*innerRadius }; + vertexAfterCap2Vertex = (Vector2){ center.x + cosf(DEG2RAD*(endAngle - vertexAngleAfterC0))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - vertexAngleAfterC0))*innerRadius }; + + c3.x = vertexBeforeCap2Vertex.x + (vertexAfterCap2Vertex.x - vertexBeforeCap2Vertex.x)*t; + c3.y = vertexBeforeCap2Vertex.y + (vertexAfterCap2Vertex.y - vertexBeforeCap2Vertex.y)*t; + } + } + else + { + float outerAngleBetweenCapEnds = ((endAngle + 90.0f) - (startAngle - 90.0f))/2.0f; + float s1Length = thick/cosf(DEG2RAD*outerAngleBetweenCapEnds); + float betweenStartAndEndAngle = 180.0f + (endAngle + startAngle)/2.0f; + c0 = (Vector2){ center.x + cosf(DEG2RAD*betweenStartAndEndAngle)*s1Length, center.y + sinf(DEG2RAD*betweenStartAndEndAngle)*s1Length }; + c3 = c0; + + c2 = (Vector2){ center.x + cosf(DEG2RAD*startAngle)*outerRadius, center.y + sinf(DEG2RAD*startAngle)*outerRadius }; + c5 = (Vector2){ center.x + cosf(DEG2RAD*endAngle)*outerRadius, center.y + sinf(DEG2RAD*endAngle)*outerRadius }; + + // Change the frame of reference so that `center` is the origin and `startAngle` is 0 degrees + + Vector2 c0Translated = { c0.x - center.x, c0.y - center.y }; + Vector2 circleVertex1 = { c2.x - center.x, c2.y - center.y }; + Vector2 circleVertex2 = { cosf(DEG2RAD*(startAngle + stepLength))*outerRadius, sinf(DEG2RAD*(startAngle + stepLength))*outerRadius }; + + // Copied from "raymath.h" Vector2Rotate() + float tempX = c0Translated.x; + c0Translated.x = cosf(-DEG2RAD*startAngle)*tempX - sinf(-DEG2RAD*startAngle)*c0Translated.y; + c0Translated.y = sinf(-DEG2RAD*startAngle)*tempX + cosf(-DEG2RAD*startAngle)*c0Translated.y; + + tempX = circleVertex1.x; + circleVertex1.x = cosf(-DEG2RAD*startAngle)*tempX - sinf(-DEG2RAD*startAngle)*circleVertex1.y; + circleVertex1.y = sinf(-DEG2RAD*startAngle)*tempX + cosf(-DEG2RAD*startAngle)*circleVertex1.y; + + tempX = circleVertex2.x; + circleVertex2.x = cosf(-DEG2RAD*startAngle)*tempX - sinf(-DEG2RAD*startAngle)*circleVertex2.y; + circleVertex2.y = sinf(-DEG2RAD*startAngle)*tempX + cosf(-DEG2RAD*startAngle)*circleVertex2.y; + + // Figure out the line that `circleVertex1` and `circleVertex2` are on + float rise = circleVertex1.y - circleVertex2.y; + float run = circleVertex1.x - circleVertex2.x; + // Get where that line intersects the horizontal line that `c0Translated` is on + float c1Rise = c0Translated.y - circleVertex1.y; + float c1Run = (c1Rise/rise)*run; + float c1DistanceFromC0 = (circleVertex1.x + c1Run) - c0Translated.x; + + c1 = (Vector2){ c0.x + cosf(DEG2RAD*startAngle)*c1DistanceFromC0, c0.y + sinf(DEG2RAD*startAngle)*c1DistanceFromC0 }; + c4 = (Vector2){ c0.x + cosf(DEG2RAD*endAngle)*c1DistanceFromC0, c0.y + sinf(DEG2RAD*endAngle)*c1DistanceFromC0 }; + + if (c1DistanceFromC0 < 0.0f) + { + // The caps are intersecting each other + + Vector2 circleVertex3 = { c5.x - center.x, c5.y - center.y }; + Vector2 circleVertex4 = { cosf(DEG2RAD*(endAngle - stepLength))*outerRadius, sinf(DEG2RAD*(endAngle - stepLength))*outerRadius }; + + tempX = circleVertex3.x; + circleVertex3.x = cosf(-DEG2RAD*startAngle)*tempX - sinf(-DEG2RAD*startAngle)*circleVertex3.y; + circleVertex3.y = sinf(-DEG2RAD*startAngle)*tempX + cosf(-DEG2RAD*startAngle)*circleVertex3.y; + + tempX = circleVertex4.x; + circleVertex4.x = cosf(-DEG2RAD*startAngle)*tempX - sinf(-DEG2RAD*startAngle)*circleVertex4.y; + circleVertex4.y = sinf(-DEG2RAD*startAngle)*tempX + cosf(-DEG2RAD*startAngle)*circleVertex4.y; + + // `startAngle` is 0 degrees within this frame of reference, + // so C1 just goes horizontally out from C0 + Vector2 c1Translated = { c0Translated.x + c1DistanceFromC0, c0Translated.y }; + + // Make `circleVertex2` the origin + circleVertex1.x -= circleVertex2.x; + circleVertex1.y -= circleVertex2.y; + circleVertex3.x -= circleVertex2.x; + circleVertex3.y -= circleVertex2.y; + circleVertex4.x -= circleVertex2.x; + circleVertex4.y -= circleVertex2.y; + c1Translated.x -= circleVertex2.x; + c1Translated.y -= circleVertex2.y; + + // Make the line between `circleVertex1` and `circleVertex2` a horizontal line + float theta = atan2f(circleVertex1.y, circleVertex1.x); + + // Copied from "raymath.h" Vector2Rotate() + tempX = circleVertex1.x; + circleVertex1.x = cosf(-theta)*tempX - sinf(-theta)*circleVertex1.y; + circleVertex1.y = sinf(-theta)*tempX + cosf(-theta)*circleVertex1.y; + + tempX = circleVertex3.x; + circleVertex3.x = cosf(-theta)*tempX - sinf(-theta)*circleVertex3.y; + circleVertex3.y = sinf(-theta)*tempX + cosf(-theta)*circleVertex3.y; + + tempX = circleVertex4.x; + circleVertex4.x = cosf(-theta)*tempX - sinf(-theta)*circleVertex4.y; + circleVertex4.y = sinf(-theta)*tempX + cosf(-theta)*circleVertex4.y; + + tempX = c1Translated.x; + c1Translated.x = cosf(-theta)*tempX - sinf(-theta)*c1Translated.y; + c1Translated.y = sinf(-theta)*tempX + cosf(-theta)*c1Translated.y; + + // Find where the line that `circleVertex3` and `circleVertex4` are on would intersect the + // line segment defined by `circleVertex1` and `c1Translated` + rise = circleVertex3.y - circleVertex4.y; + run = circleVertex3.x - circleVertex4.x; + float targetRise = -circleVertex3.y; + float targetX = circleVertex3.x + (targetRise/rise)*run; + + float t = (c1Translated.x - targetX)/(c1Translated.x - circleVertex1.x); + + c1 = (Vector2){ c1.x + (c2.x - c1.x)*t, c1.y + (c2.y - c1.y)*t }; + c4 = c1; + c0 = c1; + c3 = c1; + } + + // Swap vertices to correct the winding order + Vector2 temp = c0; + c0 = c2; + c2 = temp; + + temp = c3; + c3 = c5; + c5 = temp; + } + } + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + rlColor4ub(color.r, color.g, color.b, color.a); + + // Draw the circle outline + for (int i = 0; i < segments; i++) + { + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerRadius); + + angle += stepLength; + } + + // Draw the caps + if (showCapLines) + { + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c0.x, c0.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c1.x, c1.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c2.x, c2.y); + + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c5.x, c5.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c4.x, c4.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c3.x, c3.y); + + // Some extra work may be needed when `thick` is positive + if (thick >= 0.0f) + { + // Fill in the gaps between cap 1 and the circle outline and cap 2 and the circle outline + if (stepsBeforeC1 > 0) + { + // Draw quads using pairs of vertices on the circle outline + angle = 0; + for (int i = 0; i < stepsBeforeC1/2; i++) + { + // Cap1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c1.x, c1.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + (angle + stepLength*2.0f)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + (angle + stepLength*2.0f)))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerRadius); + + // Cap2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c4.x, c4.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - (angle + stepLength*2.0f)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - (angle + stepLength*2.0f)))*innerRadius); + + angle += stepLength*2.0f; + } + + if (stepsBeforeC1%2 == 1) + { + // Cap1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c1.x, c1.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c1.x, c1.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerRadius); + + // Cap2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c4.x, c4.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c4.x, c4.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius); + } + } + + // Fill in the gap between C0, C3 and the circle outline + if (s1OutsideOfCircle) + { + int verticesBetweenC0andC3 = (segments - stepsBeforeC0*2) - 1; + + // No gap to fill + if (verticesBetweenC0andC3 == 0) + { + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c3.x, c3.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c0.x, c0.y); + } + // There's a gap to fill + else + { + // Triangle touching C0 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(c0.x, c0.y); + + // Triangle touching C3 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(c3.x, c3.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius); + + // Triangles between the previous two + verticesBetweenC0andC3 -= 1; + angle = startAngle + stepLength*(stepsBeforeC0 + 1); + for (int i = 0; i < verticesBetweenC0andC3/2; i++) + { + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength*2.0f))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength*2.0f))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + + angle += stepLength*2.0f; + } + + if (verticesBetweenC0andC3%2 == 1) + { + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x, center.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + } + } + } + } + } + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + + rlColor4ub(color.r, color.g, color.b, color.a); + + // Draw the circle outline + for (int i = 0; i < segments; i++) + { + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerRadius, center.y + sinf(DEG2RAD*angle)*outerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerRadius); + + angle += stepLength; + } + + // Draw the caps + if (showCapLines) + { + // Cap 1 + rlVertex2f(center.x, center.y); + rlVertex2f(c0.x, c0.y); + rlVertex2f(c1.x, c1.y); + + rlVertex2f(center.x, center.y); + rlVertex2f(c1.x, c1.y); + rlVertex2f(c2.x, c2.y); + + // Cap 2 + rlVertex2f(center.x, center.y); + rlVertex2f(c5.x, c5.y); + rlVertex2f(c4.x, c4.y); + + rlVertex2f(center.x, center.y); + rlVertex2f(c4.x, c4.y); + rlVertex2f(c3.x, c3.y); + + // Some extra work may be needed when `thick` is positive + if (thick >= 0.0f) + { + // Fill in the gaps between cap 1 and the circle outline and cap 2 and the circle outline + if (stepsBeforeC1 > 0) + { + angle = 0; + for (int i = 0; i < stepsBeforeC1; i++) + { + // Cap 1 + rlVertex2f(c1.x, c1.y); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + (angle + stepLength)))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerRadius); + + // Cap 2 + rlVertex2f(c4.x, c4.y); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - (angle + stepLength)))*innerRadius); + + angle += stepLength; + } + } + + // Fill in the gap between C0, C3 and the circle outline + if (s1OutsideOfCircle) + { + int verticesBetweenC0andC3 = (segments - stepsBeforeC0*2) - 1; + + // No gap to fill + if (verticesBetweenC0andC3 == 0) + { + rlVertex2f(center.x, center.y); + rlVertex2f(c3.x, c3.y); + rlVertex2f(c0.x, c0.y); + } + // There's a gap to fill + else + { + // Triangle touching C0 + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius, center.y + sinf(DEG2RAD*(startAngle + stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius); + rlVertex2f(c0.x, c0.y); + + // Triangle touching C3 + rlVertex2f(center.x, center.y); + rlVertex2f(c3.x, c3.y); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius, center.y + sinf(DEG2RAD*(endAngle - stepLength*(float)(stepsBeforeC0 + 1)))*innerRadius); + + // Triangles between the previous two + verticesBetweenC0andC3 -= 1; + angle = startAngle + stepLength*(stepsBeforeC0 + 1); + for (int i = 0; i < verticesBetweenC0andC3; i++) + { + rlVertex2f(center.x, center.y); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerRadius, center.y + sinf(DEG2RAD*angle)*innerRadius); + + angle += stepLength; + } + } + } + } + } + + rlEnd(); +#endif +} + // Draw circle outline void DrawCircleLines(int centerX, int centerY, float radius, Color color) { @@ -1690,6 +2582,76 @@ void DrawEllipseLinesV(Vector2 center, float radiusH, float radiusV, Color color rlEnd(); } +// Draw ellipse outline with thickness +void DrawEllipseLinesEx(Vector2 center, float radiusH, float radiusV, float thick, Color color) +{ + float outerRadiusH = radiusH, innerRadiusH = radiusH - thick; + float outerRadiusV = radiusV, innerRadiusV = radiusV - thick; + + if (thick >= 0.0f) { + // Just a filled-in ellipse + if (innerRadiusH <= 0.0f || innerRadiusV <= 0.0f) + { + DrawEllipseV(center, radiusH, radiusV, color); + return; + } + } + else + { + // The outline is growing outside of the ellipse, so swap the inner and outer radius + float tmp = outerRadiusH; + outerRadiusH = innerRadiusH; + innerRadiusH = tmp; + + tmp = outerRadiusV; + outerRadiusV = innerRadiusV; + innerRadiusV = tmp; + } + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < 360; i += 10) + { + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*i)*innerRadiusH, center.y + sinf(DEG2RAD*i)*innerRadiusV); + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*innerRadiusH, center.y + sinf(DEG2RAD*(i + 10))*innerRadiusV); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*outerRadiusH, center.y + sinf(DEG2RAD*(i + 10))*outerRadiusV); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*i)*outerRadiusH, center.y + sinf(DEG2RAD*i)*outerRadiusV); + } + rlEnd(); + + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < 360; i += 10) + { + rlVertex2f(center.x + cosf(DEG2RAD*i)*innerRadiusH, center.y + sinf(DEG2RAD*i)*innerRadiusV); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*innerRadiusH, center.y + sinf(DEG2RAD*(i + 10))*innerRadiusV); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*outerRadiusH, center.y + sinf(DEG2RAD*(i + 10))*outerRadiusV); + + rlVertex2f(center.x + cosf(DEG2RAD*i)*innerRadiusH, center.y + sinf(DEG2RAD*i)*innerRadiusV); + rlVertex2f(center.x + cosf(DEG2RAD*(i + 10))*outerRadiusH, center.y + sinf(DEG2RAD*(i + 10))*outerRadiusV); + rlVertex2f(center.x + cosf(DEG2RAD*i)*outerRadiusH, center.y + sinf(DEG2RAD*i)*outerRadiusV); + } + rlEnd(); +#endif +} + // Draw ring void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color) { @@ -1866,6 +2828,886 @@ void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float s rlEnd(); } +// Draw ring outline with line thickness +void DrawRingLinesEx(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, float thick, Color color) +{ + if (startAngle == endAngle) return; + + // Function expects (outerRadius > innerRadius) + if (outerRadius < innerRadius) + { + float tmp = outerRadius; + outerRadius = innerRadius; + innerRadius = tmp; + + if (outerRadius <= 0.0f) outerRadius = 0.1f; + } + + // Function expects (endAngle > startAngle) + if (endAngle < startAngle) + { + // Swap values + float tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + bool showCapLines = true; + // Drawing a whole circle, things get weird without limiting the circle to 360 degrees + if (endAngle - startAngle >= 360.0f) + { + showCapLines = thick >= 0.0f; + endAngle = startAngle + 360.0f; + } + + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) + { + // 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((endAngle - startAngle)*(2*PI/th)/360.0f); + + if (segments <= 0) segments = minSegments; + } + + float stepLength = (endAngle - startAngle)/(float)segments; + + // We are not drawing a circle, we are drawing an n-sided polygon + // So, we need to adjust the outline thickness of the "circle" for it to look correct with fewer segments + float apothem = outerRadius*cosf(DEG2RAD*((endAngle - startAngle)/2.0f)/(float)segments); + float radiusThick = thick*(outerRadius/apothem); + + // These names can be confusing, but they are useful + // Since 2 rings are being drawn, there are 4 radiuses (or radii) + // "Inner" means closer to the center, "outer" means farther from the center + // Sorted from farthest to closest you get: + // 1. outerOuterRadius (farthest) + // 2. innerOuterRadius + // 3. outerInnerRadius + // 4. innerInnerRadius (closest) + float innerOuterRadius = 0.0f; + float outerOuterRadius = 0.0f; + float innerInnerRadius = 0.0f; + float outerInnerRadius = 0.0f; + + if (thick >= 0.0f) + { + innerRadius = fmaxf(0.0f, innerRadius); + + // Just a filled-in ring + if (radiusThick > (outerRadius - innerRadius)/2.0f) + { + DrawRing(center, innerRadius, outerRadius, startAngle, endAngle, segments, color); + return; + } + + innerInnerRadius = innerRadius; + outerInnerRadius = innerInnerRadius + radiusThick; + + outerOuterRadius = outerRadius; + innerOuterRadius = outerOuterRadius - radiusThick; + } + else + { + // Just a circle sector outline + if (innerRadius <= 0.0f) + { + DrawCircleSectorLinesEx(center, outerRadius, startAngle, endAngle, segments, thick, color); + return; + } + + outerInnerRadius = innerRadius; + innerInnerRadius = fmaxf(0.0f, outerInnerRadius + radiusThick); + + innerOuterRadius = outerRadius; + outerOuterRadius = innerOuterRadius - radiusThick; + } + + // For positive `thick` values + int stepsBeforeInner = 0; + int stepsBeforeOuter = 0; + float tInner = 0.0f; + float tOuter = 0.0f; + bool innerAnglesCrossEachOther = false; + + // For negative `thick` values + Vector2 cap1SecondInnerVertex = { 0 }; + Vector2 cap1SecondOuterVertex = { 0 }; + Vector2 cap2SecondInnerVertex = { 0 }; + Vector2 cap2SecondOuterVertex = { 0 }; + bool capsIntersect = false; + Vector2 capIntersectionVertex = { 0 }; + + if (showCapLines) + { + if (thick >= 0.0f) + { + // Get the angle of the arc that has `thick` length along the inner and outer radii + float cap1InnerAngleEnd = RAD2DEG*(thick/outerInnerRadius); + float cap1OuterAngleEnd = RAD2DEG*(thick/innerOuterRadius); + + // Just a filled-in ring + if (endAngle - startAngle < cap1OuterAngleEnd*2.0f) + { + DrawRing(center, innerRadius, outerRadius, startAngle, endAngle, segments, color); + return; + } + + if (endAngle - startAngle < cap1InnerAngleEnd*2.0f) innerAnglesCrossEachOther = true; + + stepsBeforeInner = (int)(cap1InnerAngleEnd/stepLength); + stepsBeforeOuter = (int)(cap1OuterAngleEnd/stepLength); + + // We need to find where `cap1InnerAngleEnd` intersects the edge defined + // by `beforeInnerVertex` and `afterInnerVertex` + // + // We can make this easy by making `center` the origin (0, 0) and + // making `cap1InnerAngleEnd` 0 degrees (a horizontal line) + // + // With that, we know these lines intersect when 'y' equals 0, + // so we just need to solve for 't' (as in `Lerp(start, end, t)`) + Vector2 beforeInnerVertex = { cosf(DEG2RAD*((float)stepsBeforeInner*stepLength - cap1InnerAngleEnd))*outerInnerRadius, sinf(DEG2RAD*((float)stepsBeforeInner*stepLength - cap1InnerAngleEnd))*outerInnerRadius }; + Vector2 afterInnerVertex = { cosf(DEG2RAD*((float)(stepsBeforeInner + 1)*stepLength - cap1InnerAngleEnd))*outerInnerRadius, sinf(DEG2RAD*((float)(stepsBeforeInner + 1)*stepLength - cap1InnerAngleEnd))*outerInnerRadius }; + tInner = beforeInnerVertex.y/(beforeInnerVertex.y - afterInnerVertex.y); + + // The same as above, but for the outer edge + Vector2 beforeOuterVertex = { cosf(DEG2RAD*((float)stepsBeforeOuter*stepLength - cap1OuterAngleEnd))*innerOuterRadius, sinf(DEG2RAD*((float)stepsBeforeOuter*stepLength - cap1OuterAngleEnd))*innerOuterRadius }; + Vector2 afterOuterVertex = { cosf(DEG2RAD*((float)(stepsBeforeOuter + 1)*stepLength - cap1OuterAngleEnd))*innerOuterRadius, sinf(DEG2RAD*((float)(stepsBeforeOuter + 1)*stepLength - cap1OuterAngleEnd))*innerOuterRadius }; + tOuter = beforeOuterVertex.y/(beforeOuterVertex.y - afterOuterVertex.y); + } + else + { + // "Cap 1" is the outline on `startAngle` and "Cap 2" is the outline on `endAngle` + + /* + A sketch to help make all this a little more understandable + (This is an overly simplified representation of cap 1) + + I2[][][][][]O2 <- y = thick + [] [] + [] [] + I0----------O0 <- angle = 0 degrees, y = 0 + [] [] + I1 O1 <- angle = stepLength + + Cap 2 is a mirror copy of cap 1, the inside and outside vertices switch sides + + We're using a frame of reference where `center` is (0, 0) and `startAngle` is 0 degrees + + I0 is `innerInnerRadius` distance from `center` at `starAngle` + I1 is `innerInnerRadius` distance from `center` at `starAngle + stepLength` + I2 goes out from I0 perpendicular to `startAngle` + O0 is the same as I0, except using `outerOuterRadius` instead of `innerInnerRadius` + O1 is the same as I1, except using `outerOuterRadius` instead of `innerInnerRadius` + O2 is the same as I2, except goes out from O0 + + The intersection cases between the caps edges are: + 1. No intersections, easy + 2. The I0->I2 and I2->O2 edges intersect between the caps + 3. The I0->I2 and O0->O2 edges intersect between the caps + + Notice that cap 1 and 2's I2->O2 and O0->O2 edges can't intersect at the same time, + and, if there's any intersection, I0->I2 is one of the edges + */ + + Vector2 cap1O0 = { outerOuterRadius, 0.0f }; + Vector2 cap1O1 = { cosf(DEG2RAD*stepLength)*outerOuterRadius, sinf(DEG2RAD*stepLength)*outerOuterRadius }; + + // Assuming a linear interpolation such as `value = Lerp(start, end, t)` + // We can find O2 by getting its 't' between O1.y and O0.y (which is always greater than 1) + // We can solve for `t` using `t = (start - value)/(start - end)` + // Since we know `end = 0` we can simplify it to `t = (start - value)/start` + float tOuter = (cap1O1.y - thick)/cap1O1.y; + Vector2 cap1O2 = { cap1O1.x + (cap1O0.x - cap1O1.x)*tOuter, thick }; + + Vector2 cap1I0 = { innerInnerRadius, 0.0f }; + + float capLongEdgeLength = outerOuterRadius - innerInnerRadius; + Vector2 cap1I2 = { cap1O2.x - capLongEdgeLength, thick }; + + Vector2 cap2O0 = { cosf(DEG2RAD*(endAngle - startAngle))*outerOuterRadius, sinf(DEG2RAD*(endAngle - startAngle))*outerOuterRadius }; + Vector2 cap2O1 = { cosf(DEG2RAD*(endAngle - startAngle - stepLength))*outerOuterRadius, sinf(DEG2RAD*(endAngle - startAngle - stepLength))*outerOuterRadius }; + Vector2 cap2O2 = { cap2O1.x + (cap2O0.x - cap2O1.x)*tOuter, cap2O1.y + (cap2O0.y - cap2O1.y)*tOuter }; + + Vector2 cap2I0 = { cosf(DEG2RAD*(endAngle - startAngle))*innerInnerRadius, sinf(DEG2RAD*(endAngle - startAngle))*innerInnerRadius }; + Vector2 cap2I2 = { cap2O2.x - cosf(DEG2RAD*(endAngle - startAngle))*capLongEdgeLength, cap2O2.y - sinf(DEG2RAD*(endAngle - startAngle))*capLongEdgeLength}; + + // The 't' of the intersection between I2 and O2 (`Lerp(I2, O2, t)`) + float tCapLongEdgeCross = -1.0f; + // Avoid division by zero + if (cap2I2.y - cap2O2.y != 0.0f) + { + // Find where the long edge of cap 2 intersects the long edge of cap 1 + tCapLongEdgeCross = (cap2I2.y - thick)/(cap2I2.y - cap2O2.y); + if ((tCapLongEdgeCross >= 0.0f) && (tCapLongEdgeCross <= 1.0f)) capsIntersect = true; + } + + // Rotate the frame of reference so that cap 1's I0->I2 edge is a vertical line + float rotateBy = -DEG2RAD*stepLength/2.0f; + + // Copied from "raymath.h" Vector2Rotate() + // Though we only use the x axis, so we ignore the y axis + float cosres = cosf(rotateBy); + float sinres = sinf(rotateBy); + + cap1I2.x = cap1I2.x*cosres - cap1I2.y*sinres; + cap1O2.x = cap1O2.x*cosres - cap1O2.y*sinres; + cap2I0.x = cap2I0.x*cosres - cap2I0.y*sinres; + cap2I2.x = cap2I2.x*cosres - cap2I2.y*sinres; + cap2O0.x = cap2O0.x*cosres - cap2O0.y*sinres; + cap2O2.x = cap2O2.x*cosres - cap2O2.y*sinres; + + // The 't' of the intersection between I0 and I2 (`Lerp(I0, I2, t)`) + float tCrossInner = -1.0f; + // Avoid division by zero + if (cap2I0.x - cap2I2.x != 0.0f) tCrossInner = (cap2I0.x - cap1I2.x)/(cap2I0.x - cap2I2.x); + // Make sure `tCrossInner` is 0 when it should be (mitigate floating-point rounding woes) + if (innerInnerRadius <= 0.0f) tCrossInner = 0.0f; + + // The 't' of the intersection between O0 and O2 (`Lerp(O0, O2, t)`) + float tCrossOuter = -1.0f; + // Avoid division by zero + if (cap2O0.x - cap2O2.x != 0.0f) tCrossOuter = (cap2O0.x - cap1O2.x)/(cap2O0.x - cap2O2.x); + + // With our additional information, calculate the vertices we need + // outside of our modified frame of reference + + cap1O0 = (Vector2){ center.x + cosf(DEG2RAD*startAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*startAngle)*outerOuterRadius }; + cap1O1 = (Vector2){ center.x + cosf(DEG2RAD*(startAngle + stepLength))*outerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + stepLength))*outerOuterRadius }; + cap1O2 = (Vector2){ cap1O1.x + (cap1O0.x - cap1O1.x)*tOuter, cap1O1.y + (cap1O0.y - cap1O1.y)*tOuter }; + + cap2O0 = (Vector2){ center.x + cosf(DEG2RAD*endAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*endAngle)*outerOuterRadius }; + cap2O1 = (Vector2){ center.x + cosf(DEG2RAD*(endAngle - stepLength))*outerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - stepLength))*outerOuterRadius }; + cap2O2 = (Vector2){ cap2O1.x + (cap2O0.x - cap2O1.x)*tOuter, cap2O1.y + (cap2O0.y - cap2O1.y)*tOuter }; + + cap1I0 = (Vector2){ center.x + cosf(DEG2RAD*startAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*startAngle)*innerInnerRadius }; + cap1I2 = (Vector2){ cap1O2.x - cosf(DEG2RAD*startAngle)*capLongEdgeLength, cap1O2.y - sinf(DEG2RAD*startAngle)*capLongEdgeLength }; + + cap2I0 = (Vector2){ center.x + cosf(DEG2RAD*endAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*endAngle)*innerInnerRadius }; + cap2I2 = (Vector2){ cap2O2.x - cosf(DEG2RAD*endAngle)*capLongEdgeLength, cap2O2.y - sinf(DEG2RAD*endAngle)*capLongEdgeLength }; + + if (capsIntersect) + { + capIntersectionVertex = (Vector2){ cap2I2.x + (cap2O2.x - cap2I2.x)*tCapLongEdgeCross, cap2I2.y + (cap2O2.y - cap2I2.y)*tCapLongEdgeCross }; + + cap2I2 = (Vector2){ cap2I0.x + (cap2I2.x - cap2I0.x)*tCrossInner, cap2I0.y + (cap2I2.y - cap2I0.y)*tCrossInner }; + cap1I2 = cap2I2; + } + else if ((tCrossOuter >= 0.0f) && (tCrossOuter <= 1.0f)) + { + cap2O2 = (Vector2){ cap2O0.x + (cap2O2.x - cap2O0.x)*tCrossOuter, cap2O0.y + (cap2O2.y - cap2O0.y)*tCrossOuter }; + cap1O2 = cap2O2; + + cap2I2 = (Vector2){ cap2I0.x + (cap2I2.x - cap2I0.x)*tCrossInner, cap2I0.y + (cap2I2.y - cap2I0.y)*tCrossInner }; + cap1I2 = cap2I2; + } + + cap1SecondInnerVertex = cap1I2; + cap1SecondOuterVertex = cap1O2; + cap2SecondInnerVertex = cap2I2; + cap2SecondOuterVertex = cap2O2; + } + } + + float angle = startAngle; + +#if SUPPORT_QUADS_DRAW_MODE + rlSetTexture(GetShapesTexture().id); + Rectangle shapeRect = GetShapesTextureRectangle(); + + rlBegin(RL_QUADS); + + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < segments; i++) + { + // `innerRadius` outline + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerInnerRadius, center.y + sinf(DEG2RAD*angle)*outerInnerRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerInnerRadius, center.y + sinf(DEG2RAD*angle)*innerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerInnerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerInnerRadius); + + // `outerRadius` outline + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerOuterRadius, center.y + sinf(DEG2RAD*angle)*outerOuterRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerOuterRadius, center.y + sinf(DEG2RAD*angle)*innerOuterRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerOuterRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerOuterRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerOuterRadius); + + angle += stepLength; + } + + if (showCapLines) + { + if (thick >= 0.0f) + { + angle = 0.0f; + + for (int i = 0; i < stepsBeforeOuter; i++) + { + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerOuterRadius); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerOuterRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius); + + angle += stepLength; + } + + // We've already moved `stepsBeforeOuter` steps from each end + int totalStepsLeft = segments - stepsBeforeOuter*2; + int innerStepsLeft = stepsBeforeInner - stepsBeforeOuter; + + // Cap 1 + Vector2 cap1OuterVertexBeforeEnd = { center.x + cosf(DEG2RAD*(startAngle + angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerOuterRadius }; + Vector2 cap1OuterVertexAfterEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius }; + Vector2 cap1InnerVertexBeforeEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + (float)innerStepsLeft*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + (float)innerStepsLeft*stepLength))*outerInnerRadius }; + Vector2 cap1InnerVertexAfterEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius }; + Vector2 cap1InnerVertexEnd = { cap1InnerVertexBeforeEnd.x + (cap1InnerVertexAfterEnd.x - cap1InnerVertexBeforeEnd.x)*tInner, cap1InnerVertexBeforeEnd.y + (cap1InnerVertexAfterEnd.y - cap1InnerVertexBeforeEnd.y)*tInner }; + Vector2 cap1OuterVertexEnd = { cap1OuterVertexBeforeEnd.x + (cap1OuterVertexAfterEnd.x - cap1OuterVertexBeforeEnd.x)*tOuter, cap1OuterVertexBeforeEnd.y + (cap1OuterVertexAfterEnd.y - cap1OuterVertexBeforeEnd.y)*tOuter }; + + // Cap 2 + Vector2 cap2OuterVertexBeforeEnd = { center.x + cosf(DEG2RAD*(endAngle - angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerOuterRadius }; + Vector2 cap2OuterVertexAfterEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius }; + Vector2 cap2InnerVertexBeforeEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - (float)innerStepsLeft*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - (float)innerStepsLeft*stepLength))*outerInnerRadius }; + Vector2 cap2InnerVertexAfterEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius }; + Vector2 cap2InnerVertexEnd = { cap2InnerVertexBeforeEnd.x + (cap2InnerVertexAfterEnd.x - cap2InnerVertexBeforeEnd.x)*tInner, cap2InnerVertexBeforeEnd.y + (cap2InnerVertexAfterEnd.y - cap2InnerVertexBeforeEnd.y)*tInner }; + Vector2 cap2OuterVertexEnd = { cap2OuterVertexBeforeEnd.x + (cap2OuterVertexAfterEnd.x - cap2OuterVertexBeforeEnd.x)*tOuter, cap2OuterVertexBeforeEnd.y + (cap2OuterVertexAfterEnd.y - cap2OuterVertexBeforeEnd.y)*tOuter }; + + int stepsCount = (innerAnglesCrossEachOther)? totalStepsLeft/2 : innerStepsLeft; + + // Iterate over pairs of steps + for (int i = 0; i < stepsCount/2; i++) + { + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength*2.0f))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength*2.0f))*outerInnerRadius); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength*2.0f))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength*2.0f))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + + angle += stepLength*2.0f; + } + + // Handle the last step if there's an odd amount + if (stepsCount%2 == 1) + { + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + + angle += stepLength; + } + + // When the inner angles coming from `startAngle` and `endAngle` cross each other, + // the `*innerVertexEnd` vertices go past each other and cause the geometry to intersect itself + if (innerAnglesCrossEachOther) + { + // We need to find where the line defined by `cap1InnerVertexEnd` and `cap1OuterVertexEnd` intersects + // the line defined by `cap2InnerVertexEnd` and `cap2OuterVertexEnd` + // That point is then used instead to prevent the outline from intersecting itself + + // Make `cap1InnerVertexEnd` the origin and the angle to `cap1OuterVertexEnd` 0 degrees + Vector2 tempCap1OuterVertexEnd = { cap1OuterVertexEnd.x - cap1InnerVertexEnd.x, cap1OuterVertexEnd.y - cap1InnerVertexEnd.y }; + Vector2 tempCap2InnerVertexEnd = { cap2InnerVertexEnd.x - cap1InnerVertexEnd.x, cap2InnerVertexEnd.y - cap1InnerVertexEnd.y }; + Vector2 tempCap2OuterVertexEnd = { cap2OuterVertexEnd.x - cap1InnerVertexEnd.x, cap2OuterVertexEnd.y - cap1InnerVertexEnd.y }; + + float rotateBy = -atan2f(tempCap1OuterVertexEnd.y, tempCap1OuterVertexEnd.x); + // We only need the y coordinates, so only rotate the y coordinates + float start = sinf(rotateBy)*tempCap2InnerVertexEnd.x + cosf(rotateBy)*tempCap2InnerVertexEnd.y; + float end = sinf(rotateBy)*tempCap2OuterVertexEnd.x + cosf(rotateBy)*tempCap2OuterVertexEnd.y; + float tCross = start/(start - end); + + Vector2 intersection = { cap2InnerVertexEnd.x + (cap2OuterVertexEnd.x - cap2InnerVertexEnd.x)*tCross, cap2InnerVertexEnd.y + (cap2OuterVertexEnd.y - cap2InnerVertexEnd.y)*tCross }; + + if (segments%2 == 0) + { + // There are an even number of segments, so there's 1 vertex exactly in the middle + + Vector2 middleInnerVertex = { center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius }; + + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(middleInnerVertex.x, middleInnerVertex.y); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(middleInnerVertex.x, middleInnerVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + } + else + { + // There are an odd number of segments, so there are 2 vertices in the middle + + Vector2 middleInnerVertex1 = { center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius }; + Vector2 middleInnerVertex2 = { center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius }; + + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(middleInnerVertex1.x, middleInnerVertex1.y); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(middleInnerVertex2.x, middleInnerVertex2.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + + // Triangle between the caps + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(intersection.x, intersection.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(middleInnerVertex1.x, middleInnerVertex1.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(middleInnerVertex2.x, middleInnerVertex2.y); + } + } + else + { + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1InnerVertexBeforeEnd.x, cap1InnerVertexBeforeEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1InnerVertexEnd.x, cap1InnerVertexEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2InnerVertexEnd.x, cap2InnerVertexEnd.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2InnerVertexBeforeEnd.x, cap2InnerVertexBeforeEnd.y); + } + } + else + { + // Cap 1 + Vector2 cap1FirstInnerVertex = { center.x + cosf(DEG2RAD*startAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*startAngle)*innerInnerRadius }; + Vector2 cap1FirstOuterVertex = { center.x + cosf(DEG2RAD*startAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*startAngle)*outerOuterRadius }; + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1FirstInnerVertex.x, cap1FirstInnerVertex.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1FirstOuterVertex.x, cap1FirstOuterVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1SecondOuterVertex.x, cap1SecondOuterVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1SecondInnerVertex.x, cap1SecondInnerVertex.y); + + // Cap 2 + Vector2 cap2FirstInnerVertex = { center.x + cosf(DEG2RAD*endAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*endAngle)*innerInnerRadius }; + Vector2 cap2FirstOuterVertex = { center.x + cosf(DEG2RAD*endAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*endAngle)*outerOuterRadius }; + + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2FirstInnerVertex.x, cap2FirstInnerVertex.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2SecondInnerVertex.x, cap2SecondInnerVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2SecondOuterVertex.x, cap2SecondOuterVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2FirstOuterVertex.x, cap2FirstOuterVertex.y); + + if (capsIntersect) + { + // Cap 1 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap1SecondInnerVertex.x, cap1SecondInnerVertex.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1SecondInnerVertex.x, cap1SecondInnerVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap1SecondOuterVertex.x, cap1SecondOuterVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(capIntersectionVertex.x, capIntersectionVertex.y); + + // Cap 2 + rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2SecondInnerVertex.x, cap2SecondInnerVertex.y); + + rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(capIntersectionVertex.x, capIntersectionVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); + rlVertex2f(cap2SecondOuterVertex.x, cap2SecondOuterVertex.y); + + rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); + rlVertex2f(cap2SecondInnerVertex.x, cap2SecondInnerVertex.y); + } + } + } + rlEnd(); +#else + rlBegin(RL_TRIANGLES); + + rlColor4ub(color.r, color.g, color.b, color.a); + + for (int i = 0; i < segments; i++) + { + // `innerRadius` outline + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerInnerRadius, center.y + sinf(DEG2RAD*angle)*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerInnerRadius, center.y + sinf(DEG2RAD*angle)*innerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerInnerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerInnerRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerInnerRadius, center.y + sinf(DEG2RAD*angle)*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerInnerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerInnerRadius); + + // `outerRadius` outline + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerOuterRadius, center.y + sinf(DEG2RAD*angle)*outerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*angle)*innerOuterRadius, center.y + sinf(DEG2RAD*angle)*innerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerOuterRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*angle)*outerOuterRadius, center.y + sinf(DEG2RAD*angle)*outerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*innerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(angle + stepLength))*outerOuterRadius, center.y + sinf(DEG2RAD*(angle + stepLength))*outerOuterRadius); + + angle += stepLength; + } + + if (showCapLines) + { + if (thick >= 0.0f) + { + angle = 0.0f; + + for (int i = 0; i < stepsBeforeOuter; i++) + { + // Cap 1 + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerOuterRadius); + + // Cap 2 + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius); + + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius); + + angle += stepLength; + } + + // We've already moved `stepsBeforeOuter` steps from each end + int totalStepsLeft = segments - stepsBeforeOuter*2; + int innerStepsLeft = stepsBeforeInner - stepsBeforeOuter; + + // Cap 1 + Vector2 cap1OuterVertexBeforeEnd = { center.x + cosf(DEG2RAD*(startAngle + angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*innerOuterRadius }; + Vector2 cap1OuterVertexAfterEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*innerOuterRadius }; + Vector2 cap1InnerVertexBeforeEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + (float)innerStepsLeft*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + (float)innerStepsLeft*stepLength))*outerInnerRadius }; + Vector2 cap1InnerVertexAfterEnd = { center.x + cosf(DEG2RAD*(startAngle + angle + (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius }; + Vector2 cap1InnerVertexEnd = { cap1InnerVertexBeforeEnd.x + (cap1InnerVertexAfterEnd.x - cap1InnerVertexBeforeEnd.x)*tInner, cap1InnerVertexBeforeEnd.y + (cap1InnerVertexAfterEnd.y - cap1InnerVertexBeforeEnd.y)*tInner }; + Vector2 cap1OuterVertexEnd = { cap1OuterVertexBeforeEnd.x + (cap1OuterVertexAfterEnd.x - cap1OuterVertexBeforeEnd.x)*tOuter, cap1OuterVertexBeforeEnd.y + (cap1OuterVertexAfterEnd.y - cap1OuterVertexBeforeEnd.y)*tOuter }; + + // Cap 2 + Vector2 cap2OuterVertexBeforeEnd = { center.x + cosf(DEG2RAD*(endAngle - angle))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*innerOuterRadius }; + Vector2 cap2OuterVertexAfterEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*innerOuterRadius }; + Vector2 cap2InnerVertexBeforeEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - (float)innerStepsLeft*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - (float)innerStepsLeft*stepLength))*outerInnerRadius }; + Vector2 cap2InnerVertexAfterEnd = { center.x + cosf(DEG2RAD*(endAngle - angle - (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - (float)(innerStepsLeft + 1)*stepLength))*outerInnerRadius }; + Vector2 cap2InnerVertexEnd = { cap2InnerVertexBeforeEnd.x + (cap2InnerVertexAfterEnd.x - cap2InnerVertexBeforeEnd.x)*tInner, cap2InnerVertexBeforeEnd.y + (cap2InnerVertexAfterEnd.y - cap2InnerVertexBeforeEnd.y)*tInner }; + Vector2 cap2OuterVertexEnd = { cap2OuterVertexBeforeEnd.x + (cap2OuterVertexAfterEnd.x - cap2OuterVertexBeforeEnd.x)*tOuter, cap2OuterVertexBeforeEnd.y + (cap2OuterVertexAfterEnd.y - cap2OuterVertexBeforeEnd.y)*tOuter }; + + int stepsCount = (innerAnglesCrossEachOther)? totalStepsLeft/2 : innerStepsLeft; + + for (int i = 0; i < stepsCount; i++) + { + // Cap 1 + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle + stepLength))*outerInnerRadius); + + // Cap 2 + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle - stepLength))*outerInnerRadius); + rlVertex2f(center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius); + + angle += stepLength; + } + + // When the inner angles coming from `startAngle` and `endAngle` cross each other, + // the `*innerVertexEnd` vertices go past each other and cause the geometry to intersect itself + if (innerAnglesCrossEachOther) + { + // We need to find where the line defined by `cap1InnerVertexEnd` and `cap1OuterVertexEnd` intersects + // the line defined by `cap2InnerVertexEnd` and `cap2OuterVertexEnd` + // That point is then used instead to prevent the outline from intersecting itself + + // Make `cap1InnerVertexEnd` the origin and the angle to `cap1OuterVertexEnd` 0 degrees + Vector2 tempCap1OuterVertexEnd = { cap1OuterVertexEnd.x - cap1InnerVertexEnd.x, cap1OuterVertexEnd.y - cap1InnerVertexEnd.y }; + Vector2 tempCap2InnerVertexEnd = { cap2InnerVertexEnd.x - cap1InnerVertexEnd.x, cap2InnerVertexEnd.y - cap1InnerVertexEnd.y }; + Vector2 tempCap2OuterVertexEnd = { cap2OuterVertexEnd.x - cap1InnerVertexEnd.x, cap2OuterVertexEnd.y - cap1InnerVertexEnd.y }; + + float rotateBy = -atan2f(tempCap1OuterVertexEnd.y, tempCap1OuterVertexEnd.x); + // We only need the y coordinates, so only rotate the y coordinates + float start = sinf(rotateBy)*tempCap2InnerVertexEnd.x + cosf(rotateBy)*tempCap2InnerVertexEnd.y; + float end = sinf(rotateBy)*tempCap2OuterVertexEnd.x + cosf(rotateBy)*tempCap2OuterVertexEnd.y; + float tCross = start/(start - end); + + Vector2 intersection = { cap2InnerVertexEnd.x + (cap2OuterVertexEnd.x - cap2InnerVertexEnd.x)*tCross, cap2InnerVertexEnd.y + (cap2OuterVertexEnd.y - cap2InnerVertexEnd.y)*tCross }; + + if (segments%2 == 0) + { + // There are an even number of segments, so there's 1 vertex exactly in the middle + + Vector2 middleInnerVertex = { center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius }; + + // Cap 1 + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + rlVertex2f(middleInnerVertex.x, middleInnerVertex.y); + + // Cap 2 + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(middleInnerVertex.x, middleInnerVertex.y); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + } + else + { + // There are an odd number of segments, so there are 2 vertices in the middle + + Vector2 middleInnerVertex1 = { center.x + cosf(DEG2RAD*(startAngle + angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(startAngle + angle))*outerInnerRadius }; + Vector2 middleInnerVertex2 = { center.x + cosf(DEG2RAD*(endAngle - angle))*outerInnerRadius, center.y + sinf(DEG2RAD*(endAngle - angle))*outerInnerRadius }; + + // Cap 1 + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + rlVertex2f(middleInnerVertex1.x, middleInnerVertex1.y); + + // Cap 2 + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(middleInnerVertex2.x, middleInnerVertex2.y); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + + // Triangle between the caps + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(middleInnerVertex1.x, middleInnerVertex1.y); + + rlVertex2f(intersection.x, intersection.y); + rlVertex2f(middleInnerVertex1.x, middleInnerVertex1.y); + rlVertex2f(middleInnerVertex2.x, middleInnerVertex2.y); + } + } + else + { + // Cap 1 + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + rlVertex2f(cap1InnerVertexBeforeEnd.x, cap1InnerVertexBeforeEnd.y); + rlVertex2f(cap1InnerVertexEnd.x, cap1InnerVertexEnd.y); + + rlVertex2f(cap1OuterVertexBeforeEnd.x, cap1OuterVertexBeforeEnd.y); + rlVertex2f(cap1InnerVertexEnd.x, cap1InnerVertexEnd.y); + rlVertex2f(cap1OuterVertexEnd.x, cap1OuterVertexEnd.y); + + // Cap 2 + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + rlVertex2f(cap2OuterVertexEnd.x, cap2OuterVertexEnd.y); + rlVertex2f(cap2InnerVertexEnd.x, cap2InnerVertexEnd.y); + + rlVertex2f(cap2OuterVertexBeforeEnd.x, cap2OuterVertexBeforeEnd.y); + rlVertex2f(cap2InnerVertexEnd.x, cap2InnerVertexEnd.y); + rlVertex2f(cap2InnerVertexBeforeEnd.x, cap2InnerVertexBeforeEnd.y); + } + } + else + { + // Cap 1 + Vector2 cap1FirstInnerVertex = { center.x + cosf(DEG2RAD*startAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*startAngle)*innerInnerRadius }; + Vector2 cap1FirstOuterVertex = { center.x + cosf(DEG2RAD*startAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*startAngle)*outerOuterRadius }; + + rlVertex2f(cap1FirstInnerVertex.x, cap1FirstInnerVertex.y); + rlVertex2f(cap1FirstOuterVertex.x, cap1FirstOuterVertex.y); + rlVertex2f(cap1SecondOuterVertex.x, cap1SecondOuterVertex.y); + + rlVertex2f(cap1FirstInnerVertex.x, cap1FirstInnerVertex.y); + rlVertex2f(cap1SecondOuterVertex.x, cap1SecondOuterVertex.y); + rlVertex2f(cap1SecondInnerVertex.x, cap1SecondInnerVertex.y); + + // Cap 2 + Vector2 cap2FirstInnerVertex = { center.x + cosf(DEG2RAD*endAngle)*innerInnerRadius, center.y + sinf(DEG2RAD*endAngle)*innerInnerRadius }; + Vector2 cap2FirstOuterVertex = { center.x + cosf(DEG2RAD*endAngle)*outerOuterRadius, center.y + sinf(DEG2RAD*endAngle)*outerOuterRadius }; + + rlVertex2f(cap2FirstInnerVertex.x, cap2FirstInnerVertex.y); + rlVertex2f(cap2SecondInnerVertex.x, cap2SecondInnerVertex.y); + rlVertex2f(cap2SecondOuterVertex.x, cap2SecondOuterVertex.y); + + rlVertex2f(cap2FirstInnerVertex.x, cap2FirstInnerVertex.y); + rlVertex2f(cap2SecondOuterVertex.x, cap2SecondOuterVertex.y); + rlVertex2f(cap2FirstOuterVertex.x, cap2FirstOuterVertex.y); + + if (capsIntersect) + { + // Cap 1 + rlVertex2f(cap1SecondInnerVertex.x, cap1SecondInnerVertex.y); + rlVertex2f(cap1SecondOuterVertex.x, cap1SecondOuterVertex.y); + rlVertex2f(capIntersectionVertex.x, capIntersectionVertex.y); + + // Cap 2 + rlVertex2f(cap2SecondInnerVertex.x, cap2SecondInnerVertex.y); + rlVertex2f(capIntersectionVertex.x, capIntersectionVertex.y); + rlVertex2f(cap2SecondOuterVertex.x, cap2SecondOuterVertex.y); + } + } + } + + rlEnd(); +#endif +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Splines functions //----------------------------------------------------------------------------------