diff --git a/.gitignore b/.gitignore index b221a37b..cf9cdfe1 100644 --- a/.gitignore +++ b/.gitignore @@ -72,4 +72,5 @@ src/libraylib.bc # external libraries DLLs !src/external/glfw3/lib/win32/glfw3.dll !src/external/openal_soft/lib/win32/OpenAL32.dll -!src/external/OculusSDK/LibOVR/LibOVRRT32_1.dll \ No newline at end of file +!src/external/OculusSDK/LibOVR/LibOVRRT32_1.dll +!src/external/pthread/pthreadGC2.dll \ No newline at end of file diff --git a/examples/physics_basic_rigidbody.c b/examples/physics_basic_rigidbody.c index 8870c55b..75720c97 100644 --- a/examples/physics_basic_rigidbody.c +++ b/examples/physics_basic_rigidbody.c @@ -5,6 +5,10 @@ * This example has been created using raylib 1.5 (www.raylib.com) * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) * +* +* Compile example using: +* cmd /c IF NOT EXIST pthreadGC2.dll copy C:\raylib\raylib\src\external\pthread\pthreadGC2.dll $(CURRENT_DIRECTORY) /Y +* * Copyright (c) 2016 Victor Fisac and Ramon Santamaria (@raysan5) * ********************************************************************************************/ @@ -25,7 +29,6 @@ int main() int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [physac] example - basic rigidbody"); - InitPhysics((Vector2){ 0.0f, -9.81f/2 }); // Initialize physics module // Debug variables @@ -60,11 +63,9 @@ int main() while (!WindowShouldClose()) // Detect window close button or ESC key { // Update - //---------------------------------------------------------------------------------- - UpdatePhysics(); // Update all created physic objects - + //---------------------------------------------------------------------------------- // Check rectangle movement inputs - if (IsKeyDown('W') && rectangle->rigidbody.isGrounded) rectangle->rigidbody.velocity.y = JUMP_VELOCITY; + if (IsKeyPressed('W')) rectangle->rigidbody.velocity.y = JUMP_VELOCITY; if (IsKeyDown('A')) rectangle->rigidbody.velocity.x = -MOVE_VELOCITY; else if (IsKeyDown('D')) rectangle->rigidbody.velocity.x = MOVE_VELOCITY; @@ -111,6 +112,8 @@ int main() // Draw help message DrawText("Use WASD to move rectangle and ARROWS to move square", screenWidth/2 - MeasureText("Use WASD to move rectangle and ARROWS to move square", 20)/2, screenHeight*0.075f, 20, LIGHTGRAY); + DrawFPS(10, 10); + EndDrawing(); //---------------------------------------------------------------------------------- } @@ -118,7 +121,6 @@ int main() // De-Initialization //-------------------------------------------------------------------------------------- ClosePhysics(); // Unitialize physics (including all loaded objects) - CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/examples/physics_forces.c b/examples/physics_forces.c index 3e90a21d..efe8e240 100644 --- a/examples/physics_forces.c +++ b/examples/physics_forces.c @@ -5,6 +5,11 @@ * This example has been created using raylib 1.5 (www.raylib.com) * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) * +* NOTE: This example requires raylib module [rlgl] +* +* Compile example using: +* cmd /c IF NOT EXIST pthreadGC2.dll copy C:\raylib\raylib\src\external\pthread\pthreadGC2.dll $(CURRENT_DIRECTORY) /Y +* * Copyright (c) 2016 Victor Fisac and Ramon Santamaria (@raysan5) * ********************************************************************************************/ @@ -27,7 +32,6 @@ int main() int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [physac] example - forces"); - InitPhysics((Vector2){ 0.0f, -9.81f/2 }); // Initialize physics module // Global variables @@ -69,7 +73,6 @@ int main() { // Update //---------------------------------------------------------------------------------- - UpdatePhysics(); // Update all created physic objects // Update mouse position value mousePosition = GetMousePosition(); @@ -166,7 +169,9 @@ int main() // Draw help messages DrawText("Use LEFT MOUSE BUTTON to apply a force", screenWidth/2 - MeasureText("Use LEFT MOUSE BUTTON to apply a force", 20)/2, screenHeight*0.075f, 20, LIGHTGRAY); - DrawText("Use R to reset objects position", screenWidth/2 - MeasureText("Use R to reset objects position", 20)/2, screenHeight*0.875f, 20, GRAY); + DrawText("Use R to reset objects position", screenWidth/2 - MeasureText("Use R to reset objects position", 20)/2, screenHeight*0.875f, 20, GRAY); + + DrawFPS(10, 10); EndDrawing(); //---------------------------------------------------------------------------------- @@ -175,7 +180,6 @@ int main() // De-Initialization //-------------------------------------------------------------------------------------- ClosePhysics(); // Unitialize physics module - CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/src/external/pthread/pthreadGC2.dll b/src/external/pthread/pthreadGC2.dll new file mode 100644 index 00000000..67b9289d Binary files /dev/null and b/src/external/pthread/pthreadGC2.dll differ diff --git a/src/physac.h b/src/physac.h index 6a90dc29..dd4c4126 100644 --- a/src/physac.h +++ b/src/physac.h @@ -15,6 +15,10 @@ * The generated implementation will stay private inside implementation file and all * internal symbols and functions will only be visible inside that file. * +* #define PHYSAC_NO_THREADS +* The generated implementation won't include pthread library and user must create a secondary thread to call PhysicsThread(). +* It is so important that the thread where PhysicsThread() is called must not have v-sync or any other CPU limitation. +* * #define PHYSAC_STANDALONE * Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined * internally in the library and input management and drawing functions must be provided by @@ -27,12 +31,16 @@ * * LIMITATIONS: * -* // TODO. +* - There is a limit of 256 physic objects. +* - Physics behaviour can be unexpected using bounciness or friction values out of 0.0f - 1.0f range. +* - The module is limited to 2D axis oriented physics. +* - Physics colliders must be rectangle or circle shapes (there is not a custom polygon collider type). * * VERSIONS: * -* 1.0 (09-Jun-2016) Module names review and converted to header-only. -* 0.9 (23-Mar-2016) Complete module redesign, steps-based for better physics resolution. +* 1.0 (14-Jun-2016) New module defines and fixed some delta time calculation bugs. +* 0.9 (09-Jun-2016) Module names review and converted to header-only. +* 0.8 (23-Mar-2016) Complete module redesign, steps-based for better physics resolution. * 0.3 (13-Feb-2016) Reviewed to add PhysicObjects pool. * 0.2 (03-Jan-2016) Improved physics calculations. * 0.1 (30-Dec-2015) Initial release. @@ -146,7 +154,7 @@ typedef struct PhysicBodyData { // Module Functions Declaration //---------------------------------------------------------------------------------- PHYSACDEF void InitPhysics(Vector2 gravity); // Initializes pointers array (just pointers, fixed size) -PHYSACDEF void UpdatePhysics(); // Update physic objects, calculating physic behaviours and collisions detection +PHYSACDEF void* PhysicsThread(void *arg); // Physics calculations thread function PHYSACDEF void ClosePhysics(); // Unitialize all physic objects and empty the objects pool PHYSACDEF PhysicBody CreatePhysicBody(Vector2 position, float rotation, Vector2 scale); // Create a new physic body dinamically, initialize it and add to pool @@ -177,12 +185,26 @@ PHYSACDEF Rectangle TransformToRectangle(Transform transform); #endif #include // Required for: cos(), sin(), abs(), fminf() +#include // Required for typedef unsigned long long int uint64_t, used by hi-res timer + +#ifndef PHYSAC_NO_THREADS + #include // Required for: pthread_create() +#endif + +#if defined(PLATFORM_DESKTOP) + // Functions required to query time on Windows + int __stdcall QueryPerformanceCounter(unsigned long long int *lpPerformanceCount); + int __stdcall QueryPerformanceFrequency(unsigned long long int *lpFrequency); +#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + #include // Required for: timespec + #include // Required for: clock_gettime() +#endif //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- #define MAX_PHYSIC_BODIES 256 // Maximum available physic bodies slots in bodies pool -#define PHYSICS_STEPS 64 // Physics update steps per frame for improved collision-detection +#define PHYSICS_TIMESTEP 0.016666 // Physics fixed time step (1/fps) #define PHYSICS_ACCURACY 0.0001f // Velocity subtract operations round filter (friction) #define PHYSICS_ERRORPERCENT 0.001f // Collision resolve position fix @@ -195,6 +217,9 @@ PHYSACDEF Rectangle TransformToRectangle(Transform transform); //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- +static bool physicsThreadEnabled = false; // Physics calculations thread exit control +static uint64_t baseTime; // Base time measure for hi-res timer +static double currentTime, previousTime; // Used to track timmings static PhysicBody physicBodies[MAX_PHYSIC_BODIES]; // Physic bodies pool static int physicBodiesCount; // Counts current enabled physic bodies static Vector2 gravityForce; // Gravity force @@ -202,6 +227,9 @@ static Vector2 gravityForce; // Gravity f //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- +static void UpdatePhysics(double deltaTime); // Update physic objects, calculating physic behaviours and collisions detection +static void InitTimer(void); // Initialize hi-resolution timer +static double GetCurrentTime(void); // Time measure returned are microseconds static float Vector2DotProduct(Vector2 v1, Vector2 v2); // Returns the dot product of two Vector2 static float Vector2Length(Vector2 v); // Returns the length of a Vector2 @@ -215,392 +243,20 @@ PHYSACDEF void InitPhysics(Vector2 gravity) // Initialize physics variables physicBodiesCount = 0; gravityForce = gravity; -} - -// Update physic objects, calculating physic behaviours and collisions detection -PHYSACDEF void UpdatePhysics() -{ - // Reset all physic objects is grounded state - for (int i = 0; i < physicBodiesCount; i++) physicBodies[i]->rigidbody.isGrounded = false; - for (int steps = 0; steps < PHYSICS_STEPS; steps++) - { - for (int i = 0; i < physicBodiesCount; i++) - { - if (physicBodies[i]->enabled) - { - // Update physic behaviour - if (physicBodies[i]->rigidbody.enabled) - { - // Apply friction to acceleration in X axis - if (physicBodies[i]->rigidbody.acceleration.x > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.x -= physicBodies[i]->rigidbody.friction/PHYSICS_STEPS; - else if (physicBodies[i]->rigidbody.acceleration.x < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.x += physicBodies[i]->rigidbody.friction/PHYSICS_STEPS; - else physicBodies[i]->rigidbody.acceleration.x = 0.0f; - - // Apply friction to acceleration in Y axis - if (physicBodies[i]->rigidbody.acceleration.y > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.y -= physicBodies[i]->rigidbody.friction/PHYSICS_STEPS; - else if (physicBodies[i]->rigidbody.acceleration.y < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.y += physicBodies[i]->rigidbody.friction/PHYSICS_STEPS; - else physicBodies[i]->rigidbody.acceleration.y = 0.0f; - - // Apply friction to velocity in X axis - if (physicBodies[i]->rigidbody.velocity.x > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.x -= physicBodies[i]->rigidbody.friction/PHYSICS_STEPS; - else if (physicBodies[i]->rigidbody.velocity.x < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.x += physicBodies[i]->rigidbody.friction/PHYSICS_STEPS; - else physicBodies[i]->rigidbody.velocity.x = 0.0f; - - // Apply friction to velocity in Y axis - if (physicBodies[i]->rigidbody.velocity.y > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.y -= physicBodies[i]->rigidbody.friction/PHYSICS_STEPS; - else if (physicBodies[i]->rigidbody.velocity.y < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.y += physicBodies[i]->rigidbody.friction/PHYSICS_STEPS; - else physicBodies[i]->rigidbody.velocity.y = 0.0f; - - // Apply gravity to velocity - if (physicBodies[i]->rigidbody.applyGravity) - { - physicBodies[i]->rigidbody.velocity.x += gravityForce.x/PHYSICS_STEPS; - physicBodies[i]->rigidbody.velocity.y += gravityForce.y/PHYSICS_STEPS; - } - - // Apply acceleration to velocity - physicBodies[i]->rigidbody.velocity.x += physicBodies[i]->rigidbody.acceleration.x/PHYSICS_STEPS; - physicBodies[i]->rigidbody.velocity.y += physicBodies[i]->rigidbody.acceleration.y/PHYSICS_STEPS; - - // Apply velocity to position - physicBodies[i]->transform.position.x += physicBodies[i]->rigidbody.velocity.x/PHYSICS_STEPS; - physicBodies[i]->transform.position.y -= physicBodies[i]->rigidbody.velocity.y/PHYSICS_STEPS; - } - - // Update collision detection - if (physicBodies[i]->collider.enabled) - { - // Update collider bounds - physicBodies[i]->collider.bounds = TransformToRectangle(physicBodies[i]->transform); - - // Check collision with other colliders - for (int k = 0; k < physicBodiesCount; k++) - { - if (physicBodies[k]->collider.enabled && i != k) - { - // Resolve physic collision - // NOTE: collision resolve is generic for all directions and conditions (no axis separated cases behaviours) - // and it is separated in rigidbody attributes resolve (velocity changes by impulse) and position correction (position overlap) - - // 1. Calculate collision normal - // ------------------------------------------------------------------------------------------------------------------------------------- - - // Define collision contact normal, direction and penetration depth - Vector2 contactNormal = { 0.0f, 0.0f }; - Vector2 direction = { 0.0f, 0.0f }; - float penetrationDepth = 0.0f; - - switch (physicBodies[i]->collider.type) - { - case COLLIDER_RECTANGLE: - { - switch (physicBodies[k]->collider.type) - { - case COLLIDER_RECTANGLE: - { - // Check if colliders are overlapped - if (CheckCollisionRecs(physicBodies[i]->collider.bounds, physicBodies[k]->collider.bounds)) - { - // Calculate direction vector from i to k - direction.x = (physicBodies[k]->transform.position.x + physicBodies[k]->transform.scale.x/2) - (physicBodies[i]->transform.position.x + physicBodies[i]->transform.scale.x/2); - direction.y = (physicBodies[k]->transform.position.y + physicBodies[k]->transform.scale.y/2) - (physicBodies[i]->transform.position.y + physicBodies[i]->transform.scale.y/2); - - // Define overlapping and penetration attributes - Vector2 overlap; - - // Calculate overlap on X axis - overlap.x = (physicBodies[i]->transform.scale.x + physicBodies[k]->transform.scale.x)/2 - abs(direction.x); - - // SAT test on X axis - if (overlap.x > 0.0f) - { - // Calculate overlap on Y axis - overlap.y = (physicBodies[i]->transform.scale.y + physicBodies[k]->transform.scale.y)/2 - abs(direction.y); - - // SAT test on Y axis - if (overlap.y > 0.0f) - { - // Find out which axis is axis of least penetration - if (overlap.y > overlap.x) - { - // Point towards k knowing that direction points from i to k - if (direction.x < 0.0f) contactNormal = (Vector2){ -1.0f, 0.0f }; - else contactNormal = (Vector2){ 1.0f, 0.0f }; - - // Update penetration depth for position correction - penetrationDepth = overlap.x; - } - else - { - // Point towards k knowing that direction points from i to k - if (direction.y < 0.0f) contactNormal = (Vector2){ 0.0f, 1.0f }; - else contactNormal = (Vector2){ 0.0f, -1.0f }; - - // Update penetration depth for position correction - penetrationDepth = overlap.y; - } - } - } - } - } break; - case COLLIDER_CIRCLE: - { - if (CheckCollisionCircleRec(physicBodies[k]->transform.position, physicBodies[k]->collider.radius, physicBodies[i]->collider.bounds)) - { - // Calculate direction vector between circles - direction.x = physicBodies[k]->transform.position.x - physicBodies[i]->transform.position.x + physicBodies[i]->transform.scale.x/2; - direction.y = physicBodies[k]->transform.position.y - physicBodies[i]->transform.position.y + physicBodies[i]->transform.scale.y/2; - - // Calculate closest point on rectangle to circle - Vector2 closestPoint = { 0.0f, 0.0f }; - if (direction.x > 0.0f) closestPoint.x = physicBodies[i]->collider.bounds.x + physicBodies[i]->collider.bounds.width; - else closestPoint.x = physicBodies[i]->collider.bounds.x; - - if (direction.y > 0.0f) closestPoint.y = physicBodies[i]->collider.bounds.y + physicBodies[i]->collider.bounds.height; - else closestPoint.y = physicBodies[i]->collider.bounds.y; - - // Check if the closest point is inside the circle - if (CheckCollisionPointCircle(closestPoint, physicBodies[k]->transform.position, physicBodies[k]->collider.radius)) - { - // Recalculate direction based on closest point position - direction.x = physicBodies[k]->transform.position.x - closestPoint.x; - direction.y = physicBodies[k]->transform.position.y - closestPoint.y; - float distance = Vector2Length(direction); - - // Calculate final contact normal - contactNormal.x = direction.x/distance; - contactNormal.y = -direction.y/distance; - - // Calculate penetration depth - penetrationDepth = physicBodies[k]->collider.radius - distance; - } - else - { - if (abs(direction.y) < abs(direction.x)) - { - // Calculate final contact normal - if (direction.y > 0.0f) - { - contactNormal = (Vector2){ 0.0f, -1.0f }; - penetrationDepth = fabs(physicBodies[i]->collider.bounds.y - physicBodies[k]->transform.position.y - physicBodies[k]->collider.radius); - } - else - { - contactNormal = (Vector2){ 0.0f, 1.0f }; - penetrationDepth = fabs(physicBodies[i]->collider.bounds.y - physicBodies[k]->transform.position.y + physicBodies[k]->collider.radius); - } - } - else - { - // Calculate final contact normal - if (direction.x > 0.0f) - { - contactNormal = (Vector2){ 1.0f, 0.0f }; - penetrationDepth = fabs(physicBodies[k]->transform.position.x + physicBodies[k]->collider.radius - physicBodies[i]->collider.bounds.x); - } - else - { - contactNormal = (Vector2){ -1.0f, 0.0f }; - penetrationDepth = fabs(physicBodies[i]->collider.bounds.x + physicBodies[i]->collider.bounds.width - physicBodies[k]->transform.position.x - physicBodies[k]->collider.radius); - } - } - } - } - } break; - } - } break; - case COLLIDER_CIRCLE: - { - switch (physicBodies[k]->collider.type) - { - case COLLIDER_RECTANGLE: - { - if (CheckCollisionCircleRec(physicBodies[i]->transform.position, physicBodies[i]->collider.radius, physicBodies[k]->collider.bounds)) - { - // Calculate direction vector between circles - direction.x = physicBodies[k]->transform.position.x + physicBodies[i]->transform.scale.x/2 - physicBodies[i]->transform.position.x; - direction.y = physicBodies[k]->transform.position.y + physicBodies[i]->transform.scale.y/2 - physicBodies[i]->transform.position.y; - - // Calculate closest point on rectangle to circle - Vector2 closestPoint = { 0.0f, 0.0f }; - if (direction.x > 0.0f) closestPoint.x = physicBodies[k]->collider.bounds.x + physicBodies[k]->collider.bounds.width; - else closestPoint.x = physicBodies[k]->collider.bounds.x; - - if (direction.y > 0.0f) closestPoint.y = physicBodies[k]->collider.bounds.y + physicBodies[k]->collider.bounds.height; - else closestPoint.y = physicBodies[k]->collider.bounds.y; - - // Check if the closest point is inside the circle - if (CheckCollisionPointCircle(closestPoint, physicBodies[i]->transform.position, physicBodies[i]->collider.radius)) - { - // Recalculate direction based on closest point position - direction.x = physicBodies[i]->transform.position.x - closestPoint.x; - direction.y = physicBodies[i]->transform.position.y - closestPoint.y; - float distance = Vector2Length(direction); - - // Calculate final contact normal - contactNormal.x = direction.x/distance; - contactNormal.y = -direction.y/distance; - - // Calculate penetration depth - penetrationDepth = physicBodies[k]->collider.radius - distance; - } - else - { - if (abs(direction.y) < abs(direction.x)) - { - // Calculate final contact normal - if (direction.y > 0.0f) - { - contactNormal = (Vector2){ 0.0f, -1.0f }; - penetrationDepth = fabs(physicBodies[k]->collider.bounds.y - physicBodies[i]->transform.position.y - physicBodies[i]->collider.radius); - } - else - { - contactNormal = (Vector2){ 0.0f, 1.0f }; - penetrationDepth = fabs(physicBodies[k]->collider.bounds.y - physicBodies[i]->transform.position.y + physicBodies[i]->collider.radius); - } - } - else - { - // Calculate final contact normal and penetration depth - if (direction.x > 0.0f) - { - contactNormal = (Vector2){ 1.0f, 0.0f }; - penetrationDepth = fabs(physicBodies[i]->transform.position.x + physicBodies[i]->collider.radius - physicBodies[k]->collider.bounds.x); - } - else - { - contactNormal = (Vector2){ -1.0f, 0.0f }; - penetrationDepth = fabs(physicBodies[k]->collider.bounds.x + physicBodies[k]->collider.bounds.width - physicBodies[i]->transform.position.x - physicBodies[i]->collider.radius); - } - } - } - } - } break; - case COLLIDER_CIRCLE: - { - // Check if colliders are overlapped - if (CheckCollisionCircles(physicBodies[i]->transform.position, physicBodies[i]->collider.radius, physicBodies[k]->transform.position, physicBodies[k]->collider.radius)) - { - // Calculate direction vector between circles - direction.x = physicBodies[k]->transform.position.x - physicBodies[i]->transform.position.x; - direction.y = physicBodies[k]->transform.position.y - physicBodies[i]->transform.position.y; - - // Calculate distance between circles - float distance = Vector2Length(direction); - - // Check if circles are not completely overlapped - if (distance != 0.0f) - { - // Calculate contact normal direction (Y axis needs to be flipped) - contactNormal.x = direction.x/distance; - contactNormal.y = -direction.y/distance; - } - else contactNormal = (Vector2){ 1.0f, 0.0f }; // Choose random (but consistent) values - } - } break; - default: break; - } - } break; - default: break; - } - - // Update rigidbody grounded state - if (physicBodies[i]->rigidbody.enabled) - { - if (contactNormal.y < 0.0f) physicBodies[i]->rigidbody.isGrounded = true; - } - - // 2. Calculate collision impulse - // ------------------------------------------------------------------------------------------------------------------------------------- - - // Calculate relative velocity - Vector2 relVelocity = { 0.0f, 0.0f }; - relVelocity.x = physicBodies[k]->rigidbody.velocity.x - physicBodies[i]->rigidbody.velocity.x; - relVelocity.y = physicBodies[k]->rigidbody.velocity.y - physicBodies[i]->rigidbody.velocity.y; - - // Calculate relative velocity in terms of the normal direction - float velAlongNormal = Vector2DotProduct(relVelocity, contactNormal); - - // Dot not resolve if velocities are separating - if (velAlongNormal <= 0.0f) - { - // Calculate minimum bounciness value from both objects - float e = fminf(physicBodies[i]->rigidbody.bounciness, physicBodies[k]->rigidbody.bounciness); - - // Calculate impulse scalar value - float j = -(1.0f + e)*velAlongNormal; - j /= 1.0f/physicBodies[i]->rigidbody.mass + 1.0f/physicBodies[k]->rigidbody.mass; - - // Calculate final impulse vector - Vector2 impulse = { j*contactNormal.x, j*contactNormal.y }; - - // Calculate collision mass ration - float massSum = physicBodies[i]->rigidbody.mass + physicBodies[k]->rigidbody.mass; - float ratio = 0.0f; - - // Apply impulse to current rigidbodies velocities if they are enabled - if (physicBodies[i]->rigidbody.enabled) - { - // Calculate inverted mass ration - ratio = physicBodies[i]->rigidbody.mass/massSum; - - // Apply impulse direction to velocity - physicBodies[i]->rigidbody.velocity.x -= impulse.x*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); - physicBodies[i]->rigidbody.velocity.y -= impulse.y*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); - } - - if (physicBodies[k]->rigidbody.enabled) - { - // Calculate inverted mass ration - ratio = physicBodies[k]->rigidbody.mass/massSum; - - // Apply impulse direction to velocity - physicBodies[k]->rigidbody.velocity.x += impulse.x*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); - physicBodies[k]->rigidbody.velocity.y += impulse.y*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); - } - - // 3. Correct colliders overlaping (transform position) - // --------------------------------------------------------------------------------------------------------------------------------- - - // Calculate transform position penetration correction - Vector2 posCorrection; - posCorrection.x = penetrationDepth/((1.0f/physicBodies[i]->rigidbody.mass) + (1.0f/physicBodies[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.x; - posCorrection.y = penetrationDepth/((1.0f/physicBodies[i]->rigidbody.mass) + (1.0f/physicBodies[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.y; - - // Fix transform positions - if (physicBodies[i]->rigidbody.enabled) - { - // Fix physic objects transform position - physicBodies[i]->transform.position.x -= 1.0f/physicBodies[i]->rigidbody.mass*posCorrection.x; - physicBodies[i]->transform.position.y += 1.0f/physicBodies[i]->rigidbody.mass*posCorrection.y; - - // Update collider bounds - physicBodies[i]->collider.bounds = TransformToRectangle(physicBodies[i]->transform); - - if (physicBodies[k]->rigidbody.enabled) - { - // Fix physic objects transform position - physicBodies[k]->transform.position.x += 1.0f/physicBodies[k]->rigidbody.mass*posCorrection.x; - physicBodies[k]->transform.position.y -= 1.0f/physicBodies[k]->rigidbody.mass*posCorrection.y; - - // Update collider bounds - physicBodies[k]->collider.bounds = TransformToRectangle(physicBodies[k]->transform); - } - } - } - } - } - } - } - } - } + #ifndef PHYSAC_NO_THREADS // NOTE: if defined, user will need to create a thread for PhysicsThread function manually + // Create physics thread + pthread_t tid; + pthread_create(&tid, NULL, &PhysicsThread, NULL); + #endif } // Unitialize all physic objects and empty the objects pool PHYSACDEF void ClosePhysics() { + // Exit physics thread loop + physicsThreadEnabled = false; + // Free all dynamic memory allocations for (int i = 0; i < physicBodiesCount; i++) PHYSAC_FREE(physicBodies[i]); @@ -716,9 +372,71 @@ PHYSACDEF Rectangle TransformToRectangle(Transform transform) return (Rectangle){transform.position.x, transform.position.y, transform.scale.x, transform.scale.y}; } +// Physics calculations thread function +PHYSACDEF void* PhysicsThread(void *arg) +{ + // Initialize thread loop state + physicsThreadEnabled = true; + + // Initialize hi-resolution timer + InitTimer(); + + // Physics update loop + while (physicsThreadEnabled) + { + currentTime = GetCurrentTime(); + double deltaTime = (double)(currentTime - previousTime); + previousTime = currentTime; + + // Delta time value needs to be inverse multiplied by physics time step value (1/target fps) + UpdatePhysics(deltaTime/PHYSICS_TIMESTEP); + } + + return NULL; +} + //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- +// Initialize hi-resolution timer +static void InitTimer(void) +{ +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + struct timespec now; + + if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success + { + baseTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; + } +#endif + + previousTime = GetCurrentTime(); // Get time as double +} + +// Time measure returned are microseconds +static double GetCurrentTime(void) +{ + double time; + +#if defined(PLATFORM_DESKTOP) + unsigned long long int clockFrequency, currentTime; + + QueryPerformanceFrequency(&clockFrequency); + QueryPerformanceCounter(¤tTime); + + time = (double)((double)currentTime/(double)clockFrequency); +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t temp = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec; + + time = (double)(temp - baseTime)*1e-9; +#endif + + return time; +} // Returns the dot product of two Vector2 static float Vector2DotProduct(Vector2 v1, Vector2 v2) @@ -739,4 +457,376 @@ static float Vector2Length(Vector2 v) return result; } +// Update physic objects, calculating physic behaviours and collisions detection +static void UpdatePhysics(double deltaTime) +{ + for (int i = 0; i < physicBodiesCount; i++) + { + if (physicBodies[i]->enabled) + { + // Update physic behaviour + if (physicBodies[i]->rigidbody.enabled) + { + // Apply friction to acceleration in X axis + if (physicBodies[i]->rigidbody.acceleration.x > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.x -= physicBodies[i]->rigidbody.friction*deltaTime; + else if (physicBodies[i]->rigidbody.acceleration.x < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.x += physicBodies[i]->rigidbody.friction*deltaTime; + else physicBodies[i]->rigidbody.acceleration.x = 0.0f; + + // Apply friction to acceleration in Y axis + if (physicBodies[i]->rigidbody.acceleration.y > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.y -= physicBodies[i]->rigidbody.friction*deltaTime; + else if (physicBodies[i]->rigidbody.acceleration.y < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.y += physicBodies[i]->rigidbody.friction*deltaTime; + else physicBodies[i]->rigidbody.acceleration.y = 0.0f; + + // Apply friction to velocity in X axis + if (physicBodies[i]->rigidbody.velocity.x > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.x -= physicBodies[i]->rigidbody.friction*deltaTime; + else if (physicBodies[i]->rigidbody.velocity.x < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.x += physicBodies[i]->rigidbody.friction*deltaTime; + else physicBodies[i]->rigidbody.velocity.x = 0.0f; + + // Apply friction to velocity in Y axis + if (physicBodies[i]->rigidbody.velocity.y > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.y -= physicBodies[i]->rigidbody.friction*deltaTime; + else if (physicBodies[i]->rigidbody.velocity.y < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.y += physicBodies[i]->rigidbody.friction*deltaTime; + else physicBodies[i]->rigidbody.velocity.y = 0.0f; + + // Apply gravity to velocity + if (physicBodies[i]->rigidbody.applyGravity) + { + physicBodies[i]->rigidbody.velocity.x += gravityForce.x*deltaTime; + physicBodies[i]->rigidbody.velocity.y += gravityForce.y*deltaTime; + } + + // Apply acceleration to velocity + physicBodies[i]->rigidbody.velocity.x += physicBodies[i]->rigidbody.acceleration.x*deltaTime; + physicBodies[i]->rigidbody.velocity.y += physicBodies[i]->rigidbody.acceleration.y*deltaTime; + + // Apply velocity to position + physicBodies[i]->transform.position.x += physicBodies[i]->rigidbody.velocity.x*deltaTime; + physicBodies[i]->transform.position.y -= physicBodies[i]->rigidbody.velocity.y*deltaTime; + } + + // Update collision detection + if (physicBodies[i]->collider.enabled) + { + // Update collider bounds + physicBodies[i]->collider.bounds = TransformToRectangle(physicBodies[i]->transform); + + // Check collision with other colliders + for (int k = 0; k < physicBodiesCount; k++) + { + if (physicBodies[k]->collider.enabled && i != k) + { + // Resolve physic collision + // NOTE: collision resolve is generic for all directions and conditions (no axis separated cases behaviours) + // and it is separated in rigidbody attributes resolve (velocity changes by impulse) and position correction (position overlap) + + // 1. Calculate collision normal + // ------------------------------------------------------------------------------------------------------------------------------------- + + // Define collision contact normal, direction and penetration depth + Vector2 contactNormal = { 0.0f, 0.0f }; + Vector2 direction = { 0.0f, 0.0f }; + float penetrationDepth = 0.0f; + + switch (physicBodies[i]->collider.type) + { + case COLLIDER_RECTANGLE: + { + switch (physicBodies[k]->collider.type) + { + case COLLIDER_RECTANGLE: + { + // Check if colliders are overlapped + if (CheckCollisionRecs(physicBodies[i]->collider.bounds, physicBodies[k]->collider.bounds)) + { + // Calculate direction vector from i to k + direction.x = (physicBodies[k]->transform.position.x + physicBodies[k]->transform.scale.x/2) - (physicBodies[i]->transform.position.x + physicBodies[i]->transform.scale.x/2); + direction.y = (physicBodies[k]->transform.position.y + physicBodies[k]->transform.scale.y/2) - (physicBodies[i]->transform.position.y + physicBodies[i]->transform.scale.y/2); + + // Define overlapping and penetration attributes + Vector2 overlap; + + // Calculate overlap on X axis + overlap.x = (physicBodies[i]->transform.scale.x + physicBodies[k]->transform.scale.x)/2 - abs(direction.x); + + // SAT test on X axis + if (overlap.x > 0.0f) + { + // Calculate overlap on Y axis + overlap.y = (physicBodies[i]->transform.scale.y + physicBodies[k]->transform.scale.y)/2 - abs(direction.y); + + // SAT test on Y axis + if (overlap.y > 0.0f) + { + // Find out which axis is axis of least penetration + if (overlap.y > overlap.x) + { + // Point towards k knowing that direction points from i to k + if (direction.x < 0.0f) contactNormal = (Vector2){ -1.0f, 0.0f }; + else contactNormal = (Vector2){ 1.0f, 0.0f }; + + // Update penetration depth for position correction + penetrationDepth = overlap.x; + } + else + { + // Point towards k knowing that direction points from i to k + if (direction.y < 0.0f) contactNormal = (Vector2){ 0.0f, 1.0f }; + else contactNormal = (Vector2){ 0.0f, -1.0f }; + + // Update penetration depth for position correction + penetrationDepth = overlap.y; + } + } + } + } + } break; + case COLLIDER_CIRCLE: + { + if (CheckCollisionCircleRec(physicBodies[k]->transform.position, physicBodies[k]->collider.radius, physicBodies[i]->collider.bounds)) + { + // Calculate direction vector between circles + direction.x = physicBodies[k]->transform.position.x - physicBodies[i]->transform.position.x + physicBodies[i]->transform.scale.x/2; + direction.y = physicBodies[k]->transform.position.y - physicBodies[i]->transform.position.y + physicBodies[i]->transform.scale.y/2; + + // Calculate closest point on rectangle to circle + Vector2 closestPoint = { 0.0f, 0.0f }; + if (direction.x > 0.0f) closestPoint.x = physicBodies[i]->collider.bounds.x + physicBodies[i]->collider.bounds.width; + else closestPoint.x = physicBodies[i]->collider.bounds.x; + + if (direction.y > 0.0f) closestPoint.y = physicBodies[i]->collider.bounds.y + physicBodies[i]->collider.bounds.height; + else closestPoint.y = physicBodies[i]->collider.bounds.y; + + // Check if the closest point is inside the circle + if (CheckCollisionPointCircle(closestPoint, physicBodies[k]->transform.position, physicBodies[k]->collider.radius)) + { + // Recalculate direction based on closest point position + direction.x = physicBodies[k]->transform.position.x - closestPoint.x; + direction.y = physicBodies[k]->transform.position.y - closestPoint.y; + float distance = Vector2Length(direction); + + // Calculate final contact normal + contactNormal.x = direction.x/distance; + contactNormal.y = -direction.y/distance; + + // Calculate penetration depth + penetrationDepth = physicBodies[k]->collider.radius - distance; + } + else + { + if (abs(direction.y) < abs(direction.x)) + { + // Calculate final contact normal + if (direction.y > 0.0f) + { + contactNormal = (Vector2){ 0.0f, -1.0f }; + penetrationDepth = fabs(physicBodies[i]->collider.bounds.y - physicBodies[k]->transform.position.y - physicBodies[k]->collider.radius); + } + else + { + contactNormal = (Vector2){ 0.0f, 1.0f }; + penetrationDepth = fabs(physicBodies[i]->collider.bounds.y - physicBodies[k]->transform.position.y + physicBodies[k]->collider.radius); + } + } + else + { + // Calculate final contact normal + if (direction.x > 0.0f) + { + contactNormal = (Vector2){ 1.0f, 0.0f }; + penetrationDepth = fabs(physicBodies[k]->transform.position.x + physicBodies[k]->collider.radius - physicBodies[i]->collider.bounds.x); + } + else + { + contactNormal = (Vector2){ -1.0f, 0.0f }; + penetrationDepth = fabs(physicBodies[i]->collider.bounds.x + physicBodies[i]->collider.bounds.width - physicBodies[k]->transform.position.x - physicBodies[k]->collider.radius); + } + } + } + } + } break; + } + } break; + case COLLIDER_CIRCLE: + { + switch (physicBodies[k]->collider.type) + { + case COLLIDER_RECTANGLE: + { + if (CheckCollisionCircleRec(physicBodies[i]->transform.position, physicBodies[i]->collider.radius, physicBodies[k]->collider.bounds)) + { + // Calculate direction vector between circles + direction.x = physicBodies[k]->transform.position.x + physicBodies[i]->transform.scale.x/2 - physicBodies[i]->transform.position.x; + direction.y = physicBodies[k]->transform.position.y + physicBodies[i]->transform.scale.y/2 - physicBodies[i]->transform.position.y; + + // Calculate closest point on rectangle to circle + Vector2 closestPoint = { 0.0f, 0.0f }; + if (direction.x > 0.0f) closestPoint.x = physicBodies[k]->collider.bounds.x + physicBodies[k]->collider.bounds.width; + else closestPoint.x = physicBodies[k]->collider.bounds.x; + + if (direction.y > 0.0f) closestPoint.y = physicBodies[k]->collider.bounds.y + physicBodies[k]->collider.bounds.height; + else closestPoint.y = physicBodies[k]->collider.bounds.y; + + // Check if the closest point is inside the circle + if (CheckCollisionPointCircle(closestPoint, physicBodies[i]->transform.position, physicBodies[i]->collider.radius)) + { + // Recalculate direction based on closest point position + direction.x = physicBodies[i]->transform.position.x - closestPoint.x; + direction.y = physicBodies[i]->transform.position.y - closestPoint.y; + float distance = Vector2Length(direction); + + // Calculate final contact normal + contactNormal.x = direction.x/distance; + contactNormal.y = -direction.y/distance; + + // Calculate penetration depth + penetrationDepth = physicBodies[k]->collider.radius - distance; + } + else + { + if (abs(direction.y) < abs(direction.x)) + { + // Calculate final contact normal + if (direction.y > 0.0f) + { + contactNormal = (Vector2){ 0.0f, -1.0f }; + penetrationDepth = fabs(physicBodies[k]->collider.bounds.y - physicBodies[i]->transform.position.y - physicBodies[i]->collider.radius); + } + else + { + contactNormal = (Vector2){ 0.0f, 1.0f }; + penetrationDepth = fabs(physicBodies[k]->collider.bounds.y - physicBodies[i]->transform.position.y + physicBodies[i]->collider.radius); + } + } + else + { + // Calculate final contact normal and penetration depth + if (direction.x > 0.0f) + { + contactNormal = (Vector2){ 1.0f, 0.0f }; + penetrationDepth = fabs(physicBodies[i]->transform.position.x + physicBodies[i]->collider.radius - physicBodies[k]->collider.bounds.x); + } + else + { + contactNormal = (Vector2){ -1.0f, 0.0f }; + penetrationDepth = fabs(physicBodies[k]->collider.bounds.x + physicBodies[k]->collider.bounds.width - physicBodies[i]->transform.position.x - physicBodies[i]->collider.radius); + } + } + } + } + } break; + case COLLIDER_CIRCLE: + { + // Check if colliders are overlapped + if (CheckCollisionCircles(physicBodies[i]->transform.position, physicBodies[i]->collider.radius, physicBodies[k]->transform.position, physicBodies[k]->collider.radius)) + { + // Calculate direction vector between circles + direction.x = physicBodies[k]->transform.position.x - physicBodies[i]->transform.position.x; + direction.y = physicBodies[k]->transform.position.y - physicBodies[i]->transform.position.y; + + // Calculate distance between circles + float distance = Vector2Length(direction); + + // Check if circles are not completely overlapped + if (distance != 0.0f) + { + // Calculate contact normal direction (Y axis needs to be flipped) + contactNormal.x = direction.x/distance; + contactNormal.y = -direction.y/distance; + } + else contactNormal = (Vector2){ 1.0f, 0.0f }; // Choose random (but consistent) values + } + } break; + default: break; + } + } break; + default: break; + } + + // Update rigidbody grounded state + if (physicBodies[i]->rigidbody.enabled) physicBodies[i]->rigidbody.isGrounded = (contactNormal.y < 0.0f); + + // 2. Calculate collision impulse + // ------------------------------------------------------------------------------------------------------------------------------------- + + // Calculate relative velocity + Vector2 relVelocity = { 0.0f, 0.0f }; + relVelocity.x = physicBodies[k]->rigidbody.velocity.x - physicBodies[i]->rigidbody.velocity.x; + relVelocity.y = physicBodies[k]->rigidbody.velocity.y - physicBodies[i]->rigidbody.velocity.y; + + // Calculate relative velocity in terms of the normal direction + float velAlongNormal = Vector2DotProduct(relVelocity, contactNormal); + + // Dot not resolve if velocities are separating + if (velAlongNormal <= 0.0f) + { + // Calculate minimum bounciness value from both objects + float e = fminf(physicBodies[i]->rigidbody.bounciness, physicBodies[k]->rigidbody.bounciness); + + // Calculate impulse scalar value + float j = -(1.0f + e)*velAlongNormal; + j /= 1.0f/physicBodies[i]->rigidbody.mass + 1.0f/physicBodies[k]->rigidbody.mass; + + // Calculate final impulse vector + Vector2 impulse = { j*contactNormal.x, j*contactNormal.y }; + + // Calculate collision mass ration + float massSum = physicBodies[i]->rigidbody.mass + physicBodies[k]->rigidbody.mass; + float ratio = 0.0f; + + // Apply impulse to current rigidbodies velocities if they are enabled + if (physicBodies[i]->rigidbody.enabled) + { + // Calculate inverted mass ration + ratio = physicBodies[i]->rigidbody.mass/massSum; + + // Apply impulse direction to velocity + physicBodies[i]->rigidbody.velocity.x -= impulse.x*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); + physicBodies[i]->rigidbody.velocity.y -= impulse.y*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); + } + + if (physicBodies[k]->rigidbody.enabled) + { + // Calculate inverted mass ration + ratio = physicBodies[k]->rigidbody.mass/massSum; + + // Apply impulse direction to velocity + physicBodies[k]->rigidbody.velocity.x += impulse.x*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); + physicBodies[k]->rigidbody.velocity.y += impulse.y*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); + } + + // 3. Correct colliders overlaping (transform position) + // --------------------------------------------------------------------------------------------------------------------------------- + + // Calculate transform position penetration correction + Vector2 posCorrection; + posCorrection.x = penetrationDepth/((1.0f/physicBodies[i]->rigidbody.mass) + (1.0f/physicBodies[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.x; + posCorrection.y = penetrationDepth/((1.0f/physicBodies[i]->rigidbody.mass) + (1.0f/physicBodies[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.y; + + // Fix transform positions + if (physicBodies[i]->rigidbody.enabled) + { + // Fix physic objects transform position + physicBodies[i]->transform.position.x -= 1.0f/physicBodies[i]->rigidbody.mass*posCorrection.x; + physicBodies[i]->transform.position.y += 1.0f/physicBodies[i]->rigidbody.mass*posCorrection.y; + + // Update collider bounds + physicBodies[i]->collider.bounds = TransformToRectangle(physicBodies[i]->transform); + + if (physicBodies[k]->rigidbody.enabled) + { + // Fix physic objects transform position + physicBodies[k]->transform.position.x += 1.0f/physicBodies[k]->rigidbody.mass*posCorrection.x; + physicBodies[k]->transform.position.y -= 1.0f/physicBodies[k]->rigidbody.mass*posCorrection.y; + + // Update collider bounds + physicBodies[k]->collider.bounds = TransformToRectangle(physicBodies[k]->transform); + } + } + } + } + } + } + } + } +} + #endif // PHYSAC_IMPLEMENTATION \ No newline at end of file