From efa7ef12e675fe3e5d9511df0b2fa25941f9ed8f Mon Sep 17 00:00:00 2001 From: Henry Wilder Date: Mon, 3 Mar 2025 22:20:42 -0500 Subject: [PATCH] Ensure DrawSplineSegmentBezierCubicVar renders somewhat correctly --- CHANGELOG | 13 +++ examples/shapes/shapes_splines_drawing.c | 40 +++++++- src/raylib.h | 2 +- src/rshapes.c | 120 ++++++++++++++--------- 4 files changed, 125 insertions(+), 50 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f792bca4d..60e35728a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -179,6 +179,19 @@ WIP: Last update with commit from 02-Nov-2024 [rshapes] REVIEWED: `CheckCollisionCircleRec()` (#3584) by @ubkp [rshapes] REVIEWED: Add more detail to function comment (#4344) by @Jeffery Myers [rshapes] REVIEWED: Functions that draw point arrays take them as const (#4051) by @Jeffery Myers +[rshapes] ADDED: `DrawSplineSegmentBezierCubicVar()` (#TBD) by @AmityWilder +[rshapes] ADDED: `GetSplineVelocityLinear()` (#TBD) by @AmityWilder +[rshapes] ADDED: `GetSplineVelocityBezierQuad()` (#TBD) by @AmityWilder +[rshapes] ADDED: `GetSplineVelocityBezierCubic()` (#TBD) by @AmityWilder +[rshapes] ADDED: `GetSplineAccelerationBezierQuad()` (#TBD) by @AmityWilder +[rshapes] ADDED: `GetSplineAccelerationBezierCubic()` (#TBD) by @AmityWilder +[rshapes] ADDED: `GetSplineJoltBezierCubic()` (#TBD) by @AmityWilder +[rshapes] ADDED: `GetSplineBoundsBezierLinear()` (#TBD) by @AmityWilder +[rshapes] ADDED: `GetSplineBoundsBezierQuad()` (#TBD) by @AmityWilder +[rshapes] ADDED: `GetSplineBoundsBezierCubic()` (#TBD) by @AmityWilder +[rshapes] ADDED: `GetSplineCurvatureBezierCubic()` (#TBD) by @AmityWilder +[rshapes] ADDED: `GetSplineNearestTLinear()` (#TBD) by @AmityWilder +[rshapes] ADDED: Example of `DrawSplineSegmentBezierCubicVar` in splines drawing example (#TBD) by @AmityWilder [rtextures] ADDED: `ColorIsEqual()` by @Ray [rtextures] ADDED: `ColorLerp()`, to mix 2 colors together (#4310) by @SusgUY446 [rtextures] ADDED: `LoadImageAnimFromMemory()` (#3681) by @IoIxD diff --git a/examples/shapes/shapes_splines_drawing.c b/examples/shapes/shapes_splines_drawing.c index 065050e8e..4ba63a18f 100644 --- a/examples/shapes/shapes_splines_drawing.c +++ b/examples/shapes/shapes_splines_drawing.c @@ -34,7 +34,8 @@ typedef enum { SPLINE_LINEAR = 0, // Linear SPLINE_BASIS, // B-Spline SPLINE_CATMULLROM, // Catmull-Rom - SPLINE_BEZIER // Cubic Bezier + SPLINE_BEZIER, // Cubic Bezier + SPLINE_BEZIER_VAR // Cubic Bezier, variable thickness } SplineType; //------------------------------------------------------------------------------------ @@ -78,7 +79,7 @@ int main(void) // Spline config variables float splineThickness = 8.0f; - int splineTypeActive = SPLINE_LINEAR; // 0-Linear, 1-BSpline, 2-CatmullRom, 3-Bezier + int splineTypeActive = SPLINE_BEZIER_VAR; // 0-Linear, 1-BSpline, 2-CatmullRom, 3-Bezier, 4-BezierVar bool splineTypeEditMode = false; bool splineHelpersActive = true; @@ -120,7 +121,7 @@ int main(void) } // Cubic Bezier spline control points logic - if ((splineTypeActive == SPLINE_BEZIER) && (focusedPoint == -1)) + if (((splineTypeActive == SPLINE_BEZIER) || (splineTypeActive == SPLINE_BEZIER_VAR)) && (focusedPoint == -1)) { // Spline control point focus and selection logic for (int i = 0; i < pointCount - 1; i++) @@ -153,6 +154,7 @@ int main(void) else if (IsKeyPressed(KEY_TWO)) splineTypeActive = 1; else if (IsKeyPressed(KEY_THREE)) splineTypeActive = 2; else if (IsKeyPressed(KEY_FOUR)) splineTypeActive = 3; + else if (IsKeyPressed(KEY_FIVE)) splineTypeActive = 4; //---------------------------------------------------------------------------------- // Draw @@ -215,7 +217,36 @@ int main(void) DrawSplineSegmentBezierCubic(pointsInterleaved[i], pointsInterleaved[i + 1], pointsInterleaved[i + 2], pointsInterleaved[i + 3], splineThickness, MAROON); } */ + } + else if (splineTypeActive == SPLINE_BEZIER_VAR) + { + float thicks[] = { + 0.0f, + splineThickness, + splineThickness, + 0.0f, + }; + // NOTE: Cubic-bezier spline requires the 2 control points of each segnment to be + // provided interleaved with the start and end point of every segment + for (int i = 0; i < (pointCount - 1); i++) + { + pointsInterleaved[3*i] = points[i]; + pointsInterleaved[3*i + 1] = control[i].start; + pointsInterleaved[3*i + 2] = control[i].end; + } + + pointsInterleaved[3*(pointCount - 1)] = points[pointCount - 1]; + + // Draw spline: cubic-bezier (with control points) + for (int i = 0; i < pointCount - 1; ++i) + { + DrawSplineSegmentBezierCubicVar(points[i], control[i].start, control[i].end, points[i+1], thicks, 4, RED); + } + } + + if ((splineTypeActive == SPLINE_BEZIER) || (splineTypeActive == SPLINE_BEZIER_VAR)) + { // Draw spline control points for (int i = 0; i < pointCount - 1; i++) { @@ -242,6 +273,7 @@ int main(void) DrawCircleLinesV(points[i], (focusedPoint == i)? 12.0f : 8.0f, (focusedPoint == i)? BLUE: DARKBLUE); if ((splineTypeActive != SPLINE_LINEAR) && (splineTypeActive != SPLINE_BEZIER) && + (splineTypeActive != SPLINE_BEZIER_VAR) && (i < pointCount - 1)) DrawLineV(points[i], points[i + 1], GRAY); DrawText(TextFormat("[%.0f, %.0f]", points[i].x, points[i].y), (int)points[i].x, (int)points[i].y + 10, 10, BLACK); @@ -260,7 +292,7 @@ int main(void) GuiUnlock(); GuiLabel((Rectangle){ 12, 10, 140, 24 }, "Spline type:"); - if (GuiDropdownBox((Rectangle){ 12, 8 + 24, 140, 28 }, "LINEAR;BSPLINE;CATMULLROM;BEZIER", &splineTypeActive, splineTypeEditMode)) splineTypeEditMode = !splineTypeEditMode; + if (GuiDropdownBox((Rectangle){ 12, 8 + 24, 140, 28 }, "LINEAR;BSPLINE;CATMULLROM;BEZIER;BEZIER VARIABLE", &splineTypeActive, splineTypeEditMode)) splineTypeEditMode = !splineTypeEditMode; EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/src/raylib.h b/src/raylib.h index b30594416..16677041b 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1298,7 +1298,7 @@ RLAPI void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4 RLAPI void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: Catmull-Rom, 4 points RLAPI void DrawSplineSegmentBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float thick, Color color); // Draw spline segment: Quadratic Bezier, 2 points, 1 control point RLAPI void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float thick, Color color); // Draw spline segment: Cubic Bezier, 2 points, 2 control points -RLAPI void DrawSplineSegmentBezierCubicVar(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, const float* thicks, int thickCount, Color color); // Draw spline segment with variable thickness: Cubic Bezier, 2 points, 2 control points, 1 or more thickness +RLAPI void DrawSplineSegmentBezierCubicVar(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, const float* thicks, int thickCount, Color color); // Draw spline segment with variable thickness: Cubic Bezier, 2 points, 2 control points // Spline segment point evaluation functions, for a given t [0.0f .. 1.0f] RLAPI Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t); // Get (evaluate) spline point: Linear diff --git a/src/rshapes.c b/src/rshapes.c index 9f78adf27..aedb58d1c 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -2107,63 +2107,93 @@ void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4 DrawTriangleStrip(points, 2*SPLINE_SEGMENT_DIVISIONS + 2, color); } -// Draw spline segment with variable thickness: Cubic Bezier, 2 points, 2 control points, 1 or more thickness +// Draw spline segment with variable thickness: Cubic Bezier, 2 points, 2 control points void DrawSplineSegmentBezierCubicVar(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, const float* thicks, int thickCount, Color color) { - if (thickCount >= 1) + if (thickCount >= 4) { - const float step = 1.0f/SPLINE_SEGMENT_DIVISIONS; + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); - Vector2 previous = p1; - Vector2 current = { 0 }; - float t = 0.0f; + const float step = 1.0f/SPLINE_SEGMENT_DIVISIONS; - Vector2 points[2*SPLINE_SEGMENT_DIVISIONS + 2] = { 0 }; + Vector2 previous[2] = { 0 }; + Vector2 current[2] = { 0 }; + float t = 0.0f; - for (int i = 1; i <= SPLINE_SEGMENT_DIVISIONS; i++) - { - t = step*(float)i; - - float thick; - if (thickCount > 1) { - float tMajor = t*(float)thickCount; - int tIndex = (int)tMajor; - if (tIndex >= thickCount) tIndex = thickCount - 1; - float tMinor = tMajor - (float)tIndex; - thick = thicks[tIndex]; - } else { - thick = thicks[0]; // constant thickness - } + for (int i = 0; i <= SPLINE_SEGMENT_DIVISIONS; i++) + { + t = step*(float)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); + Vector2 tangent = { 0 }; + { + float a = 3.0f*powf(1.0f - t, 2); + float b = 6.0f*(1.0f - t)*t; + float c = 3.0f*t*t; - current.y = a*p1.y + b*c2.y + c*c3.y + d*p4.y; - current.x = a*p1.x + b*c2.x + c*c3.x + d*p4.x; + tangent.x = a*(c2.x - p1.x) + b*(c3.x - c2.x) + c*(p4.x - c3.x); + tangent.y = a*(c2.y - p1.y) + b*(c3.y - c2.y) + c*(p4.y - c3.y); + } - float dy = current.y - previous.y; - float dx = current.x - previous.x; - float size = 0.5f*thick/sqrtf(dx*dx+dy*dy); + float speedSqr = (tangent.x*tangent.x + tangent.y*tangent.y); + if (speedSqr == 0) continue; + float speedInv = 1.0f/sqrtf(speedSqr); + tangent.x = tangent.x*speedInv; + tangent.y = tangent.y*speedInv; - 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; - } + 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*p1.y + b*c2.y + c*c3.y + d*p4.y; + point.x = a*p1.x + b*c2.x + c*c3.x + d*p4.x; + } + + // TODO: Doesn't seem to be working properly for more than 3 distinct values + float thick; + { + float tMajor = t*(float)thickCount; + int tIndex = (int)tMajor; + float tMinor = tMajor - (float)tIndex; + tIndex *= 3; + if (tIndex >= thickCount - 3) + { + tIndex = thickCount - 4; + tMinor = 1.0f; + } + float a = powf(1.0f - t, 3); + float b = 3.0f*powf(1.0f - t, 2)*t; + float c = 3.0f*(1.0f - t)*t*t; + float d = t*t*t; + + thick = a*thicks[tIndex] + b*thicks[tIndex + 1] + c*thicks[tIndex + 2] + d*thicks[tIndex + 3]; + } - 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; + current[0].x = point.x + thick*tangent.y; + current[0].y = point.y - thick*tangent.x; - previous = current; - } + current[1].x = point.x - thick*tangent.y; + current[1].y = point.y + thick*tangent.x; + + if (i > 0) + { + rlVertex2f(current[0].x, current[0].y); + rlVertex2f(previous[0].x, previous[0].y); + rlVertex2f(previous[1].x, previous[1].y); + + rlVertex2f(current[1].x, current[1].y); + rlVertex2f(current[0].x, current[0].y); + rlVertex2f(previous[1].x, previous[1].y); + } - DrawTriangleStrip(points, 2*SPLINE_SEGMENT_DIVISIONS + 2, color); + previous[0] = current[0]; + previous[1] = current[1]; + } + + rlEnd(); } } @@ -2576,7 +2606,7 @@ float GetSplineCurvatureBezierCubic(Vector2 startPos, Vector2 startControlPos, V acceleration.x = a*(endControlPos.x - 2.0f*startControlPos.x + startPos.x) + b*(endPos.x - 2.0f*endControlPos.x + startControlPos.x); acceleration.y = a*(endControlPos.y - 2.0f*startControlPos.y + startPos.y) + b*(endPos.y - 2.0f*endControlPos.y + startControlPos.y); - float curvature = (velocity.x*acceleration.y - velocity.y*acceleration.x)/powf(sqrtf(velocity.x*velocity.x + velocity.y*velocity.y), 3); + curvature = (velocity.x*acceleration.y - velocity.y*acceleration.x)/powf(sqrtf(velocity.x*velocity.x + velocity.y*velocity.y), 3); return curvature; }