diff --git a/CHANGELOG b/CHANGELOG index 244d329d..2b47cd14 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -52,7 +52,7 @@ Detailed changes: [rlgl] REVIEWED: rLoadTexture() UBSAN complaints #1891 (#3321) by @Codom [rlgl] REVIEWED: glInternalFormat as unsigned int [rshapes] ADDED: Spline drawing functions by @raysan5 -[rshapes] REVIEWED: DrawLineCatmullRom() by @raysan5 +[rshapes] REVIEWED: DrawSplineCatmullRom() by @raysan5 [rshapes] REVIEWED: Minor fix in DrawLineBezier* (#3006) by @eternalStudent [rshapes] REVIEWED: GetCollisionRec(), more performant (#3052) by @manuel5975p [rshapes] REVIEWED: Fix off-by-one error in CheckCollisionPointRec() (#3022) by @dbechrd @@ -651,7 +651,7 @@ Detailed changes: [raymath] REVIEWED: QuaternionFromAxisAngle() (#1892) [raymath] REVIEWED: QuaternionToMatrix() returning transposed result. (#1793) by @object71 [shapes] ADDED: RenderPolyLinesEx() (#1758) by @lambertwang -[shapes] ADDED: DrawLineBezierCubic() (#2021) by @SAOMDVN +[shapes] ADDED: DrawSplineBezierCubic() (#2021) by @SAOMDVN [textures] ADDED: GetImageColor() #2024 [textures] REMOVED: GenImagePerlinNoise() [textures] RENAMED: GetTextureData() to LoadImageFromTexture() @@ -789,7 +789,7 @@ Detailed changes: [rlgl] REDESIGNED: rlLoadExtensions(), more details exposed [raymath] REVIEWED: QuaternionFromEuler() (#1651) [raymath] REVIEWED: MatrixRotateZYX() (#1642) -[shapes] ADDED: DrawLineBezierQuad() (#1468) by @epsilon-phase +[shapes] ADDED: DrawSplineBezierQuad() (#1468) by @epsilon-phase [shapes] ADDED: CheckCollisionLines() [shapes] ADDED: CheckCollisionPointLine() by @mkupiec1 [shapes] REVIEWED: CheckCollisionPointTriangle() by @mkupiec1 diff --git a/examples/shapes/shapes_lines_bezier.c b/examples/shapes/shapes_lines_bezier.c index 7d7bdb0c..5bd916ee 100644 --- a/examples/shapes/shapes_lines_bezier.c +++ b/examples/shapes/shapes_lines_bezier.c @@ -30,7 +30,7 @@ int main(void) Vector2 end = { (float)screenWidth, (float)screenHeight }; Vector2 startControl = { 100, 0 }; - Vector2 endControl = { (float)GetScreenWidth() - 100, (float)GetScreenHeight() }; + Vector2 endControl = { GetScreenWidth() - 100, GetScreenHeight() }; SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -60,9 +60,11 @@ int main(void) DrawText("USE MOUSE LEFT-RIGHT CLICK to DEFINE LINE START and END POINTS", 15, 20, 20, GRAY); - //DrawLineBezier(start, end, 2.0f, RED); + // Draw line cubic-bezier, in-out interpolation (easing), no control points + DrawLineBezier(start, end, 3.0f, BLUE); - DrawLineBezierCubic(start, end, startControl, endControl, 2.0f, RED); + // Draw spline cubic-bezier with control points + DrawSplineBezierCubic(start, startControl, endControl, end, 2.0f, RED); DrawLineEx(start, startControl, 1.0, LIGHTGRAY); DrawLineEx(end, endControl, 1.0, LIGHTGRAY); diff --git a/examples/shapes/shapes_lines_splines.c b/examples/shapes/shapes_splines_drawing.c similarity index 52% rename from examples/shapes/shapes_lines_splines.c rename to examples/shapes/shapes_splines_drawing.c index c020c60b..711aad21 100644 --- a/examples/shapes/shapes_lines_splines.c +++ b/examples/shapes/shapes_splines_drawing.c @@ -13,13 +13,25 @@ #include "raylib.h" -#define MAX_CONTROL_POINTS 32 +#include // Required for: NULL +#define MAX_SPLINE_POINTS 32 + +// Bezier spline control points +// NOTE: Every segment has two control points typedef struct { Vector2 start; Vector2 end; } ControlPoint; +// Spline types +typedef enum { + SPLINE_LINEAR = 0, + SPLINE_BASIS, // B-Spline + SPLINE_CATMULLROM, + SPLINE_BEZIER +} SplineType; + //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ @@ -33,7 +45,7 @@ int main(void) SetConfigFlags(FLAG_MSAA_4X_HINT); InitWindow(screenWidth, screenHeight, "raylib [shapes] example - splines drawing"); - Vector2 points[MAX_CONTROL_POINTS] = { + Vector2 points[MAX_SPLINE_POINTS] = { { 100.0f, 200.0f }, { 300.0f, 400.0f }, { 500.0f, 300.0f }, @@ -43,15 +55,18 @@ int main(void) int pointCount = 5; int selectedPoint = -1; + int focusedPoint = -1; + Vector2 *selectedControlPoint = NULL; + Vector2 *focusedControlPoint = NULL; - int splineType = 0; // 0-Linear, 1-BSpline, 2-CatmullRom, 3-Bezier + int splineType = SPLINE_LINEAR; // 0-Linear, 1-BSpline, 2-CatmullRom, 3-Bezier - // Cubic Bezier control points - ControlPoint control[MAX_CONTROL_POINTS] = { 0 }; + // Cubic Bezier control points initialization + ControlPoint control[MAX_SPLINE_POINTS] = { 0 }; for (int i = 0; i < pointCount - 1; i++) { - control[i].start = points[i]; - control[i].end = points[i + 1]; + control[i].start = (Vector2){ points[i].x - 20, points[i].y - 20 }; + control[i].end = (Vector2){ points[i + 1].x + 20, points[i + 1].y + 20 }; } SetTargetFPS(60); // Set our game to run at 60 frames-per-second @@ -62,30 +77,60 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - // Points movement logic - if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON) && (pointCount < MAX_CONTROL_POINTS)) + // Spline points creation logic (at the end of spline) + if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON) && (pointCount < MAX_SPLINE_POINTS)) { points[pointCount] = GetMousePosition(); pointCount++; } + // Spline point focus and selection logic for (int i = 0; i < pointCount; i++) { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && CheckCollisionPointCircle(GetMousePosition(), points[i], 6.0f)) + if (CheckCollisionPointCircle(GetMousePosition(), points[i], 8.0f)) { - selectedPoint = i; + focusedPoint = i; + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) selectedPoint = i; break; } + else focusedPoint = -1; } - + + // Spline point movement logic if (selectedPoint >= 0) { points[selectedPoint] = GetMousePosition(); if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) selectedPoint = -1; } - // TODO: Cubic Bezier spline control points logic - + // Cubic Bezier spline control points logic + if ((splineType == SPLINE_BEZIER) && (focusedPoint == -1)) + { + // Spline control point focus and selection logic + for (int i = 0; i < pointCount; i++) + { + if (CheckCollisionPointCircle(GetMousePosition(), control[i].start, 6.0f)) + { + focusedControlPoint = &control[i].start; + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) selectedControlPoint = &control[i].start; + break; + } + else if (CheckCollisionPointCircle(GetMousePosition(), control[i].end, 6.0f)) + { + focusedControlPoint = &control[i].end; + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) selectedControlPoint = &control[i].end; + break; + } + else focusedControlPoint = NULL; + } + + // Spline control point movement logic + if (selectedControlPoint != NULL) + { + *selectedControlPoint = GetMousePosition(); + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) selectedControlPoint = NULL; + } + } // Spline selection logic if (IsKeyPressed(KEY_ONE)) splineType = 0; @@ -100,47 +145,48 @@ int main(void) ClearBackground(RAYWHITE); - if (splineType == 0) // Linear + if (splineType == SPLINE_LINEAR) { - // Draw linear spline - for (int i = 0; i < pointCount - 1; i++) - { - DrawLineEx(points[i], points[i + 1], 2.0f, RED); - } + // Draw spline: linear + DrawSplineLinear(points, pointCount, 2.0f, RED); } - else if (splineType == 1) // B-Spline + else if (splineType == SPLINE_BASIS) { - // Draw b-spline - DrawLineBSpline(points, pointCount, 2.0f, RED); - //for (int i = 0; i < (pointCount - 3); i++) DrawLineBSplineSegment(points[i], points[i + 1], points[i + 2], points[i + 3], 24.0f, BLUE); + // Draw spline: basis + DrawSplineBasis(points, pointCount, 2.0f, RED); + //for (int i = 0; i < (pointCount - 3); i++) DrawSplineBasisSegment(points[i], points[i + 1], points[i + 2], points[i + 3], 24.0f, BLUE); } - else if (splineType == 2) // CatmullRom Spline + else if (splineType == SPLINE_CATMULLROM) { // Draw spline: catmull-rom - DrawLineCatmullRom(points, pointCount, 2.0f, RED); - //for (int i = 0; i < (pointCount - 3); i++) DrawLineCatmullRomSegment(points[i], points[i + 1], points[i + 2], points[i + 3], 24.0f, Fade(BLUE, 0.4f)); + DrawSplineCatmullRom(points, pointCount, 2.0f, RED); + //for (int i = 0; i < (pointCount - 3); i++) DrawSplineCatmullRomSegment(points[i], points[i + 1], points[i + 2], points[i + 3], 24.0f, Fade(BLUE, 0.4f)); } - else if (splineType == 3) // Cubic Bezier + else if (splineType == SPLINE_BEZIER) { - // Draw line bezier cubic (with control points) + // Draw spline: cubic-bezier (with control points) for (int i = 0; i < pointCount - 1; i++) { - DrawLineBezierCubic(points[i], points[i + 1], control[i].start, control[i + 1].end, 2.0f, RED); + DrawSplineBezierCubic(points[i], control[i].start, control[i].end, points[i + 1], 2.0f, RED); - // TODO: Every cubic bezier point should have two control points + // Every cubic bezier point should have two control points DrawCircleV(control[i].start, 4, GOLD); DrawCircleV(control[i].end, 4, GOLD); + if (focusedControlPoint == &control[i].start) DrawCircleV(control[i].start, 6, GREEN); + else if (focusedControlPoint == &control[i].end) DrawCircleV(control[i].end, 6, GREEN); DrawLineEx(points[i], control[i].start, 1.0, LIGHTGRAY); DrawLineEx(points[i + 1], control[i].end, 1.0, LIGHTGRAY); } } - // Draw control points + // Draw spline key-points for (int i = 0; i < pointCount; i++) { - DrawCircleV(points[i], 6.0f, RED); - if ((splineType != 0) && (i < pointCount - 1)) DrawLineV(points[i], points[i + 1], GRAY); + DrawCircleV(points[i], (focusedPoint == i)? 8.0f : 5.0f, (focusedPoint == i)? BLUE: RED); + if ((splineType != 0) && (i < pointCount - 1)) DrawLineV(points[i], points[i + 1], LIGHTGRAY); } + + // TODO: Draw help EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/src/raylib.h b/src/raylib.h index 4a353a70..9cbb72a0 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1221,14 +1221,10 @@ RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set t RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel (Vector version) RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line -RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (Vector version) -RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line defining thickness -RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line using cubic-bezier curves in-out -RLAPI void DrawLineBezierQuad(Vector2 startPos, Vector2 endPos, Vector2 controlPos, float thick, Color color); // Draw line using quadratic bezier curves with a control point -RLAPI void DrawLineBezierCubic(Vector2 startPos, Vector2 endPos, Vector2 startControlPos, Vector2 endControlPos, float thick, Color color); // Draw line using cubic bezier curves with 2 control points -RLAPI void DrawLineBSpline(Vector2 *points, int pointCount, float thick, Color color); // Draw a B-Spline line, minimum 4 points -RLAPI void DrawLineCatmullRom(Vector2 *points, int pointCount, float thick, Color color); // Draw a Catmull Rom spline line, minimum 4 points -RLAPI void DrawLineStrip(Vector2 *points, int pointCount, Color color); // Draw lines sequence +RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines) +RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads) +RLAPI void DrawLineStrip(Vector2 *points, int pointCount, Color color); // Draw lines sequence (using gl lines) +RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw line segment cubic-bezier in-out interpolation RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline @@ -1259,6 +1255,20 @@ RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Col RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters +// Splines drawing functions +RLAPI void DrawSplineLinear(Vector2 *points, int pointCount, float thick, Color color); // Draw spline: linear, minimum 2 points +RLAPI void DrawSplineBasis(Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points +RLAPI void DrawSplineCatmullRom(Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull Rom, minimum 4 points +RLAPI void DrawSplineBezierQuad(Vector2 startPos, Vector2 controlPos, Vector2 endPos, float thick, Color color); // Draw spline segment: quadratic-bezier, one control point +RLAPI void DrawSplineBezierCubic(Vector2 startPos, Vector2 startControlPos, Vector2 endControlPos, Vector2 endPos, float thick, Color color); // Draw spline segment: cubic-bezier, two control points + +// Get (evaluate) spline point for a given t [0.0f .. 1.0f] +RLAPI Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t); // Get (evaluate) spline point: linear +RLAPI Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: B-Spline +RLAPI Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: Catmull-Rom +RLAPI Vector2 GetSplinePointBezierQuad(Vector2 startPos, Vector2 controlPos, Vector2 endPos, float t); // Get (evaluate) spline point: quadratic-bezier +RLAPI Vector2 GetSplinePointBezierCubic(Vector2 startPos, Vector2 startControlPos, Vector2 endControlPos, Vector2 endPos, float t); // Get (evaluate) spline point: cubic-bezier + // Basic shapes collision detection functions RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles diff --git a/src/rshapes.c b/src/rshapes.c index e8e533d3..8b6c1721 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -161,7 +161,7 @@ void DrawPixelV(Vector2 position, Color color) #endif } -// Draw a line +// Draw a line (using gl lines) void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color) { rlBegin(RL_LINES); @@ -171,7 +171,7 @@ void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color colo rlEnd(); } -// Draw a line (Vector version) +// Draw a line (using gl lines) void DrawLineV(Vector2 startPos, Vector2 endPos, Color color) { rlBegin(RL_LINES); @@ -181,29 +181,24 @@ void DrawLineV(Vector2 startPos, Vector2 endPos, Color color) rlEnd(); } -// Draw a line defining thickness -void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color) +// Draw lines sequuence (using gl lines) +void DrawLineStrip(Vector2 *points, int pointCount, Color color) { - Vector2 delta = { endPos.x - startPos.x, endPos.y - startPos.y }; - float length = sqrtf(delta.x*delta.x + delta.y*delta.y); - - if ((length > 0) && (thick > 0)) + if (pointCount >= 2) { - float scale = thick/(2*length); + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); - Vector2 radius = { -scale*delta.y, scale*delta.x }; - Vector2 strip[4] = { - { startPos.x - radius.x, startPos.y - radius.y }, - { startPos.x + radius.x, startPos.y + radius.y }, - { endPos.x - radius.x, endPos.y - radius.y }, - { endPos.x + radius.x, endPos.y + radius.y } - }; - - DrawTriangleStrip(strip, 4, color); + for (int i = 0; i < pointCount - 1; i++) + { + rlVertex2f(points[i].x, points[i].y); + rlVertex2f(points[i + 1].x, points[i + 1].y); + } + rlEnd(); } } -// Draw line using cubic-bezier curves in-out +// Draw line using cubic-bezier spline, in-out interpolation, no control points void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color) { Vector2 previous = startPos; @@ -241,254 +236,25 @@ void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color) DrawTriangleStrip(points, 2*SPLINE_LINE_DIVISIONS + 2, color); } -// Draw line using quadratic bezier curves with a control point -void DrawLineBezierQuad(Vector2 startPos, Vector2 endPos, Vector2 controlPos, float thick, Color color) +// Draw a line defining thickness +void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color) { - const float step = 1.0f/SPLINE_LINE_DIVISIONS; + Vector2 delta = { endPos.x - startPos.x, endPos.y - startPos.y }; + float length = sqrtf(delta.x*delta.x + delta.y*delta.y); - Vector2 previous = startPos; - Vector2 current = { 0 }; - float t = 0.0f; - - Vector2 points[2*SPLINE_LINE_DIVISIONS + 2] = { 0 }; - - for (int i = 1; i <= SPLINE_LINE_DIVISIONS; i++) + if ((length > 0) && (thick > 0)) { - t = step*i; + float scale = thick/(2*length); - float a = powf(1.0f - t, 2); - float b = 2.0f*(1.0f - t)*t; - float c = powf(t, 2); + Vector2 radius = { -scale*delta.y, scale*delta.x }; + Vector2 strip[4] = { + { startPos.x - radius.x, startPos.y - radius.y }, + { startPos.x + radius.x, startPos.y + radius.y }, + { endPos.x - radius.x, endPos.y - radius.y }, + { endPos.x + radius.x, endPos.y + radius.y } + }; - // NOTE: The easing functions aren't suitable here because they don't take a control point - current.y = a*startPos.y + b*controlPos.y + c*endPos.y; - current.x = a*startPos.x + b*controlPos.x + c*endPos.x; - - float dy = current.y - previous.y; - float dx = current.x - previous.x; - float size = 0.5f*thick/sqrtf(dx*dx+dy*dy); - - if (i == 1) - { - points[0].x = previous.x + dy*size; - points[0].y = previous.y - dx*size; - points[1].x = previous.x - dy*size; - points[1].y = previous.y + dx*size; - } - - points[2*i + 1].x = current.x - dy*size; - points[2*i + 1].y = current.y + dx*size; - points[2*i].x = current.x + dy*size; - points[2*i].y = current.y - dx*size; - - previous = current; - } - - DrawTriangleStrip(points, 2*SPLINE_LINE_DIVISIONS + 2, color); -} - -// Draw line using cubic bezier curves with 2 control points -void DrawLineBezierCubic(Vector2 startPos, Vector2 endPos, Vector2 startControlPos, Vector2 endControlPos, float thick, Color color) -{ - const float step = 1.0f/SPLINE_LINE_DIVISIONS; - - Vector2 previous = startPos; - Vector2 current = { 0 }; - float t = 0.0f; - - Vector2 points[2*SPLINE_LINE_DIVISIONS + 2] = { 0 }; - - for (int i = 1; i <= SPLINE_LINE_DIVISIONS; i++) - { - t = step*i; - - float a = powf(1.0f - t, 3); - float b = 3.0f*powf(1.0f - t, 2)*t; - float c = 3.0f*(1.0f - t)*powf(t, 2); - float d = powf(t, 3); - - current.y = a*startPos.y + b*startControlPos.y + c*endControlPos.y + d*endPos.y; - current.x = a*startPos.x + b*startControlPos.x + c*endControlPos.x + d*endPos.x; - - float dy = current.y - previous.y; - float dx = current.x - previous.x; - float size = 0.5f*thick/sqrtf(dx*dx+dy*dy); - - if (i == 1) - { - points[0].x = previous.x + dy*size; - points[0].y = previous.y - dx*size; - points[1].x = previous.x - dy*size; - points[1].y = previous.y + dx*size; - } - - points[2*i + 1].x = current.x - dy*size; - points[2*i + 1].y = current.y + dx*size; - points[2*i].x = current.x + dy*size; - points[2*i].y = current.y - dx*size; - - previous = current; - } - - DrawTriangleStrip(points, 2*SPLINE_LINE_DIVISIONS + 2, color); -} - -// Draw a B-Spline line, minimum 4 points -void DrawLineBSpline(Vector2 *points, int pointCount, float thick, Color color) -{ - if (pointCount < 4) return; - - float a[4] = { 0 }; - float b[4] = { 0 }; - float dy = 0.0f; - float dx = 0.0f; - float size = 0.0f; - - Vector2 currentPoint = { 0 }; - Vector2 nextPoint = { 0 }; - Vector2 vertices[2*SPLINE_LINE_DIVISIONS + 2] = { 0 }; - - for (int i = 0; i < (pointCount - 3); i++) - { - float t = 0.0f; - Vector2 p1 = points[i], p2 = points[i + 1], p3 = points[i + 2], p4 = points[i + 3]; - - a[0] = (-p1.x + 3.0f*p2.x - 3.0f*p3.x + p4.x)/6.0f; - a[1] = (3.0f*p1.x - 6.0f*p2.x + 3.0f*p3.x)/6.0f; - a[2] = (-3.0f*p1.x + 3.0f*p3.x)/6.0f; - a[3] = (p1.x + 4.0f*p2.x + p3.x)/6.0f; - - b[0] = (-p1.y + 3.0f*p2.y - 3.0f*p3.y + p4.y)/6.0f; - b[1] = (3.0f*p1.y - 6.0f*p2.y + 3.0f*p3.y)/6.0f; - b[2] = (-3.0f*p1.y + 3.0f*p3.y)/6.0f; - b[3] = (p1.y + 4.0f*p2.y + p3.y)/6.0f; - - currentPoint.x = a[3]; - currentPoint.y = b[3]; - - if (i == 0) DrawCircleV(currentPoint, thick/2.0f, color); // Draw init line circle-cap - - if (i > 0) - { - vertices[0].x = currentPoint.x + dy*size; - vertices[0].y = currentPoint.y - dx*size; - vertices[1].x = currentPoint.x - dy*size; - vertices[1].y = currentPoint.y + dx*size; - } - - for (int j = 1; j <= SPLINE_LINE_DIVISIONS; j++) - { - t = ((float)j)/((float)SPLINE_LINE_DIVISIONS); - - nextPoint.x = a[3] + t*(a[2] + t*(a[1] + t*a[0])); - nextPoint.y = b[3] + t*(b[2] + t*(b[1] + t*b[0])); - - dy = nextPoint.y - currentPoint.y; - dx = nextPoint.x - currentPoint.x; - size = 0.5f*thick/sqrtf(dx*dx+dy*dy); - - if ((i == 0) && (j == 1)) - { - vertices[0].x = currentPoint.x + dy*size; - vertices[0].y = currentPoint.y - dx*size; - vertices[1].x = currentPoint.x - dy*size; - vertices[1].y = currentPoint.y + dx*size; - } - - vertices[2*j + 1].x = nextPoint.x - dy*size; - vertices[2*j + 1].y = nextPoint.y + dx*size; - vertices[2*j].x = nextPoint.x + dy*size; - vertices[2*j].y = nextPoint.y - dx*size; - - currentPoint = nextPoint; - } - - DrawTriangleStrip(vertices, 2*SPLINE_LINE_DIVISIONS + 2, color); - } - - DrawCircleV(currentPoint, thick/2.0f, color); // Draw end line circle-cap -} - -// Draw a Catmull Rom spline line, minimum 4 points -void DrawLineCatmullRom(Vector2 *points, int pointCount, float thick, Color color) -{ - if (pointCount < 4) return; - - float dy = 0.0f; - float dx = 0.0f; - float size = 0.0f; - - Vector2 currentPoint = points[1]; - Vector2 nextPoint = { 0 }; - Vector2 vertices[2*SPLINE_LINE_DIVISIONS + 2] = { 0 }; - - DrawCircleV(currentPoint, thick/2.0f, color); // Draw init line circle-cap - - for (int i = 0; i < (pointCount - 3); i++) - { - float t = 0.0f; - Vector2 p1 = points[i], p2 = points[i + 1], p3 = points[i + 2], p4 = points[i + 3]; - - if (i > 0) - { - vertices[0].x = currentPoint.x + dy*size; - vertices[0].y = currentPoint.y - dx*size; - vertices[1].x = currentPoint.x - dy*size; - vertices[1].y = currentPoint.y + dx*size; - } - - for (int j = 1; j <= SPLINE_LINE_DIVISIONS; j++) - { - t = ((float)j)/((float)SPLINE_LINE_DIVISIONS); - - float q0 = (-1.0f*t*t*t) + (2.0f*t*t) + (-1.0f*t); - float q1 = (3.0f*t*t*t) + (-5.0f*t*t) + 2.0f; - float q2 = (-3.0f*t*t*t) + (4.0f*t*t) + t; - float q3 = t*t*t - t*t; - - nextPoint.x = 0.5f*((p1.x*q0) + (p2.x*q1) + (p3.x*q2) + (p4.x*q3)); - nextPoint.y = 0.5f*((p1.y*q0) + (p2.y*q1) + (p3.y*q2) + (p4.y*q3)); - - dy = nextPoint.y - currentPoint.y; - dx = nextPoint.x - currentPoint.x; - size = (0.5f*thick)/sqrtf(dx*dx + dy*dy); - - if ((i == 0) && (j == 1)) - { - vertices[0].x = currentPoint.x + dy*size; - vertices[0].y = currentPoint.y - dx*size; - vertices[1].x = currentPoint.x - dy*size; - vertices[1].y = currentPoint.y + dx*size; - } - - vertices[2*j + 1].x = nextPoint.x - dy*size; - vertices[2*j + 1].y = nextPoint.y + dx*size; - vertices[2*j].x = nextPoint.x + dy*size; - vertices[2*j].y = nextPoint.y - dx*size; - - currentPoint = nextPoint; - } - - DrawTriangleStrip(vertices, 2*SPLINE_LINE_DIVISIONS + 2, color); - } - - DrawCircleV(currentPoint, thick/2.0f, color); // Draw end line circle-cap -} - -// Draw lines sequence -void DrawLineStrip(Vector2 *points, int pointCount, Color color) -{ - if (pointCount >= 2) - { - rlBegin(RL_LINES); - rlColor4ub(color.r, color.g, color.b, color.a); - - for (int i = 0; i < pointCount - 1; i++) - { - rlVertex2f(points[i].x, points[i].y); - rlVertex2f(points[i + 1].x, points[i + 1].y); - } - rlEnd(); + DrawTriangleStrip(strip, 4, color); } } @@ -1773,6 +1539,353 @@ void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, fl #endif } +//---------------------------------------------------------------------------------- +// Module Functions Definition - Splines functions +//---------------------------------------------------------------------------------- + +// Draw spline: linear, minimum 2 points +void DrawSplineLinear(Vector2 *points, int pointCount, float thick, Color color) +{ + Vector2 delta = { 0 }; + float length = 0.0f; + float scale = 0.0f; + + for (int i = 0; i < pointCount - 1; i++) + { + delta = (Vector2){ points[i + 1].x - points[i].x, points[i + 1].y - points[i].y }; + length = sqrtf(delta.x*delta.x + delta.y*delta.y); + + if (length > 0) scale = thick/(2*length); + + Vector2 radius = { -scale*delta.y, scale*delta.x }; + Vector2 strip[4] = { + { points[i].x - radius.x, points[i].y - radius.y }, + { points[i].x + radius.x, points[i].y + radius.y }, + { points[i + 1].x - radius.x, points[i + 1].y - radius.y }, + { points[i + 1].x + radius.x, points[i + 1].y + radius.y } + }; + + DrawTriangleStrip(strip, 4, color); + } +} + +// Draw spline: B-Spline, minimum 4 points +void DrawSplineBasis(Vector2 *points, int pointCount, float thick, Color color) +{ + if (pointCount < 4) return; + + float a[4] = { 0 }; + float b[4] = { 0 }; + float dy = 0.0f; + float dx = 0.0f; + float size = 0.0f; + + Vector2 currentPoint = { 0 }; + Vector2 nextPoint = { 0 }; + Vector2 vertices[2*SPLINE_LINE_DIVISIONS + 2] = { 0 }; + + for (int i = 0; i < (pointCount - 3); i++) + { + float t = 0.0f; + Vector2 p1 = points[i], p2 = points[i + 1], p3 = points[i + 2], p4 = points[i + 3]; + + a[0] = (-p1.x + 3.0f*p2.x - 3.0f*p3.x + p4.x)/6.0f; + a[1] = (3.0f*p1.x - 6.0f*p2.x + 3.0f*p3.x)/6.0f; + a[2] = (-3.0f*p1.x + 3.0f*p3.x)/6.0f; + a[3] = (p1.x + 4.0f*p2.x + p3.x)/6.0f; + + b[0] = (-p1.y + 3.0f*p2.y - 3.0f*p3.y + p4.y)/6.0f; + b[1] = (3.0f*p1.y - 6.0f*p2.y + 3.0f*p3.y)/6.0f; + b[2] = (-3.0f*p1.y + 3.0f*p3.y)/6.0f; + b[3] = (p1.y + 4.0f*p2.y + p3.y)/6.0f; + + currentPoint.x = a[3]; + currentPoint.y = b[3]; + + if (i == 0) DrawCircleV(currentPoint, thick/2.0f, color); // Draw init line circle-cap + + if (i > 0) + { + vertices[0].x = currentPoint.x + dy*size; + vertices[0].y = currentPoint.y - dx*size; + vertices[1].x = currentPoint.x - dy*size; + vertices[1].y = currentPoint.y + dx*size; + } + + for (int j = 1; j <= SPLINE_LINE_DIVISIONS; j++) + { + t = ((float)j)/((float)SPLINE_LINE_DIVISIONS); + + nextPoint.x = a[3] + t*(a[2] + t*(a[1] + t*a[0])); + nextPoint.y = b[3] + t*(b[2] + t*(b[1] + t*b[0])); + + dy = nextPoint.y - currentPoint.y; + dx = nextPoint.x - currentPoint.x; + size = 0.5f*thick/sqrtf(dx*dx+dy*dy); + + if ((i == 0) && (j == 1)) + { + vertices[0].x = currentPoint.x + dy*size; + vertices[0].y = currentPoint.y - dx*size; + vertices[1].x = currentPoint.x - dy*size; + vertices[1].y = currentPoint.y + dx*size; + } + + vertices[2*j + 1].x = nextPoint.x - dy*size; + vertices[2*j + 1].y = nextPoint.y + dx*size; + vertices[2*j].x = nextPoint.x + dy*size; + vertices[2*j].y = nextPoint.y - dx*size; + + currentPoint = nextPoint; + } + + DrawTriangleStrip(vertices, 2*SPLINE_LINE_DIVISIONS + 2, color); + } + + DrawCircleV(currentPoint, thick/2.0f, color); // Draw end line circle-cap +} + +// Draw spline: Catmull Rom, minimum 4 points +void DrawSplineCatmullRom(Vector2 *points, int pointCount, float thick, Color color) +{ + if (pointCount < 4) return; + + float dy = 0.0f; + float dx = 0.0f; + float size = 0.0f; + + Vector2 currentPoint = points[1]; + Vector2 nextPoint = { 0 }; + Vector2 vertices[2*SPLINE_LINE_DIVISIONS + 2] = { 0 }; + + DrawCircleV(currentPoint, thick/2.0f, color); // Draw init line circle-cap + + for (int i = 0; i < (pointCount - 3); i++) + { + float t = 0.0f; + Vector2 p1 = points[i], p2 = points[i + 1], p3 = points[i + 2], p4 = points[i + 3]; + + if (i > 0) + { + vertices[0].x = currentPoint.x + dy*size; + vertices[0].y = currentPoint.y - dx*size; + vertices[1].x = currentPoint.x - dy*size; + vertices[1].y = currentPoint.y + dx*size; + } + + for (int j = 1; j <= SPLINE_LINE_DIVISIONS; j++) + { + t = ((float)j)/((float)SPLINE_LINE_DIVISIONS); + + float q0 = (-1.0f*t*t*t) + (2.0f*t*t) + (-1.0f*t); + float q1 = (3.0f*t*t*t) + (-5.0f*t*t) + 2.0f; + float q2 = (-3.0f*t*t*t) + (4.0f*t*t) + t; + float q3 = t*t*t - t*t; + + nextPoint.x = 0.5f*((p1.x*q0) + (p2.x*q1) + (p3.x*q2) + (p4.x*q3)); + nextPoint.y = 0.5f*((p1.y*q0) + (p2.y*q1) + (p3.y*q2) + (p4.y*q3)); + + dy = nextPoint.y - currentPoint.y; + dx = nextPoint.x - currentPoint.x; + size = (0.5f*thick)/sqrtf(dx*dx + dy*dy); + + if ((i == 0) && (j == 1)) + { + vertices[0].x = currentPoint.x + dy*size; + vertices[0].y = currentPoint.y - dx*size; + vertices[1].x = currentPoint.x - dy*size; + vertices[1].y = currentPoint.y + dx*size; + } + + vertices[2*j + 1].x = nextPoint.x - dy*size; + vertices[2*j + 1].y = nextPoint.y + dx*size; + vertices[2*j].x = nextPoint.x + dy*size; + vertices[2*j].y = nextPoint.y - dx*size; + + currentPoint = nextPoint; + } + + DrawTriangleStrip(vertices, 2*SPLINE_LINE_DIVISIONS + 2, color); + } + + DrawCircleV(currentPoint, thick/2.0f, color); // Draw end line circle-cap +} + + +// Draw spline segment: quadratic-bezier, one control point +void DrawSplineBezierQuad(Vector2 startPos, Vector2 endPos, Vector2 controlPos, float thick, Color color) +{ + const float step = 1.0f/SPLINE_LINE_DIVISIONS; + + Vector2 previous = startPos; + Vector2 current = { 0 }; + float t = 0.0f; + + Vector2 points[2*SPLINE_LINE_DIVISIONS + 2] = { 0 }; + + for (int i = 1; i <= SPLINE_LINE_DIVISIONS; i++) + { + t = step*i; + + float a = powf(1.0f - t, 2); + float b = 2.0f*(1.0f - t)*t; + float c = powf(t, 2); + + // NOTE: The easing functions aren't suitable here because they don't take a control point + current.y = a*startPos.y + b*controlPos.y + c*endPos.y; + current.x = a*startPos.x + b*controlPos.x + c*endPos.x; + + float dy = current.y - previous.y; + float dx = current.x - previous.x; + float size = 0.5f*thick/sqrtf(dx*dx+dy*dy); + + if (i == 1) + { + points[0].x = previous.x + dy*size; + points[0].y = previous.y - dx*size; + points[1].x = previous.x - dy*size; + points[1].y = previous.y + dx*size; + } + + points[2*i + 1].x = current.x - dy*size; + points[2*i + 1].y = current.y + dx*size; + points[2*i].x = current.x + dy*size; + points[2*i].y = current.y - dx*size; + + previous = current; + } + + DrawTriangleStrip(points, 2*SPLINE_LINE_DIVISIONS + 2, color); +} + +// Draw spline segment: cubic-bezier, two control point +void DrawSplineBezierCubic(Vector2 startPos, Vector2 startControlPos, Vector2 endControlPos, Vector2 endPos, float thick, Color color) +{ + const float step = 1.0f/SPLINE_LINE_DIVISIONS; + + Vector2 previous = startPos; + Vector2 current = { 0 }; + float t = 0.0f; + + Vector2 points[2*SPLINE_LINE_DIVISIONS + 2] = { 0 }; + + for (int i = 1; i <= SPLINE_LINE_DIVISIONS; i++) + { + t = step*i; + + float a = powf(1.0f - t, 3); + float b = 3.0f*powf(1.0f - t, 2)*t; + float c = 3.0f*(1.0f - t)*powf(t, 2); + float d = powf(t, 3); + + current.y = a*startPos.y + b*startControlPos.y + c*endControlPos.y + d*endPos.y; + current.x = a*startPos.x + b*startControlPos.x + c*endControlPos.x + d*endPos.x; + + float dy = current.y - previous.y; + float dx = current.x - previous.x; + float size = 0.5f*thick/sqrtf(dx*dx+dy*dy); + + if (i == 1) + { + points[0].x = previous.x + dy*size; + points[0].y = previous.y - dx*size; + points[1].x = previous.x - dy*size; + points[1].y = previous.y + dx*size; + } + + points[2*i + 1].x = current.x - dy*size; + points[2*i + 1].y = current.y + dx*size; + points[2*i].x = current.x + dy*size; + points[2*i].y = current.y - dx*size; + + previous = current; + } + + DrawTriangleStrip(points, 2*SPLINE_LINE_DIVISIONS + 2, color); +} + +// Get spline point for a given t [0.0f .. 1.0f], Linear +Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t) +{ + Vector2 point = { 0 }; + + point.x = startPos.x*(1.0f - t) + endPos.x*t; + point.y = startPos.y*(1.0f - t) + endPos.y*t; + + return point; +} + +// Get spline point for a given t [0.0f .. 1.0f], B-Spline +Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t) +{ + Vector2 point = { 0 }; + + float a[4] = { 0 }; + float b[4] = { 0 }; + + a[0] = (-p1.x + 3*p2.x - 3*p3.x + p4.x)/6.0f; + a[1] = (3*p1.x - 6*p2.x + 3*p3.x)/6.0f; + a[2] = (-3*p1.x + 3*p3.x)/6.0f; + a[3] = (p1.x + 4*p2.x + p3.x)/6.0f; + + b[0] = (-p1.y + 3*p2.y - 3*p3.y + p4.y)/6.0f; + b[1] = (3*p1.y - 6*p2.y + 3*p3.y)/6.0f; + b[2] = (-3*p1.y + 3*p3.y)/6.0f; + b[3] = (p1.y + 4*p2.y + p3.y)/6.0f; + + point.x = a[3] + t*(a[2] + t*(a[1] + t*a[0])); + point.y = b[3] + t*(b[2] + t*(b[1] + t*b[0])); + + return point; +} + +// Get spline point for a given t [0.0f .. 1.0f], Catmull-Rom +Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t) +{ + Vector2 point = { 0 }; + + float q0 = (-1*t*t*t) + (2*t*t) + (-1*t); + float q1 = (3*t*t*t) + (-5*t*t) + 2; + float q2 = (-3*t*t*t) + (4*t*t) + t; + float q3 = t*t*t - t*t; + + point.x = 0.5f*((p1.x*q0) + (p2.x*q1) + (p3.x*q2) + (p4.x*q3)); + point.y = 0.5f*((p1.y*q0) + (p2.y*q1) + (p3.y*q2) + (p4.y*q3)); + + return point; +} + +// Get spline point for a given t [0.0f .. 1.0f], Quadratic Bezier +Vector2 GetSplinePointBezierQuad(Vector2 startPos, Vector2 controlPos, Vector2 endPos, float t) +{ + Vector2 point = { 0 }; + + float a = powf(1.0f - t, 2); + float b = 2.0f*(1.0f - t)*t; + float c = powf(t, 2); + + point.y = a*startPos.y + b*controlPos.y + c*endPos.y; + point.x = a*startPos.x + b*controlPos.x + c*endPos.x; + + return point; +} + +// Get spline point for a given t [0.0f .. 1.0f], Cubic Bezier +Vector2 GetSplinePointBezierCubic(Vector2 startPos, Vector2 startControlPos, Vector2 endControlPos, Vector2 endPos, float t) +{ + Vector2 point = { 0 }; + + float a = powf(1.0f - t, 3); + float b = 3.0f*powf(1.0f - t, 2)*t; + float c = 3.0f*(1.0f - t)*powf(t, 2); + float d = powf(t, 3); + + point.y = a*startPos.y + b*startControlPos.y + c*endControlPos.y + d*endPos.y; + point.x = a*startPos.x + b*startControlPos.x + c*endControlPos.x + d*endPos.x; + + return point; +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Collision Detection functions //----------------------------------------------------------------------------------