#include #include #include #define ABS(n) ((n) < 0 ? -(n) : (n)) typedef struct Point2D { int x, y; float length; // Length between this point and the next one in the spline } Point2D; typedef struct Spline { Point2D *points; int n_points; float total_length; } Spline; Spline spline; float marker; float speed; void on_create(void); int clicked_point = -1; void on_update(void); bool looped; Point2D get_spline_point(float t); Point2D get_spline_gradient(float t); void update_lengths(void); float normalise_offset(float offset); Point2D ship[] = {{ 1,1 },{ 1,3 },{ 3,0 },{ 0,-3 },{ -3,0 },{ -1, 3 },{ -1,1 }}; int ship_length = 7; void draw_model(Point2D *points, int points_length, int x, int y, int scale, float rotation, Color color); static inline bool inside_circle(Vector2 v, int cx, int cy, float r){ return ABS(v.x - cx) <= r && ABS(v.y - cy) <= r; } int main(int argc, char *argv[]){ InitWindow(1600, 800, "Splines"); SetTargetFPS(144); on_create(); while (!WindowShouldClose()){ on_update(); BeginDrawing(); ClearBackground(BLACK); char buf[520]; sprintf(buf, "Length: %.3f", spline.total_length); DrawText(buf, 10.0f, 10.0f, 1, WHITE); for (int i = 0; i < spline.n_points; i++){ DrawCircle(spline.points[i].x, spline.points[i].y, 12.0f, RED); if (inside_circle(GetMousePosition(), spline.points[i].x, spline.points[i].y, 12.0f)){ DrawCircle(spline.points[i].x, spline.points[i].y, 12.0f, YELLOW); if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && clicked_point == -1) clicked_point = i; } // sprintf(buf,"[%d,%d]", spline.points[i].x, spline.points[i].y); // DrawText(buf, spline.points[i].x + 14.0f, spline.points[i].y, 1, WHITE); } float offset = normalise_offset(marker); Point2D p1 = get_spline_point(offset); Point2D g1 = get_spline_gradient(offset); float r = -1 * atan2(-g1.y, g1.x) + (PI / 2.0f); /*DrawLine(30.0f * sin(r) + p1.x, 30.0f * cos(r) + p1.y, -30.0f * sin(r) + p1.x, -30.0f * cos(r) + p1.y, BLUE);*/ draw_model(ship, ship_length, p1.x, p1.y, 12, r, BLUE); sprintf(buf, "Marker: %.3f", marker); DrawText(buf, 10.0f, 20.0f, 1, WHITE); sprintf(buf, "Speed: %.2f", speed); DrawText(buf, 10.0f, 30.0f, 1, WHITE); EndDrawing(); } CloseWindow(); return 0; } void on_create(void){ //static Point2D _points[] = {{100,410}, {400,410}, {700,410}, {1000,410}}; // spline.n_points = 4; // static Point2D _points[] = {{100,410},{200,410},{300,410},{400,410},{500,410},{600,410},{700,410},{800,410},{900,410},{1000,410}}; static Point2D points[10]; spline.n_points = 10; spline.points = points; for (int i = 0; i < 10; i++){ points[i] = (Point2D){ .x = 300.f * sinf((float)i / 10.0f * PI * 2.0f) + GetScreenWidth() / 2.0f, .y = 300.f * cosf((float)i / 10.0f * PI * 2.0f) + GetScreenHeight() / 2.0f, }; } marker = 0.0f; speed = 4.0f; looped = true; update_lengths(); } void on_update(void){ if (clicked_point != -1){ Vector2 mouse_pos = GetMousePosition(); if (mouse_pos.x >= 0 && mouse_pos.x < GetScreenWidth() && mouse_pos.y >= 0 && mouse_pos.y < GetScreenHeight() ){ spline.points[clicked_point].x = mouse_pos.x; spline.points[clicked_point].y = mouse_pos.y; update_lengths(); } } if (IsMouseButtonUp(MOUSE_BUTTON_LEFT)) clicked_point = -1; speed += GetMouseWheelMove() * 0.3f; if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)){ marker += speed; if (marker >= spline.total_length) marker = (int)marker % (int)spline.total_length; else if (marker < 0.0f) marker = spline.total_length; } for (float t = 0.0f; t < (float)spline.n_points; t+=0.0013f){ Point2D pos = get_spline_point(t); DrawPixel(pos.x, pos.y, WHITE); } } Point2D get_spline_point(float t){ int p0, p1, p2, p3; if (!looped){ p1 = (int)t + 1; p2 = p1 + 1; p3 = p2 + 1; p0 = p1 - 1; }else{ p1 = (int)t; p2 = (p1 + 1) % spline.n_points; p3 = (p2 + 1) % spline.n_points; p0 = (p1 - 1 + spline.n_points) % spline.n_points; // p0 could go negative if p1 is 0 } t = t - (int)t; float tt = t * t, ttt = tt * t; // Influential field values float q0 = -ttt + 2.0f*tt - t, q1 = 3.0f*ttt - 5.0f*tt + 2.0f, q2 = -3.0f*ttt + 4.0f*tt + t, q3 = ttt - tt; float tx = (spline.points[p0].x * q0 + spline.points[p1].x * q1 + spline.points[p2].x * q2 + spline.points[p3].x * q3) / 2.0f; float ty = (spline.points[p0].y * q0 + spline.points[p1].y * q1 + spline.points[p2].y * q2 + spline.points[p3].y * q3) / 2.0f; return (Point2D){tx,ty}; } Point2D get_spline_gradient(float t){ int p0, p1, p2, p3; if (!looped){ p1 = (int)t + 1; p2 = p1 + 1; p3 = p2 + 1; p0 = p1 - 1; }else{ p1 = (int)t; p2 = (p1 + 1) % spline.n_points; p3 = (p2 + 1) % spline.n_points; p0 = (p1 - 1 + spline.n_points) % spline.n_points; } t = t - (int)t; float tt = t * t, ttt = tt * t; // Influential field values float q0 = -3.0f*tt + 4.0f*t - 1, q1 = 9.0f*tt - 10.0f*t, q2 = -9.0f*tt + 8.0f*t + 1.0f, q3 = 3.0f*tt - 2.0f*t; float tx = (spline.points[p0].x * q0 + spline.points[p1].x * q1 + spline.points[p2].x * q2 + spline.points[p3].x * q3) / 2; float ty = (spline.points[p0].y * q0 + spline.points[p1].y * q1 + spline.points[p2].y * q2 + spline.points[p3].y * q3) / 2; return (Point2D){tx,ty}; } void update_lengths(){ float step_size = 0.001f; spline.total_length = 0.0f; for (int point = 0; point < spline.n_points; point++){ float length = 0.0f; Point2D old_point, new_point; old_point = get_spline_point((float)point); for (float t = 0; t < 1.0f; t += step_size){ new_point = get_spline_point((float)point + t); length += sqrtf((new_point.x - old_point.x) * (new_point.x - old_point.x) + (new_point.y - old_point.y) * (new_point.y - old_point.y)); old_point = new_point; } spline.points[point].length = length; spline.total_length += length; } } float normalise_offset(float offset){ // Get base node int i = 0; while (offset > spline.points[i].length){ offset -= spline.points[i].length; i++; } // The fractional part is the offset return (float)i + (offset / spline.points[i].length); } void draw_model(Point2D *points, int points_length, int x, int y, int scale, float rotation, Color color){ for (int i = 0; i < points_length; i++){ Point2D first = points[i % points_length]; Point2D second = points[(i + 1) % points_length]; Point2D third = points[(i + 2) % points_length]; int first_x = (first.x * scale), first_y = (first.y * scale), second_x = (second.x * scale), second_y = (second.y * scale); int rotated_first_x = first_x * cosf(rotation) - first_y * sinf(rotation), rotated_first_y = first_x * sinf(rotation) + first_y * cosf(rotation), rotated_second_x = second_x * cosf(rotation) - second_y * sinf(rotation), rotated_second_y = second_x * sinf(rotation) + second_y * cosf(rotation); int third_x = (third.x * scale), third_y = (third.y * scale); int rotated_third_x = third_x * cosf(rotation) - third_y * sinf(rotation), rotated_third_y = third_x * sinf(rotation) + third_y * cosf(rotation); DrawTriangle( (Vector2){x + rotated_first_x, y + rotated_first_y}, (Vector2){x + rotated_second_x, y + rotated_second_y}, (Vector2){x + rotated_third_x, y + rotated_third_y}, color); } }