From 9a3283f698881963c3f8e589353ddcb4599ab5d7 Mon Sep 17 00:00:00 2001 From: Ziya Date: Sat, 18 Apr 2026 20:40:07 +0400 Subject: [PATCH] [examples] `shapes_collision_ellipses` (#5722) * Added shapes_collision_ellipses example * Address review comments from raysan * Address review comments * update collision ellipses screenshot * Restore shapes_following_eyes.c to upstream * Rename shapes_collision_ellipses to shapes_ellipse_collision and fix header --- examples/shapes/shapes_ellipse_collision.c | 133 +++++++++++++++++++ examples/shapes/shapes_ellipse_collision.png | Bin 0 -> 16967 bytes 2 files changed, 133 insertions(+) create mode 100644 examples/shapes/shapes_ellipse_collision.c create mode 100644 examples/shapes/shapes_ellipse_collision.png diff --git a/examples/shapes/shapes_ellipse_collision.c b/examples/shapes/shapes_ellipse_collision.c new file mode 100644 index 000000000..4a6d7e911 --- /dev/null +++ b/examples/shapes/shapes_ellipse_collision.c @@ -0,0 +1,133 @@ +/******************************************************************************************* +* +* raylib [shapes] example - ellipse collision +* +* Example complexity rating: [★★☆☆] 2/4 +* +* Example originally created with raylib 5.5, last time updated with raylib 5.5 +* +* Example contributed by Ziya (@Monjaris) +* +* 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) 2025 Ziya (@Monjaris) +* +********************************************************************************************/ + +#include "raylib.h" +#include + +// Check if point is inside ellipse +static bool CheckCollisionPointEllipse(Vector2 point, Vector2 center, float rx, float ry) +{ + float dx = (point.x - center.x)/rx; + float dy = (point.y - center.y)/ry; + return (dx*dx + dy*dy) <= 1.0f; +} + +// Check if two ellipses collide +// Uses radial boundary distance in the direction between centers — scales correctly with radii +static bool CheckCollisionEllipses(Vector2 c1, float rx1, float ry1, Vector2 c2, float rx2, float ry2) +{ + float dx = c2.x - c1.x; + float dy = c2.y - c1.y; + float dist = sqrtf(dx*dx + dy*dy); + + // Ellipses are on top of each other + if (dist == 0.0f) return true; + + float theta = atan2f(dy, dx); + float cosT = cosf(theta); + float sinT = sinf(theta); + + // Radial distance from center to ellipse boundary in direction theta + // r(theta) = (rx * ry) / sqrt((ry*cos)^2 + (rx*sin)^2) + float r1 = (rx1*ry1)/sqrtf((ry1*cosT)*(ry1*cosT) + (rx1*sinT)*(rx1*sinT)); + float r2 = (rx2*ry2)/sqrtf((ry2*cosT)*(ry2*cosT) + (rx2*sinT)*(rx2*sinT)); + + return dist <= (r1 + r2); +} + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - collision ellipses"); + SetTargetFPS(60); + + Vector2 ellipseACenter = { (float)screenWidth/4, (float)screenHeight/2 }; + float ellipseARx = 120.0f; + float ellipseARy = 70.0f; + + Vector2 ellipseBCenter = { (float)screenWidth*3/4, (float)screenHeight/2 }; + float ellipseBRx = 90.0f; + float ellipseBRy = 140.0f; + + // 0 = controlling A, 1 = controlling B + int controlled = 0; + + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (IsKeyPressed(KEY_A)) controlled = 0; + if (IsKeyPressed(KEY_B)) controlled = 1; + + if (controlled == 0) ellipseACenter = GetMousePosition(); + else ellipseBCenter = GetMousePosition(); + + bool ellipsesCollide = CheckCollisionEllipses( + ellipseACenter, ellipseARx, ellipseARy, + ellipseBCenter, ellipseBRx, ellipseBRy + ); + + bool mouseInA = CheckCollisionPointEllipse(GetMousePosition(), ellipseACenter, ellipseARx, ellipseARy); + bool mouseInB = CheckCollisionPointEllipse(GetMousePosition(), ellipseBCenter, ellipseBRx, ellipseBRy); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawEllipse((int)ellipseACenter.x, (int)ellipseACenter.y, ellipseARx, ellipseARy, ellipsesCollide ? RED : BLUE); + + DrawEllipse((int)ellipseBCenter.x, (int)ellipseBCenter.y, ellipseBRx, ellipseBRy, ellipsesCollide ? RED : GREEN); + + DrawEllipseLines((int)ellipseACenter.x, (int)ellipseACenter.y, ellipseARx, ellipseARy, WHITE); + + DrawEllipseLines((int)ellipseBCenter.x, (int)ellipseBCenter.y, ellipseBRx, ellipseBRy, WHITE); + + DrawCircleV(ellipseACenter, 4, WHITE); + DrawCircleV(ellipseBCenter, 4, WHITE); + + if (ellipsesCollide) DrawText("ELLIPSES COLLIDE", screenWidth/2 - 120, 40, 28, RED); + else DrawText("NO COLLISION", screenWidth/2 - 80, 40, 28, DARKGRAY); + + DrawText(controlled == 0 ? "Controlling: A" : "Controlling: B", 20, screenHeight - 40, 20, YELLOW); + + if (mouseInA && controlled != 0) DrawText("Mouse inside ellipse A", 20, screenHeight - 70, 20, BLUE); + if (mouseInB && controlled != 1) DrawText("Mouse inside ellipse B", 20, screenHeight - 70, 20, GREEN); + + DrawText("Press [A] or [B] to switch control", 20, 20, 20, GRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + CloseWindow(); + + return 0; +} diff --git a/examples/shapes/shapes_ellipse_collision.png b/examples/shapes/shapes_ellipse_collision.png new file mode 100644 index 0000000000000000000000000000000000000000..30c66c164a3ad2ef0e4f17d7bd2d9b6a4ec638e0 GIT binary patch literal 16967 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 yO?^;