From 305efcf5ad715305ccb33b0e1d2d9baf4a34976a Mon Sep 17 00:00:00 2001 From: victorfisac Date: Sat, 5 Mar 2016 17:05:02 +0100 Subject: Redesigned physics module (IN PROGRESS) physac modules is being redesigned. Physics base behaviour is done and it is composed by three steps: apply physics, resolve collisions and fix overlapping. A basic example is currently in progress. The next steps are try to add torque and unoriented physic collisions and implement physics basic functions to add forces. Rigidbody grounding state is automatically calculated and has a perfect result. Rigidbodies interacts well with each others. To achieve physics accuracy, UpdatePhysics() is called a number of times per frame. In a future, it should be changed to another thread and call it without any target frame restriction. Basic physics example has been redone (not finished) using the new module functions. Forces examples will be redone so I removed it from branch. --- src/physac.h | 58 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 28 deletions(-) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index 9e1b0b88..b948e4ce 100644 --- a/src/physac.h +++ b/src/physac.h @@ -1,6 +1,6 @@ /********************************************************************************************** * -* [physac] raylib physics engine module - Basic functions to apply physics to 2D objects +* [physac] raylib physics module - Basic functions to apply physics to 2D objects * * Copyright (c) 2015 Victor Fisac and Ramon Santamaria * @@ -31,62 +31,64 @@ //---------------------------------------------------------------------------------- // Types and Structures Definition +// NOTE: Below types are required for PHYSAC_STANDALONE usage //---------------------------------------------------------------------------------- -// Collider types + +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; + typedef enum { COLLIDER_CIRCLE, COLLIDER_RECTANGLE, COLLIDER_CAPSULE } ColliderType; -// Transform struct typedef struct Transform { Vector2 position; float rotation; Vector2 scale; } Transform; -// Rigidbody struct typedef struct Rigidbody { - bool enabled; + bool enabled; // Acts as kinematic state (collisions are calculated anyway) float mass; Vector2 acceleration; Vector2 velocity; - bool isGrounded; - bool isContact; // Avoid freeze player when touching floor bool applyGravity; - float friction; // 0.0f to 1.0f - float bounciness; // 0.0f to 1.0f + bool isGrounded; + float friction; // Normalized value + float bounciness; // Normalized value } Rigidbody; -// Collider struct typedef struct Collider { bool enabled; ColliderType type; - Rectangle bounds; // Used for COLLIDER_RECTANGLE and COLLIDER_CAPSULE - int radius; // Used for COLLIDER_CIRCLE and COLLIDER_CAPSULE + Rectangle bounds; // Used for COLLIDER_RECTANGLE and COLLIDER_CAPSULE + int radius; // Used for COLLIDER_CIRCLE and COLLIDER_CAPSULE } Collider; +typedef struct PhysicObject { + unsigned int id; + Transform transform; + Rigidbody rigidbody; + Collider collider; + bool enabled; +} PhysicObject; + #ifdef __cplusplus extern "C" { // Prevents name mangling of functions #endif //---------------------------------------------------------------------------------- -// Module Functions Declarations +// Module Functions Declaration //---------------------------------------------------------------------------------- -void InitPhysics(int maxPhysicElements); // Initialize all internal physics values -void UnloadPhysics(); // Unload physic elements arrays - -void AddRigidbody(int index, Rigidbody rigidbody); // Initialize a new rigidbody with parameters to internal index slot -void AddCollider(int index, Collider collider); // Initialize a new Collider with parameters to internal index slot - -void ApplyPhysics(int index, Vector2 *position); // Apply physics to internal rigidbody, physics calculations are applied to position pointer parameter -void SetRigidbodyEnabled(int index, bool state); // Set enabled state to a defined rigidbody -void SetRigidbodyVelocity(int index, Vector2 velocity); // Set velocity of rigidbody (without considering of mass value) -void SetRigidbodyAcceleration(int index, Vector2 acceleration); // Set acceleration of rigidbody (without considering of mass value) -void AddRigidbodyForce(int index, Vector2 force); // Set rigidbody force (considering mass value) -void AddForceAtPosition(Vector2 position, float intensity, float radius); // Add a force to all enabled rigidbodies at a position +void UpdatePhysics(); // Update physic objects, calculating physic behaviours and collisions detection +void ClosePhysics(); // Unitialize all physic objects and empty the objects pool -void SetColliderEnabled(int index, bool state); // Set enabled state to a defined collider +PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale); // Create a new physic object dinamically, initialize it and add to pool +void DestroyPhysicObject(PhysicObject *pObj); // Destroy a specific physic object and take it out of the list -Rigidbody GetRigidbody(int index); // Returns the internal rigidbody data defined by index parameter -Collider GetCollider(int index); // Returns the internal collider data defined by index parameter +Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale) +void DrawPhysicObjectInfo(PhysicObject *pObj, Vector2 position, int fontSize); // Draw physic object information at screen position #ifdef __cplusplus } -- cgit v1.2.3 From 78e4772f21cda45c219ce88a713708b6b0680e8f Mon Sep 17 00:00:00 2001 From: victorfisac Date: Sat, 5 Mar 2016 19:36:40 +0100 Subject: Fixed physac header little mistake --- src/physac.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index b948e4ce..fc5502c4 100644 --- a/src/physac.h +++ b/src/physac.h @@ -81,6 +81,7 @@ extern "C" { // Prevents name mangling of functions //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- +void InitPhysics(); // Initializes pointers array (just pointers, fixed size) void UpdatePhysics(); // Update physic objects, calculating physic behaviours and collisions detection void ClosePhysics(); // Unitialize all physic objects and empty the objects pool -- cgit v1.2.3 From 7128ef686d9c09d0ec3b413c07bb1cc24f03d219 Mon Sep 17 00:00:00 2001 From: victorfisac Date: Wed, 16 Mar 2016 12:45:01 +0100 Subject: physac module redesign (2/3) physac module base almost finished. All collisions are now resolved properly and some force functions was added. COLLIDER_CAPSULE removed for now because in 2D everything is composed by rectangle and circle colliders... The last step is move physics update loop into another thread and update it in a fixed time step based on fps. --- src/physac.c | 494 +++++++++++++++++++++++++++++++++++++++++++---------------- src/physac.h | 9 +- 2 files changed, 371 insertions(+), 132 deletions(-) (limited to 'src/physac.h') diff --git a/src/physac.c b/src/physac.c index 13247117..718a06bb 100644 --- a/src/physac.c +++ b/src/physac.c @@ -36,10 +36,9 @@ // Defines and Macros //---------------------------------------------------------------------------------- #define MAX_PHYSIC_OBJECTS 256 -#define PHYSICS_GRAVITY -9.81f/2 #define PHYSICS_STEPS 450 -#define PHYSICS_ACCURACY 0.0001f // Velocity subtract operations round filter (friction) -#define PHYSICS_ERRORPERCENT 0.001f // Collision resolve position fix +#define PHYSICS_ACCURACY 0.0001f // Velocity subtract operations round filter (friction) +#define PHYSICS_ERRORPERCENT 0.001f // Collision resolve position fix //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -52,53 +51,70 @@ //---------------------------------------------------------------------------------- static PhysicObject *physicObjects[MAX_PHYSIC_OBJECTS]; // Physic objects pool static int physicObjectsCount; // Counts current enabled physic objects +static Vector2 gravityForce; // Gravity force //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- 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 //---------------------------------------------------------------------------------- // Module Functions Definition //---------------------------------------------------------------------------------- // Initializes pointers array (just pointers, fixed size) -void InitPhysics() +void InitPhysics(Vector2 gravity) { // Initialize physics variables physicObjectsCount = 0; + gravityForce = gravity; } // Update physic objects, calculating physic behaviours and collisions detection void UpdatePhysics() { // Reset all physic objects is grounded state - for(int i = 0; i < physicObjectsCount; i++) + for (int i = 0; i < physicObjectsCount; i++) { - if(physicObjects[i]->rigidbody.enabled) physicObjects[i]->rigidbody.isGrounded = false; + if (physicObjects[i]->rigidbody.enabled) physicObjects[i]->rigidbody.isGrounded = false; } - for(int steps = 0; steps < PHYSICS_STEPS; steps++) + for (int steps = 0; steps < PHYSICS_STEPS; steps++) { - for(int i = 0; i < physicObjectsCount; i++) + for (int i = 0; i < physicObjectsCount; i++) { - if(physicObjects[i]->enabled) + if (physicObjects[i]->enabled) { // Update physic behaviour - if(physicObjects[i]->rigidbody.enabled) + if (physicObjects[i]->rigidbody.enabled) { // Apply friction to acceleration in X axis if (physicObjects[i]->rigidbody.acceleration.x > PHYSICS_ACCURACY) physicObjects[i]->rigidbody.acceleration.x -= physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; else if (physicObjects[i]->rigidbody.acceleration.x < PHYSICS_ACCURACY) physicObjects[i]->rigidbody.acceleration.x += physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; else physicObjects[i]->rigidbody.acceleration.x = 0.0f; + // Apply friction to acceleration in Y axis + if (physicObjects[i]->rigidbody.acceleration.y > PHYSICS_ACCURACY) physicObjects[i]->rigidbody.acceleration.y -= physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; + else if (physicObjects[i]->rigidbody.acceleration.y < PHYSICS_ACCURACY) physicObjects[i]->rigidbody.acceleration.y += physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; + else physicObjects[i]->rigidbody.acceleration.y = 0.0f; + // Apply friction to velocity in X axis if (physicObjects[i]->rigidbody.velocity.x > PHYSICS_ACCURACY) physicObjects[i]->rigidbody.velocity.x -= physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; else if (physicObjects[i]->rigidbody.velocity.x < PHYSICS_ACCURACY) physicObjects[i]->rigidbody.velocity.x += physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; else physicObjects[i]->rigidbody.velocity.x = 0.0f; + // Apply friction to velocity in Y axis + if (physicObjects[i]->rigidbody.velocity.y > PHYSICS_ACCURACY) physicObjects[i]->rigidbody.velocity.y -= physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; + else if (physicObjects[i]->rigidbody.velocity.y < PHYSICS_ACCURACY) physicObjects[i]->rigidbody.velocity.y += physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; + else physicObjects[i]->rigidbody.velocity.y = 0.0f; + // Apply gravity to velocity - if (physicObjects[i]->rigidbody.applyGravity) physicObjects[i]->rigidbody.velocity.y += PHYSICS_GRAVITY/PHYSICS_STEPS; + if (physicObjects[i]->rigidbody.applyGravity) + { + physicObjects[i]->rigidbody.velocity.x += gravityForce.x/PHYSICS_STEPS; + physicObjects[i]->rigidbody.velocity.y += gravityForce.y/PHYSICS_STEPS; + } // Apply acceleration to velocity physicObjects[i]->rigidbody.velocity.x += physicObjects[i]->rigidbody.acceleration.x/PHYSICS_STEPS; @@ -120,142 +136,314 @@ void UpdatePhysics() { if (physicObjects[k]->collider.enabled && i != k) { - // Check if colliders are overlapped - if (CheckCollisionRecs(physicObjects[i]->collider.bounds, physicObjects[k]->collider.bounds)) + // 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(physicObjects[i]->collider.type) { - // 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 ontact normal - Vector2 contactNormal = { 0.0f, 0.0f }; + case COLLIDER_RECTANGLE: + { + switch(physicObjects[k]->collider.type) + { + case COLLIDER_RECTANGLE: + { + // Check if colliders are overlapped + if (CheckCollisionRecs(physicObjects[i]->collider.bounds, physicObjects[k]->collider.bounds)) + { + // Calculate direction vector from i to k + direction.x = (physicObjects[k]->transform.position.x + physicObjects[k]->transform.scale.x/2) - (physicObjects[i]->transform.position.x + physicObjects[i]->transform.scale.x/2); + direction.y = (physicObjects[k]->transform.position.y + physicObjects[k]->transform.scale.y/2) - (physicObjects[i]->transform.position.y + physicObjects[i]->transform.scale.y/2); + + // Define overlapping and penetration attributes + Vector2 overlap; + + // Calculate overlap on X axis + overlap.x = (physicObjects[i]->transform.scale.x + physicObjects[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 = (physicObjects[i]->transform.scale.y + physicObjects[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(physicObjects[k]->transform.position, physicObjects[k]->collider.radius, physicObjects[i]->collider.bounds)) + { + // Calculate direction vector between circles + direction.x = physicObjects[k]->transform.position.x - physicObjects[i]->transform.position.x + physicObjects[i]->transform.scale.x/2; + direction.y = physicObjects[k]->transform.position.y - physicObjects[i]->transform.position.y + physicObjects[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 = physicObjects[i]->collider.bounds.x + physicObjects[i]->collider.bounds.width; + else closestPoint.x = physicObjects[i]->collider.bounds.x; + + if (direction.y > 0.0f) closestPoint.y = physicObjects[i]->collider.bounds.y + physicObjects[i]->collider.bounds.height; + else closestPoint.y = physicObjects[i]->collider.bounds.y; + + // Check if the closest point is inside the circle + if (CheckCollisionPointCircle(closestPoint, physicObjects[k]->transform.position, physicObjects[k]->collider.radius)) + { + // Recalculate direction based on closest point position + direction.x = physicObjects[k]->transform.position.x - closestPoint.x; + direction.y = physicObjects[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 = physicObjects[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(physicObjects[i]->collider.bounds.y - physicObjects[k]->transform.position.y - physicObjects[k]->collider.radius); + } + else + { + contactNormal = (Vector2){ 0.0f, 1.0f }; + penetrationDepth = fabs(physicObjects[i]->collider.bounds.y - physicObjects[k]->transform.position.y + physicObjects[k]->collider.radius); + } + } + else + { + // Calculate final contact normal + if (direction.x > 0.0f) + { + contactNormal = (Vector2){ 1.0f, 0.0f }; + penetrationDepth = fabs(physicObjects[k]->transform.position.x + physicObjects[k]->collider.radius - physicObjects[i]->collider.bounds.x); + } + else + { + contactNormal = (Vector2){ -1.0f, 0.0f }; + penetrationDepth = fabs(physicObjects[i]->collider.bounds.x + physicObjects[i]->collider.bounds.width - physicObjects[k]->transform.position.x - physicObjects[k]->collider.radius); + } + } + } + } + } break; + } + } break; + case COLLIDER_CIRCLE: + { + switch(physicObjects[k]->collider.type) + { + case COLLIDER_RECTANGLE: + { + if (CheckCollisionCircleRec(physicObjects[i]->transform.position, physicObjects[i]->collider.radius, physicObjects[k]->collider.bounds)) + { + // Calculate direction vector between circles + direction.x = physicObjects[k]->transform.position.x + physicObjects[i]->transform.scale.x/2 - physicObjects[i]->transform.position.x; + direction.y = physicObjects[k]->transform.position.y + physicObjects[i]->transform.scale.y/2 - physicObjects[i]->transform.position.y; + + // Calculate closest point on rectangle to circle + Vector2 closestPoint = { 0.0f, 0.0f }; + if (direction.x > 0.0f) closestPoint.x = physicObjects[k]->collider.bounds.x + physicObjects[k]->collider.bounds.width; + else closestPoint.x = physicObjects[k]->collider.bounds.x; + + if (direction.y > 0.0f) closestPoint.y = physicObjects[k]->collider.bounds.y + physicObjects[k]->collider.bounds.height; + else closestPoint.y = physicObjects[k]->collider.bounds.y; + + // Check if the closest point is inside the circle + if (CheckCollisionPointCircle(closestPoint, physicObjects[i]->transform.position, physicObjects[i]->collider.radius)) + { + // Recalculate direction based on closest point position + direction.x = physicObjects[i]->transform.position.x - closestPoint.x; + direction.y = physicObjects[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 = physicObjects[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(physicObjects[k]->collider.bounds.y - physicObjects[i]->transform.position.y - physicObjects[i]->collider.radius); + } + else + { + contactNormal = (Vector2){ 0.0f, 1.0f }; + penetrationDepth = fabs(physicObjects[k]->collider.bounds.y - physicObjects[i]->transform.position.y + physicObjects[i]->collider.radius); + } + } + else + { + // Calculate final contact normal and penetration depth + if (direction.x > 0.0f) + { + contactNormal = (Vector2){ 1.0f, 0.0f }; + penetrationDepth = fabs(physicObjects[i]->transform.position.x + physicObjects[i]->collider.radius - physicObjects[k]->collider.bounds.x); + } + else + { + contactNormal = (Vector2){ -1.0f, 0.0f }; + penetrationDepth = fabs(physicObjects[k]->collider.bounds.x + physicObjects[k]->collider.bounds.width - physicObjects[i]->transform.position.x - physicObjects[i]->collider.radius); + } + } + } + } + } break; + case COLLIDER_CIRCLE: + { + // Check if colliders are overlapped + if (CheckCollisionCircles(physicObjects[i]->transform.position, physicObjects[i]->collider.radius, physicObjects[k]->transform.position, physicObjects[k]->collider.radius)) + { + // Calculate direction vector between circles + direction.x = physicObjects[k]->transform.position.x - physicObjects[i]->transform.position.x; + direction.y = physicObjects[k]->transform.position.y - physicObjects[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 (physicObjects[i]->rigidbody.enabled) + { + if (contactNormal.y < 0.0f) physicObjects[i]->rigidbody.isGrounded = true; + } + + // 2. Calculate collision impulse + // ------------------------------------------------------------------------------------------------------------------------------------- + + // Calculate relative velocity + Vector2 relVelocity = { 0.0f, 0.0f }; + relVelocity.x = physicObjects[k]->rigidbody.velocity.x - physicObjects[i]->rigidbody.velocity.x; + relVelocity.y = physicObjects[k]->rigidbody.velocity.y - physicObjects[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(physicObjects[i]->rigidbody.bounciness, physicObjects[k]->rigidbody.bounciness); - // Calculate direction vector from i to k - Vector2 direction; - direction.x = (physicObjects[k]->transform.position.x + physicObjects[k]->transform.scale.x/2) - (physicObjects[i]->transform.position.x + physicObjects[i]->transform.scale.x/2); - direction.y = (physicObjects[k]->transform.position.y + physicObjects[k]->transform.scale.y/2) - (physicObjects[i]->transform.position.y + physicObjects[i]->transform.scale.y/2); + // Calculate impulse scalar value + float j = -(1.0f + e)*velAlongNormal; + j /= 1.0f/physicObjects[i]->rigidbody.mass + 1.0f/physicObjects[k]->rigidbody.mass; - // Define overlapping and penetration attributes - Vector2 overlap; - float penetrationDepth = 0.0f; + // Calculate final impulse vector + Vector2 impulse = { j*contactNormal.x, j*contactNormal.y }; - // Calculate overlap on X axis - overlap.x = (physicObjects[i]->transform.scale.x + physicObjects[k]->transform.scale.x)/2 - abs(direction.x); + // Calculate collision mass ration + float massSum = physicObjects[i]->rigidbody.mass + physicObjects[k]->rigidbody.mass; + float ratio = 0.0f; - // SAT test on X axis - if (overlap.x > 0.0f) + // Apply impulse to current rigidbodies velocities if they are enabled + if (physicObjects[i]->rigidbody.enabled) { - // Calculate overlap on Y axis - overlap.y = (physicObjects[i]->transform.scale.y + physicObjects[k]->transform.scale.y)/2 - abs(direction.y); + // Calculate inverted mass ration + ratio = physicObjects[i]->rigidbody.mass/massSum; - // 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; - } - } + // Apply impulse direction to velocity + physicObjects[i]->rigidbody.velocity.x -= impulse.x*ratio*(1.0f+physicObjects[i]->rigidbody.bounciness); + physicObjects[i]->rigidbody.velocity.y -= impulse.y*ratio*(1.0f+physicObjects[i]->rigidbody.bounciness); } - // Update rigidbody grounded state - if (physicObjects[i]->rigidbody.enabled) + if (physicObjects[k]->rigidbody.enabled) { - if (contactNormal.y < 0.0f) physicObjects[i]->rigidbody.isGrounded = true; + // Calculate inverted mass ration + ratio = physicObjects[k]->rigidbody.mass/massSum; + + // Apply impulse direction to velocity + physicObjects[k]->rigidbody.velocity.x += impulse.x*ratio*(1.0f+physicObjects[i]->rigidbody.bounciness); + physicObjects[k]->rigidbody.velocity.y += impulse.y*ratio*(1.0f+physicObjects[i]->rigidbody.bounciness); } - // 2. Calculate collision impulse - // ------------------------------------------------------------------------------------------------------------------------------------- + // 3. Correct colliders overlaping (transform position) + // --------------------------------------------------------------------------------------------------------------------------------- - // Calculate relative velocity - Vector2 relVelocity = { physicObjects[k]->rigidbody.velocity.x - physicObjects[i]->rigidbody.velocity.x, physicObjects[k]->rigidbody.velocity.y - physicObjects[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(physicObjects[i]->rigidbody.bounciness, physicObjects[k]->rigidbody.bounciness); - - // Calculate impulse scalar value - float j = -(1.0f + e) * velAlongNormal; - j /= 1.0f/physicObjects[i]->rigidbody.mass + 1.0f/physicObjects[k]->rigidbody.mass; - - // Calculate final impulse vector - Vector2 impulse = { j*contactNormal.x, j*contactNormal.y }; - - // Calculate collision mass ration - float massSum = physicObjects[i]->rigidbody.mass + physicObjects[k]->rigidbody.mass; - float ratio = 0.0f; + // Calculate transform position penetration correction + Vector2 posCorrection; + posCorrection.x = penetrationDepth/((1.0f/physicObjects[i]->rigidbody.mass) + (1.0f/physicObjects[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.x; + posCorrection.y = penetrationDepth/((1.0f/physicObjects[i]->rigidbody.mass) + (1.0f/physicObjects[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.y; + + // Fix transform positions + if (physicObjects[i]->rigidbody.enabled) + { + // Fix physic objects transform position + physicObjects[i]->transform.position.x -= 1.0f/physicObjects[i]->rigidbody.mass*posCorrection.x; + physicObjects[i]->transform.position.y += 1.0f/physicObjects[i]->rigidbody.mass*posCorrection.y; - // Apply impulse to current rigidbodies velocities if they are enabled - if (physicObjects[i]->rigidbody.enabled) - { - // Calculate inverted mass ration - ratio = physicObjects[i]->rigidbody.mass/massSum; - - // Apply impulse direction to velocity - physicObjects[i]->rigidbody.velocity.x -= impulse.x*ratio; - physicObjects[i]->rigidbody.velocity.y -= impulse.y*ratio; - } + // Update collider bounds + physicObjects[i]->collider.bounds = TransformToRectangle(physicObjects[i]->transform); - if (physicObjects[k]->rigidbody.enabled) + if (physicObjects[k]->rigidbody.enabled) { - // Calculate inverted mass ration - ratio = physicObjects[k]->rigidbody.mass/massSum; - - // Apply impulse direction to velocity - physicObjects[k]->rigidbody.velocity.x += impulse.x*ratio; - physicObjects[k]->rigidbody.velocity.y += impulse.y*ratio; - } - - // 3. Correct colliders overlaping (transform position) - // --------------------------------------------------------------------------------------------------------------------------------- - - // Calculate transform position penetration correction - Vector2 posCorrection; - posCorrection.x = penetrationDepth/((1.0f/physicObjects[i]->rigidbody.mass) + (1.0f/physicObjects[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.x; - posCorrection.y = penetrationDepth/((1.0f/physicObjects[i]->rigidbody.mass) + (1.0f/physicObjects[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.y; - - // Fix transform positions - if (physicObjects[i]->rigidbody.enabled) - { // Fix physic objects transform position - physicObjects[i]->transform.position.x -= 1.0f/physicObjects[i]->rigidbody.mass*posCorrection.x; - physicObjects[i]->transform.position.y += 1.0f/physicObjects[i]->rigidbody.mass*posCorrection.y; + physicObjects[k]->transform.position.x += 1.0f/physicObjects[k]->rigidbody.mass*posCorrection.x; + physicObjects[k]->transform.position.y -= 1.0f/physicObjects[k]->rigidbody.mass*posCorrection.y; // Update collider bounds - physicObjects[i]->collider.bounds = TransformToRectangle(physicObjects[i]->transform); - - if (physicObjects[k]->rigidbody.enabled) - { - // Fix physic objects transform position - physicObjects[k]->transform.position.x += 1.0f/physicObjects[k]->rigidbody.mass*posCorrection.x; - physicObjects[k]->transform.position.y -= 1.0f/physicObjects[k]->rigidbody.mass*posCorrection.y; - - // Update collider bounds - physicObjects[k]->collider.bounds = TransformToRectangle(physicObjects[k]->transform); - } + physicObjects[k]->collider.bounds = TransformToRectangle(physicObjects[k]->transform); } } } @@ -298,7 +486,7 @@ PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale obj->rigidbody.friction = 0.0f; obj->rigidbody.bounciness = 0.0f; - obj->collider.enabled = false; + obj->collider.enabled = true; obj->collider.type = COLLIDER_RECTANGLE; obj->collider.bounds = TransformToRectangle(obj->transform); obj->collider.radius = 0.0f; @@ -334,6 +522,45 @@ void DestroyPhysicObject(PhysicObject *pObj) physicObjectsCount--; } +// Apply directional force to a physic object +void ApplyForce(PhysicObject *pObj, Vector2 force) +{ + if (pObj->rigidbody.enabled) + { + pObj->rigidbody.velocity.x += force.x/pObj->rigidbody.mass; + pObj->rigidbody.velocity.y += force.y/pObj->rigidbody.mass; + } +} + +// Apply radial force to all physic objects in range +void ApplyForceAtPosition(Vector2 position, float force, float radius) +{ + for(int i = 0; i < physicObjectsCount; i++) + { + // Calculate direction and distance between force and physic object pposition + Vector2 distance = (Vector2){ physicObjects[i]->transform.position.x - position.x, physicObjects[i]->transform.position.y - position.y }; + + if(physicObjects[i]->collider.type == COLLIDER_RECTANGLE) + { + distance.x += physicObjects[i]->transform.scale.x/2; + distance.y += physicObjects[i]->transform.scale.y/2; + } + + float distanceLength = Vector2Length(distance); + + // Check if physic object is in force range + if(distanceLength <= radius) + { + // Normalize force direction + distance.x /= distanceLength; + distance.y /= -distanceLength; + + // Apply force to the physic object + ApplyForce(physicObjects[i], (Vector2){ distance.x*force, distance.y*force }); + } + } +} + // Convert Transform data type to Rectangle (position and scale) Rectangle TransformToRectangle(Transform transform) { @@ -369,3 +596,12 @@ static float Vector2DotProduct(Vector2 v1, Vector2 v2) return result; } + +static float Vector2Length(Vector2 v) +{ + float result; + + result = sqrt(v.x*v.x + v.y*v.y); + + return result; +} diff --git a/src/physac.h b/src/physac.h index fc5502c4..852a7e4a 100644 --- a/src/physac.h +++ b/src/physac.h @@ -40,7 +40,7 @@ typedef struct Vector2 { float y; } Vector2; -typedef enum { COLLIDER_CIRCLE, COLLIDER_RECTANGLE, COLLIDER_CAPSULE } ColliderType; +typedef enum { COLLIDER_CIRCLE, COLLIDER_RECTANGLE } ColliderType; typedef struct Transform { Vector2 position; @@ -56,7 +56,7 @@ typedef struct Rigidbody { bool applyGravity; bool isGrounded; float friction; // Normalized value - float bounciness; // Normalized value + float bounciness; } Rigidbody; typedef struct Collider { @@ -81,13 +81,16 @@ extern "C" { // Prevents name mangling of functions //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- -void InitPhysics(); // Initializes pointers array (just pointers, fixed size) +void InitPhysics(Vector2 gravity); // Initializes pointers array (just pointers, fixed size) void UpdatePhysics(); // Update physic objects, calculating physic behaviours and collisions detection void ClosePhysics(); // Unitialize all physic objects and empty the objects pool PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale); // Create a new physic object dinamically, initialize it and add to pool void DestroyPhysicObject(PhysicObject *pObj); // Destroy a specific physic object and take it out of the list +void ApplyForce(PhysicObject *pObj, Vector2 force); // Apply directional force to a physic object +void ApplyForceAtPosition(Vector2 position, float force, float radius); // Apply radial force to all physic objects in range + Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale) void DrawPhysicObjectInfo(PhysicObject *pObj, Vector2 position, int fontSize); // Draw physic object information at screen position -- cgit v1.2.3 From 0caf925d5dc32e03852e3bf3d5fc5e31bc065f03 Mon Sep 17 00:00:00 2001 From: victorfisac Date: Wed, 16 Mar 2016 12:48:30 +0100 Subject: Updated headers --- src/physac.h | 4 ++-- src/raylib.h | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index 852a7e4a..c70dbbe2 100644 --- a/src/physac.h +++ b/src/physac.h @@ -62,8 +62,8 @@ typedef struct Rigidbody { typedef struct Collider { bool enabled; ColliderType type; - Rectangle bounds; // Used for COLLIDER_RECTANGLE and COLLIDER_CAPSULE - int radius; // Used for COLLIDER_CIRCLE and COLLIDER_CAPSULE + Rectangle bounds; // Used for COLLIDER_RECTANGLE + int radius; // Used for COLLIDER_CIRCLE } Collider; typedef struct PhysicObject { diff --git a/src/raylib.h b/src/raylib.h index 527d0cb9..ddfccea7 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -466,7 +466,7 @@ typedef struct { // Camera system modes typedef enum { CAMERA_CUSTOM = 0, CAMERA_FREE, CAMERA_ORBITAL, CAMERA_FIRST_PERSON, CAMERA_THIRD_PERSON } CameraMode; -typedef enum { COLLIDER_CIRCLE, COLLIDER_RECTANGLE, COLLIDER_CAPSULE } ColliderType; +typedef enum { COLLIDER_CIRCLE, COLLIDER_RECTANGLE } ColliderType; typedef struct Transform { Vector2 position; @@ -482,14 +482,14 @@ typedef struct Rigidbody { bool applyGravity; bool isGrounded; float friction; // Normalized value - float bounciness; // Normalized value + float bounciness; } Rigidbody; typedef struct Collider { bool enabled; ColliderType type; - Rectangle bounds; // Used for COLLIDER_RECTANGLE and COLLIDER_CAPSULE - int radius; // Used for COLLIDER_CIRCLE and COLLIDER_CAPSULE + Rectangle bounds; // Used for COLLIDER_RECTANGLE + int radius; // Used for COLLIDER_CIRCLE } Collider; typedef struct PhysicObject { @@ -810,13 +810,16 @@ void SetBlendMode(int mode); // Set blend //---------------------------------------------------------------------------------- // Physics System Functions (Module: physac) //---------------------------------------------------------------------------------- -void InitPhysics(); // Initializes pointers array (just pointers, fixed size) +void InitPhysics(Vector2 gravity); // Initializes pointers array (just pointers, fixed size) void UpdatePhysics(); // Update physic objects, calculating physic behaviours and collisions detection void ClosePhysics(); // Unitialize all physic objects and empty the objects pool PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale); // Create a new physic object dinamically, initialize it and add to pool void DestroyPhysicObject(PhysicObject *pObj); // Destroy a specific physic object and take it out of the list +void ApplyForce(PhysicObject *pObj, Vector2 force); // Apply directional force to a physic object +void ApplyForceAtPosition(Vector2 position, float force, float radius); // Apply radial force to all physic objects in range + Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale) void DrawPhysicObjectInfo(PhysicObject *pObj, Vector2 position, int fontSize); // Draw physic object information at screen position -- cgit v1.2.3 From 60223a358b691c2769c362597c49e124b045209c Mon Sep 17 00:00:00 2001 From: victorfisac Date: Wed, 23 Mar 2016 15:50:41 +0100 Subject: Physac redesign (3/3) Finally, physics update is handled in main thread using steps to get accuracy in collisions detection instead of moving it to a new thread. Examples are finished as simple and clear as I could. Finally, physac module is MORE simpler than in the first version, calculation everything by the same way for both types of physic objects. I tryed to add rotated physics a couple of times but I didn't get anything good to get a base to improve it. Maybe for the next version... No bugs or strange behaviours found during testing. --- examples/physics_basic_rigidbody.c | 9 ++++--- examples/physics_basic_rigidbody.png | Bin 18144 -> 15294 bytes examples/physics_forces.c | 40 +++++++++++++++++++++------- examples/physics_forces.png | Bin 0 -> 17935 bytes src/physac.c | 49 +++++++++++++++++++---------------- src/physac.h | 6 ++--- src/raylib.h | 4 +-- 7 files changed, 68 insertions(+), 40 deletions(-) create mode 100644 examples/physics_forces.png (limited to 'src/physac.h') diff --git a/examples/physics_basic_rigidbody.c b/examples/physics_basic_rigidbody.c index f0edba72..917813ad 100644 --- a/examples/physics_basic_rigidbody.c +++ b/examples/physics_basic_rigidbody.c @@ -65,7 +65,7 @@ int main() if (IsKeyDown('A')) rectangle->rigidbody.velocity.x = -MOVE_VELOCITY; else if (IsKeyDown('D')) rectangle->rigidbody.velocity.x = MOVE_VELOCITY; - // Check player 2 movement inputs + // Check square movement inputs if (IsKeyDown(KEY_UP) && square->rigidbody.isGrounded) square->rigidbody.velocity.y = JUMP_VELOCITY; if (IsKeyDown(KEY_LEFT)) square->rigidbody.velocity.x = -MOVE_VELOCITY; else if (IsKeyDown(KEY_RIGHT)) square->rigidbody.velocity.x = MOVE_VELOCITY; @@ -80,17 +80,20 @@ int main() ClearBackground(RAYWHITE); - // Convert transform values to rectangle data type variable - DrawRectangleRec(TransformToRectangle(floor->transform), DARKGRAY); + // Draw floor, roof and walls rectangles + DrawRectangleRec(TransformToRectangle(floor->transform), DARKGRAY); // Convert transform values to rectangle data type variable DrawRectangleRec(TransformToRectangle(leftWall->transform), DARKGRAY); DrawRectangleRec(TransformToRectangle(rightWall->transform), DARKGRAY); DrawRectangleRec(TransformToRectangle(roof->transform), DARKGRAY); + // Draw middle platform rectangle DrawRectangleRec(TransformToRectangle(platform->transform), DARKGRAY); + // Draw physic objects DrawRectangleRec(TransformToRectangle(rectangle->transform), RED); DrawRectangleRec(TransformToRectangle(square->transform), BLUE); + // Draw collider lines if debug is enabled if (isDebug) { DrawRectangleLines(floor->collider.bounds.x, floor->collider.bounds.y, floor->collider.bounds.width, floor->collider.bounds.height, GREEN); diff --git a/examples/physics_basic_rigidbody.png b/examples/physics_basic_rigidbody.png index 3d691637..52f265ac 100644 Binary files a/examples/physics_basic_rigidbody.png and b/examples/physics_basic_rigidbody.png differ diff --git a/examples/physics_forces.c b/examples/physics_forces.c index 2afd14ee..74b40d57 100644 --- a/examples/physics_forces.c +++ b/examples/physics_forces.c @@ -12,9 +12,12 @@ #include "raylib.h" #include "math.h" -#define FORCE_AMOUNT 5.0f -#define FORCE_RADIUS 150 -#define LINE_LENGTH 100 +#define FORCE_AMOUNT 5.0f +#define FORCE_RADIUS 150 +#define LINE_LENGTH 75 +#define TRIANGLE_LENGTH 12 + +void DrawRigidbodyCircle(PhysicObject *obj, Color color); int main() { @@ -42,6 +45,7 @@ int main() } // Create circles physic objects + // NOTE: when creating circle physic objects, transform.scale must be { 0, 0 } and object radius must be defined in collider.radius and use this value to draw the circle. PhysicObject *circles[3]; for (int i = 0; i < 3; i++) { @@ -111,14 +115,23 @@ int main() // Draw force radius DrawCircleLines(mousePosition.x, mousePosition.y, FORCE_RADIUS, BLACK); - // Draw direction line + // Draw direction lines if (CheckCollisionPointCircle((Vector2){ rectangles[i]->transform.position.x + rectangles[i]->transform.scale.x/2, rectangles[i]->transform.position.y + rectangles[i]->transform.scale.y/2 }, mousePosition, FORCE_RADIUS)) { Vector2 direction = { rectangles[i]->transform.position.x + rectangles[i]->transform.scale.x/2 - mousePosition.x, rectangles[i]->transform.position.y + rectangles[i]->transform.scale.y/2 - mousePosition.y }; float angle = atan2l(direction.y, direction.x); - DrawLineV((Vector2){ rectangles[i]->transform.position.x + rectangles[i]->transform.scale.x/2, rectangles[i]->transform.position.y + rectangles[i]->transform.scale.y/2 }, - (Vector2){ rectangles[i]->transform.position.x + rectangles[i]->transform.scale.x/2 + (cos(angle)*LINE_LENGTH), rectangles[i]->transform.position.y + rectangles[i]->transform.scale.y/2 + (sin(angle)*LINE_LENGTH) }, BLACK); + // Calculate arrow start and end positions + Vector2 startPosition = { rectangles[i]->transform.position.x + rectangles[i]->transform.scale.x/2, rectangles[i]->transform.position.y + rectangles[i]->transform.scale.y/2 }; + Vector2 endPosition = { rectangles[i]->transform.position.x + rectangles[i]->transform.scale.x/2 + (cos(angle)*LINE_LENGTH), rectangles[i]->transform.position.y + rectangles[i]->transform.scale.y/2 + (sin(angle)*LINE_LENGTH) }; + + // Draw arrow line + DrawLineV(startPosition, endPosition, BLACK); + + // Draw arrow triangle + DrawTriangleLines((Vector2){ endPosition.x - cos(angle + 90*DEG2RAD)*LINE_LENGTH/TRIANGLE_LENGTH, endPosition.y - sin(angle + 90*DEG2RAD)*LINE_LENGTH/TRIANGLE_LENGTH }, + (Vector2){ endPosition.x + cos(angle + 90*DEG2RAD)*LINE_LENGTH/TRIANGLE_LENGTH, endPosition.y + sin(angle + 90*DEG2RAD)*LINE_LENGTH/TRIANGLE_LENGTH }, + (Vector2){ endPosition.x + cos(angle)*LINE_LENGTH/TRIANGLE_LENGTH*2, endPosition.y + sin(angle)*LINE_LENGTH/TRIANGLE_LENGTH*2 }, BLACK); } } @@ -131,14 +144,23 @@ int main() // Draw force radius DrawCircleLines(mousePosition.x, mousePosition.y, FORCE_RADIUS, BLACK); - // Draw direction line + // Draw direction lines if (CheckCollisionPointCircle((Vector2){ circles[i]->transform.position.x, circles[i]->transform.position.y }, mousePosition, FORCE_RADIUS)) { Vector2 direction = { circles[i]->transform.position.x - mousePosition.x, circles[i]->transform.position.y - mousePosition.y }; float angle = atan2l(direction.y, direction.x); - DrawLineV((Vector2){ circles[i]->transform.position.x, circles[i]->transform.position.y }, - (Vector2){ circles[i]->transform.position.x + (cos(angle)*LINE_LENGTH), circles[i]->transform.position.y + (sin(angle)*LINE_LENGTH) }, BLACK); + // Calculate arrow start and end positions + Vector2 startPosition = { circles[i]->transform.position.x, circles[i]->transform.position.y }; + Vector2 endPosition = { circles[i]->transform.position.x + (cos(angle)*LINE_LENGTH), circles[i]->transform.position.y + (sin(angle)*LINE_LENGTH) }; + + // Draw arrow line + DrawLineV(startPosition, endPosition, BLACK); + + // Draw arrow triangle + DrawTriangleLines((Vector2){ endPosition.x - cos(angle + 90*DEG2RAD)*LINE_LENGTH/TRIANGLE_LENGTH, endPosition.y - sin(angle + 90*DEG2RAD)*LINE_LENGTH/TRIANGLE_LENGTH }, + (Vector2){ endPosition.x + cos(angle + 90*DEG2RAD)*LINE_LENGTH/TRIANGLE_LENGTH, endPosition.y + sin(angle + 90*DEG2RAD)*LINE_LENGTH/TRIANGLE_LENGTH }, + (Vector2){ endPosition.x + cos(angle)*LINE_LENGTH/TRIANGLE_LENGTH*2, endPosition.y + sin(angle)*LINE_LENGTH/TRIANGLE_LENGTH*2 }, BLACK); } } diff --git a/examples/physics_forces.png b/examples/physics_forces.png new file mode 100644 index 00000000..832bdbd9 Binary files /dev/null and b/examples/physics_forces.png differ diff --git a/src/physac.c b/src/physac.c index 718a06bb..e3f956ba 100644 --- a/src/physac.c +++ b/src/physac.c @@ -2,7 +2,7 @@ * * [physac] raylib physics module - Basic functions to apply physics to 2D objects * -* Copyright (c) 2015 Victor Fisac and Ramon Santamaria +* Copyright (c) 2016 Victor Fisac and Ramon Santamaria * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -75,10 +75,7 @@ void InitPhysics(Vector2 gravity) void UpdatePhysics() { // Reset all physic objects is grounded state - for (int i = 0; i < physicObjectsCount; i++) - { - if (physicObjects[i]->rigidbody.enabled) physicObjects[i]->rigidbody.isGrounded = false; - } + for (int i = 0; i < physicObjectsCount; i++) physicObjects[i]->rigidbody.isGrounded = false; for (int steps = 0; steps < PHYSICS_STEPS; steps++) { @@ -537,26 +534,32 @@ void ApplyForceAtPosition(Vector2 position, float force, float radius) { for(int i = 0; i < physicObjectsCount; i++) { - // Calculate direction and distance between force and physic object pposition - Vector2 distance = (Vector2){ physicObjects[i]->transform.position.x - position.x, physicObjects[i]->transform.position.y - position.y }; - - if(physicObjects[i]->collider.type == COLLIDER_RECTANGLE) + if(physicObjects[i]->rigidbody.enabled) { - distance.x += physicObjects[i]->transform.scale.x/2; - distance.y += physicObjects[i]->transform.scale.y/2; - } - - float distanceLength = Vector2Length(distance); - - // Check if physic object is in force range - if(distanceLength <= radius) - { - // Normalize force direction - distance.x /= distanceLength; - distance.y /= -distanceLength; + // Calculate direction and distance between force and physic object pposition + Vector2 distance = (Vector2){ physicObjects[i]->transform.position.x - position.x, physicObjects[i]->transform.position.y - position.y }; + + if(physicObjects[i]->collider.type == COLLIDER_RECTANGLE) + { + distance.x += physicObjects[i]->transform.scale.x/2; + distance.y += physicObjects[i]->transform.scale.y/2; + } + + float distanceLength = Vector2Length(distance); - // Apply force to the physic object - ApplyForce(physicObjects[i], (Vector2){ distance.x*force, distance.y*force }); + // Check if physic object is in force range + if(distanceLength <= radius) + { + // Normalize force direction + distance.x /= distanceLength; + distance.y /= -distanceLength; + + // Calculate final force + Vector2 finalForce = { distance.x*force, distance.y*force }; + + // Apply force to the physic object + ApplyForce(physicObjects[i], finalForce); + } } } } diff --git a/src/physac.h b/src/physac.h index c70dbbe2..37544686 100644 --- a/src/physac.h +++ b/src/physac.h @@ -2,7 +2,7 @@ * * [physac] raylib physics module - Basic functions to apply physics to 2D objects * -* Copyright (c) 2015 Victor Fisac and Ramon Santamaria +* Copyright (c) 2016 Victor Fisac and Ramon Santamaria * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -44,8 +44,8 @@ typedef enum { COLLIDER_CIRCLE, COLLIDER_RECTANGLE } ColliderType; typedef struct Transform { Vector2 position; - float rotation; - Vector2 scale; + float rotation; // Radians (not used) + Vector2 scale; // Just for rectangle physic objects, for circle physic objects use collider radius and keep scale as { 0, 0 } } Transform; typedef struct Rigidbody { diff --git a/src/raylib.h b/src/raylib.h index a87b58da..1782fef3 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -498,8 +498,8 @@ typedef enum { COLLIDER_CIRCLE, COLLIDER_RECTANGLE } ColliderType; typedef struct Transform { Vector2 position; - float rotation; - Vector2 scale; + float rotation; // Radians (not used) + Vector2 scale; // Just for rectangle physic objects, for circle physic objects use collider radius and keep scale as { 0, 0 } } Transform; typedef struct Rigidbody { -- cgit v1.2.3 From af890cf21021e4a306bf3f6e4397496b95c1c93a Mon Sep 17 00:00:00 2001 From: raysan5 Date: Fri, 20 May 2016 10:53:58 +0200 Subject: Updated to avoid pointers --- src/physac.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index 37544686..6cef480a 100644 --- a/src/physac.h +++ b/src/physac.h @@ -66,13 +66,13 @@ typedef struct Collider { int radius; // Used for COLLIDER_CIRCLE } Collider; -typedef struct PhysicObject { +typedef struct PhysicObjectData { unsigned int id; Transform transform; Rigidbody rigidbody; Collider collider; bool enabled; -} PhysicObject; +} PhysicObjectData, *PhysicObject; #ifdef __cplusplus extern "C" { // Prevents name mangling of functions @@ -85,14 +85,14 @@ void InitPhysics(Vector2 gravity); void UpdatePhysics(); // Update physic objects, calculating physic behaviours and collisions detection void ClosePhysics(); // Unitialize all physic objects and empty the objects pool -PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale); // Create a new physic object dinamically, initialize it and add to pool -void DestroyPhysicObject(PhysicObject *pObj); // Destroy a specific physic object and take it out of the list +PhysicObject CreatePhysicObject(Vector2 position, float rotation, Vector2 scale); // Create a new physic object dinamically, initialize it and add to pool +void DestroyPhysicObject(PhysicObject pObj); // Destroy a specific physic object and take it out of the list -void ApplyForce(PhysicObject *pObj, Vector2 force); // Apply directional force to a physic object +void ApplyForce(PhysicObject pObj, Vector2 force); // Apply directional force to a physic object void ApplyForceAtPosition(Vector2 position, float force, float radius); // Apply radial force to all physic objects in range Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale) -void DrawPhysicObjectInfo(PhysicObject *pObj, Vector2 position, int fontSize); // Draw physic object information at screen position +void DrawPhysicObjectInfo(PhysicObject pObj, Vector2 position, int fontSize); // Draw physic object information at screen position #ifdef __cplusplus } -- cgit v1.2.3 From 2168d8aa1af1e60467983099c5f72b7ac5ab5144 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Thu, 2 Jun 2016 19:16:11 +0200 Subject: Removed DrawPhysicObjectInfo() function To avoid additional dependencies --- src/physac.c | 16 ---------------- src/physac.h | 1 - src/raylib.h | 1 - 3 files changed, 18 deletions(-) (limited to 'src/physac.h') diff --git a/src/physac.c b/src/physac.c index eed2f26e..1d577d3d 100644 --- a/src/physac.c +++ b/src/physac.c @@ -570,22 +570,6 @@ Rectangle TransformToRectangle(Transform transform) return (Rectangle){transform.position.x, transform.position.y, transform.scale.x, transform.scale.y}; } -// Draw physic object information at screen position -void DrawPhysicObjectInfo(PhysicObject pObj, Vector2 position, int fontSize) -{ - // Draw physic object ID - DrawText(FormatText("PhysicObject ID: %i - Enabled: %i", pObj->id, pObj->enabled), position.x, position.y, fontSize, BLACK); - - // Draw physic object transform values - DrawText(FormatText("\nTRANSFORM\nPosition: %f, %f\nRotation: %f\nScale: %f, %f", pObj->transform.position.x, pObj->transform.position.y, pObj->transform.rotation, pObj->transform.scale.x, pObj->transform.scale.y), position.x, position.y, fontSize, BLACK); - - // Draw physic object rigidbody values - DrawText(FormatText("\n\n\n\n\n\nRIGIDBODY\nEnabled: %i\nMass: %f\nAcceleration: %f, %f\nVelocity: %f, %f\nApplyGravity: %i\nIsGrounded: %i\nFriction: %f\nBounciness: %f", pObj->rigidbody.enabled, pObj->rigidbody.mass, pObj->rigidbody.acceleration.x, pObj->rigidbody.acceleration.y, - pObj->rigidbody.velocity.x, pObj->rigidbody.velocity.y, pObj->rigidbody.applyGravity, pObj->rigidbody.isGrounded, pObj->rigidbody.friction, pObj->rigidbody.bounciness), position.x, position.y, fontSize, BLACK); - - DrawText(FormatText("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCOLLIDER\nEnabled: %i\nBounds: %i, %i, %i, %i\nRadius: %i", pObj->collider.enabled, pObj->collider.bounds.x, pObj->collider.bounds.y, pObj->collider.bounds.width, pObj->collider.bounds.height, pObj->collider.radius), position.x, position.y, fontSize, BLACK); -} - //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- diff --git a/src/physac.h b/src/physac.h index 6cef480a..b2ae2766 100644 --- a/src/physac.h +++ b/src/physac.h @@ -92,7 +92,6 @@ void ApplyForce(PhysicObject pObj, Vector2 force); void ApplyForceAtPosition(Vector2 position, float force, float radius); // Apply radial force to all physic objects in range Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale) -void DrawPhysicObjectInfo(PhysicObject pObj, Vector2 position, int fontSize); // Draw physic object information at screen position #ifdef __cplusplus } diff --git a/src/raylib.h b/src/raylib.h index bc2f658f..a00c0ff9 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -890,7 +890,6 @@ void ApplyForce(PhysicObject pObj, Vector2 force); void ApplyForceAtPosition(Vector2 position, float force, float radius); // Apply radial force to all physic objects in range Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale) -void DrawPhysicObjectInfo(PhysicObject pObj, Vector2 position, int fontSize); // Draw physic object information at screen position //------------------------------------------------------------------------------------ // Audio Loading and Playing Functions (Module: audio) -- cgit v1.2.3 From 558ec3891bc620d1481557ae291a9524e588b31e Mon Sep 17 00:00:00 2001 From: raysan5 Date: Thu, 9 Jun 2016 20:01:59 +0200 Subject: Converted physac module to header only --- src/physac.c | 594 --------------------------------------------------- src/physac.h | 684 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 667 insertions(+), 611 deletions(-) delete mode 100644 src/physac.c (limited to 'src/physac.h') diff --git a/src/physac.c b/src/physac.c deleted file mode 100644 index 1d577d3d..00000000 --- a/src/physac.c +++ /dev/null @@ -1,594 +0,0 @@ -/********************************************************************************************** -* -* [physac] raylib physics module - Basic functions to apply physics to 2D objects -* -* Copyright (c) 2016 Victor Fisac and Ramon Santamaria -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -//#define PHYSAC_STANDALONE // NOTE: To use the physics module as standalone lib, just uncomment this line - -#if defined(PHYSAC_STANDALONE) - #include "physac.h" -#else - #include "raylib.h" -#endif - -#include // Required for: malloc(), free() -#include // Required for: cos(), sin(), abs(), fminf() - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -#define MAX_PHYSIC_OBJECTS 256 // Maximum available physic object slots in objects pool -#define PHYSICS_STEPS 450 // Physics update steps number (divided calculations in steps per frame) to get more accurately collisions detections -#define PHYSICS_ACCURACY 0.0001f // Velocity subtract operations round filter (friction) -#define PHYSICS_ERRORPERCENT 0.001f // Collision resolve position fix - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -// NOTE: Below types are required for PHYSAC_STANDALONE usage -//---------------------------------------------------------------------------------- -// ... - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -static PhysicObject physicObjects[MAX_PHYSIC_OBJECTS]; // Physic objects pool -static int physicObjectsCount; // Counts current enabled physic objects -static Vector2 gravityForce; // Gravity force - -//---------------------------------------------------------------------------------- -// Module specific Functions Declaration -//---------------------------------------------------------------------------------- -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 - -//---------------------------------------------------------------------------------- -// Module Functions Definition -//---------------------------------------------------------------------------------- - -// Initializes pointers array (just pointers, fixed size) -void InitPhysics(Vector2 gravity) -{ - // Initialize physics variables - physicObjectsCount = 0; - gravityForce = gravity; -} - -// Update physic objects, calculating physic behaviours and collisions detection -void UpdatePhysics() -{ - // Reset all physic objects is grounded state - for (int i = 0; i < physicObjectsCount; i++) physicObjects[i]->rigidbody.isGrounded = false; - - for (int steps = 0; steps < PHYSICS_STEPS; steps++) - { - for (int i = 0; i < physicObjectsCount; i++) - { - if (physicObjects[i]->enabled) - { - // Update physic behaviour - if (physicObjects[i]->rigidbody.enabled) - { - // Apply friction to acceleration in X axis - if (physicObjects[i]->rigidbody.acceleration.x > PHYSICS_ACCURACY) physicObjects[i]->rigidbody.acceleration.x -= physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; - else if (physicObjects[i]->rigidbody.acceleration.x < PHYSICS_ACCURACY) physicObjects[i]->rigidbody.acceleration.x += physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; - else physicObjects[i]->rigidbody.acceleration.x = 0.0f; - - // Apply friction to acceleration in Y axis - if (physicObjects[i]->rigidbody.acceleration.y > PHYSICS_ACCURACY) physicObjects[i]->rigidbody.acceleration.y -= physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; - else if (physicObjects[i]->rigidbody.acceleration.y < PHYSICS_ACCURACY) physicObjects[i]->rigidbody.acceleration.y += physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; - else physicObjects[i]->rigidbody.acceleration.y = 0.0f; - - // Apply friction to velocity in X axis - if (physicObjects[i]->rigidbody.velocity.x > PHYSICS_ACCURACY) physicObjects[i]->rigidbody.velocity.x -= physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; - else if (physicObjects[i]->rigidbody.velocity.x < PHYSICS_ACCURACY) physicObjects[i]->rigidbody.velocity.x += physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; - else physicObjects[i]->rigidbody.velocity.x = 0.0f; - - // Apply friction to velocity in Y axis - if (physicObjects[i]->rigidbody.velocity.y > PHYSICS_ACCURACY) physicObjects[i]->rigidbody.velocity.y -= physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; - else if (physicObjects[i]->rigidbody.velocity.y < PHYSICS_ACCURACY) physicObjects[i]->rigidbody.velocity.y += physicObjects[i]->rigidbody.friction/PHYSICS_STEPS; - else physicObjects[i]->rigidbody.velocity.y = 0.0f; - - // Apply gravity to velocity - if (physicObjects[i]->rigidbody.applyGravity) - { - physicObjects[i]->rigidbody.velocity.x += gravityForce.x/PHYSICS_STEPS; - physicObjects[i]->rigidbody.velocity.y += gravityForce.y/PHYSICS_STEPS; - } - - // Apply acceleration to velocity - physicObjects[i]->rigidbody.velocity.x += physicObjects[i]->rigidbody.acceleration.x/PHYSICS_STEPS; - physicObjects[i]->rigidbody.velocity.y += physicObjects[i]->rigidbody.acceleration.y/PHYSICS_STEPS; - - // Apply velocity to position - physicObjects[i]->transform.position.x += physicObjects[i]->rigidbody.velocity.x/PHYSICS_STEPS; - physicObjects[i]->transform.position.y -= physicObjects[i]->rigidbody.velocity.y/PHYSICS_STEPS; - } - - // Update collision detection - if (physicObjects[i]->collider.enabled) - { - // Update collider bounds - physicObjects[i]->collider.bounds = TransformToRectangle(physicObjects[i]->transform); - - // Check collision with other colliders - for (int k = 0; k < physicObjectsCount; k++) - { - if (physicObjects[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 (physicObjects[i]->collider.type) - { - case COLLIDER_RECTANGLE: - { - switch (physicObjects[k]->collider.type) - { - case COLLIDER_RECTANGLE: - { - // Check if colliders are overlapped - if (CheckCollisionRecs(physicObjects[i]->collider.bounds, physicObjects[k]->collider.bounds)) - { - // Calculate direction vector from i to k - direction.x = (physicObjects[k]->transform.position.x + physicObjects[k]->transform.scale.x/2) - (physicObjects[i]->transform.position.x + physicObjects[i]->transform.scale.x/2); - direction.y = (physicObjects[k]->transform.position.y + physicObjects[k]->transform.scale.y/2) - (physicObjects[i]->transform.position.y + physicObjects[i]->transform.scale.y/2); - - // Define overlapping and penetration attributes - Vector2 overlap; - - // Calculate overlap on X axis - overlap.x = (physicObjects[i]->transform.scale.x + physicObjects[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 = (physicObjects[i]->transform.scale.y + physicObjects[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(physicObjects[k]->transform.position, physicObjects[k]->collider.radius, physicObjects[i]->collider.bounds)) - { - // Calculate direction vector between circles - direction.x = physicObjects[k]->transform.position.x - physicObjects[i]->transform.position.x + physicObjects[i]->transform.scale.x/2; - direction.y = physicObjects[k]->transform.position.y - physicObjects[i]->transform.position.y + physicObjects[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 = physicObjects[i]->collider.bounds.x + physicObjects[i]->collider.bounds.width; - else closestPoint.x = physicObjects[i]->collider.bounds.x; - - if (direction.y > 0.0f) closestPoint.y = physicObjects[i]->collider.bounds.y + physicObjects[i]->collider.bounds.height; - else closestPoint.y = physicObjects[i]->collider.bounds.y; - - // Check if the closest point is inside the circle - if (CheckCollisionPointCircle(closestPoint, physicObjects[k]->transform.position, physicObjects[k]->collider.radius)) - { - // Recalculate direction based on closest point position - direction.x = physicObjects[k]->transform.position.x - closestPoint.x; - direction.y = physicObjects[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 = physicObjects[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(physicObjects[i]->collider.bounds.y - physicObjects[k]->transform.position.y - physicObjects[k]->collider.radius); - } - else - { - contactNormal = (Vector2){ 0.0f, 1.0f }; - penetrationDepth = fabs(physicObjects[i]->collider.bounds.y - physicObjects[k]->transform.position.y + physicObjects[k]->collider.radius); - } - } - else - { - // Calculate final contact normal - if (direction.x > 0.0f) - { - contactNormal = (Vector2){ 1.0f, 0.0f }; - penetrationDepth = fabs(physicObjects[k]->transform.position.x + physicObjects[k]->collider.radius - physicObjects[i]->collider.bounds.x); - } - else - { - contactNormal = (Vector2){ -1.0f, 0.0f }; - penetrationDepth = fabs(physicObjects[i]->collider.bounds.x + physicObjects[i]->collider.bounds.width - physicObjects[k]->transform.position.x - physicObjects[k]->collider.radius); - } - } - } - } - } break; - } - } break; - case COLLIDER_CIRCLE: - { - switch (physicObjects[k]->collider.type) - { - case COLLIDER_RECTANGLE: - { - if (CheckCollisionCircleRec(physicObjects[i]->transform.position, physicObjects[i]->collider.radius, physicObjects[k]->collider.bounds)) - { - // Calculate direction vector between circles - direction.x = physicObjects[k]->transform.position.x + physicObjects[i]->transform.scale.x/2 - physicObjects[i]->transform.position.x; - direction.y = physicObjects[k]->transform.position.y + physicObjects[i]->transform.scale.y/2 - physicObjects[i]->transform.position.y; - - // Calculate closest point on rectangle to circle - Vector2 closestPoint = { 0.0f, 0.0f }; - if (direction.x > 0.0f) closestPoint.x = physicObjects[k]->collider.bounds.x + physicObjects[k]->collider.bounds.width; - else closestPoint.x = physicObjects[k]->collider.bounds.x; - - if (direction.y > 0.0f) closestPoint.y = physicObjects[k]->collider.bounds.y + physicObjects[k]->collider.bounds.height; - else closestPoint.y = physicObjects[k]->collider.bounds.y; - - // Check if the closest point is inside the circle - if (CheckCollisionPointCircle(closestPoint, physicObjects[i]->transform.position, physicObjects[i]->collider.radius)) - { - // Recalculate direction based on closest point position - direction.x = physicObjects[i]->transform.position.x - closestPoint.x; - direction.y = physicObjects[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 = physicObjects[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(physicObjects[k]->collider.bounds.y - physicObjects[i]->transform.position.y - physicObjects[i]->collider.radius); - } - else - { - contactNormal = (Vector2){ 0.0f, 1.0f }; - penetrationDepth = fabs(physicObjects[k]->collider.bounds.y - physicObjects[i]->transform.position.y + physicObjects[i]->collider.radius); - } - } - else - { - // Calculate final contact normal and penetration depth - if (direction.x > 0.0f) - { - contactNormal = (Vector2){ 1.0f, 0.0f }; - penetrationDepth = fabs(physicObjects[i]->transform.position.x + physicObjects[i]->collider.radius - physicObjects[k]->collider.bounds.x); - } - else - { - contactNormal = (Vector2){ -1.0f, 0.0f }; - penetrationDepth = fabs(physicObjects[k]->collider.bounds.x + physicObjects[k]->collider.bounds.width - physicObjects[i]->transform.position.x - physicObjects[i]->collider.radius); - } - } - } - } - } break; - case COLLIDER_CIRCLE: - { - // Check if colliders are overlapped - if (CheckCollisionCircles(physicObjects[i]->transform.position, physicObjects[i]->collider.radius, physicObjects[k]->transform.position, physicObjects[k]->collider.radius)) - { - // Calculate direction vector between circles - direction.x = physicObjects[k]->transform.position.x - physicObjects[i]->transform.position.x; - direction.y = physicObjects[k]->transform.position.y - physicObjects[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 (physicObjects[i]->rigidbody.enabled) - { - if (contactNormal.y < 0.0f) physicObjects[i]->rigidbody.isGrounded = true; - } - - // 2. Calculate collision impulse - // ------------------------------------------------------------------------------------------------------------------------------------- - - // Calculate relative velocity - Vector2 relVelocity = { 0.0f, 0.0f }; - relVelocity.x = physicObjects[k]->rigidbody.velocity.x - physicObjects[i]->rigidbody.velocity.x; - relVelocity.y = physicObjects[k]->rigidbody.velocity.y - physicObjects[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(physicObjects[i]->rigidbody.bounciness, physicObjects[k]->rigidbody.bounciness); - - // Calculate impulse scalar value - float j = -(1.0f + e)*velAlongNormal; - j /= 1.0f/physicObjects[i]->rigidbody.mass + 1.0f/physicObjects[k]->rigidbody.mass; - - // Calculate final impulse vector - Vector2 impulse = { j*contactNormal.x, j*contactNormal.y }; - - // Calculate collision mass ration - float massSum = physicObjects[i]->rigidbody.mass + physicObjects[k]->rigidbody.mass; - float ratio = 0.0f; - - // Apply impulse to current rigidbodies velocities if they are enabled - if (physicObjects[i]->rigidbody.enabled) - { - // Calculate inverted mass ration - ratio = physicObjects[i]->rigidbody.mass/massSum; - - // Apply impulse direction to velocity - physicObjects[i]->rigidbody.velocity.x -= impulse.x*ratio*(1.0f+physicObjects[i]->rigidbody.bounciness); - physicObjects[i]->rigidbody.velocity.y -= impulse.y*ratio*(1.0f+physicObjects[i]->rigidbody.bounciness); - } - - if (physicObjects[k]->rigidbody.enabled) - { - // Calculate inverted mass ration - ratio = physicObjects[k]->rigidbody.mass/massSum; - - // Apply impulse direction to velocity - physicObjects[k]->rigidbody.velocity.x += impulse.x*ratio*(1.0f+physicObjects[i]->rigidbody.bounciness); - physicObjects[k]->rigidbody.velocity.y += impulse.y*ratio*(1.0f+physicObjects[i]->rigidbody.bounciness); - } - - // 3. Correct colliders overlaping (transform position) - // --------------------------------------------------------------------------------------------------------------------------------- - - // Calculate transform position penetration correction - Vector2 posCorrection; - posCorrection.x = penetrationDepth/((1.0f/physicObjects[i]->rigidbody.mass) + (1.0f/physicObjects[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.x; - posCorrection.y = penetrationDepth/((1.0f/physicObjects[i]->rigidbody.mass) + (1.0f/physicObjects[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.y; - - // Fix transform positions - if (physicObjects[i]->rigidbody.enabled) - { - // Fix physic objects transform position - physicObjects[i]->transform.position.x -= 1.0f/physicObjects[i]->rigidbody.mass*posCorrection.x; - physicObjects[i]->transform.position.y += 1.0f/physicObjects[i]->rigidbody.mass*posCorrection.y; - - // Update collider bounds - physicObjects[i]->collider.bounds = TransformToRectangle(physicObjects[i]->transform); - - if (physicObjects[k]->rigidbody.enabled) - { - // Fix physic objects transform position - physicObjects[k]->transform.position.x += 1.0f/physicObjects[k]->rigidbody.mass*posCorrection.x; - physicObjects[k]->transform.position.y -= 1.0f/physicObjects[k]->rigidbody.mass*posCorrection.y; - - // Update collider bounds - physicObjects[k]->collider.bounds = TransformToRectangle(physicObjects[k]->transform); - } - } - } - } - } - } - } - } - } -} - -// Unitialize all physic objects and empty the objects pool -void ClosePhysics() -{ - // Free all dynamic memory allocations - for (int i = 0; i < physicObjectsCount; i++) free(physicObjects[i]); - - // Reset enabled physic objects count - physicObjectsCount = 0; -} - -// Create a new physic object dinamically, initialize it and add to pool -PhysicObject CreatePhysicObject(Vector2 position, float rotation, Vector2 scale) -{ - // Allocate dynamic memory - PhysicObject obj = (PhysicObject)malloc(sizeof(PhysicObjectData)); - - // Initialize physic object values with generic values - obj->id = physicObjectsCount; - obj->enabled = true; - - obj->transform = (Transform){ (Vector2){ position.x - scale.x/2, position.y - scale.y/2 }, rotation, scale }; - - obj->rigidbody.enabled = false; - obj->rigidbody.mass = 1.0f; - obj->rigidbody.acceleration = (Vector2){ 0.0f, 0.0f }; - obj->rigidbody.velocity = (Vector2){ 0.0f, 0.0f }; - obj->rigidbody.applyGravity = false; - obj->rigidbody.isGrounded = false; - obj->rigidbody.friction = 0.0f; - obj->rigidbody.bounciness = 0.0f; - - obj->collider.enabled = true; - obj->collider.type = COLLIDER_RECTANGLE; - obj->collider.bounds = TransformToRectangle(obj->transform); - obj->collider.radius = 0.0f; - - // Add new physic object to the pointers array - physicObjects[physicObjectsCount] = obj; - - // Increase enabled physic objects count - physicObjectsCount++; - - return obj; -} - -// Destroy a specific physic object and take it out of the list -void DestroyPhysicObject(PhysicObject pObj) -{ - // Free dynamic memory allocation - free(physicObjects[pObj->id]); - - // Remove *obj from the pointers array - for (int i = pObj->id; i < physicObjectsCount; i++) - { - // Resort all the following pointers of the array - if ((i + 1) < physicObjectsCount) - { - physicObjects[i] = physicObjects[i + 1]; - physicObjects[i]->id = physicObjects[i + 1]->id; - } - else free(physicObjects[i]); - } - - // Decrease enabled physic objects count - physicObjectsCount--; -} - -// Apply directional force to a physic object -void ApplyForce(PhysicObject pObj, Vector2 force) -{ - if (pObj->rigidbody.enabled) - { - pObj->rigidbody.velocity.x += force.x/pObj->rigidbody.mass; - pObj->rigidbody.velocity.y += force.y/pObj->rigidbody.mass; - } -} - -// Apply radial force to all physic objects in range -void ApplyForceAtPosition(Vector2 position, float force, float radius) -{ - for (int i = 0; i < physicObjectsCount; i++) - { - if (physicObjects[i]->rigidbody.enabled) - { - // Calculate direction and distance between force and physic object pposition - Vector2 distance = (Vector2){ physicObjects[i]->transform.position.x - position.x, physicObjects[i]->transform.position.y - position.y }; - - if (physicObjects[i]->collider.type == COLLIDER_RECTANGLE) - { - distance.x += physicObjects[i]->transform.scale.x/2; - distance.y += physicObjects[i]->transform.scale.y/2; - } - - float distanceLength = Vector2Length(distance); - - // Check if physic object is in force range - if (distanceLength <= radius) - { - // Normalize force direction - distance.x /= distanceLength; - distance.y /= -distanceLength; - - // Calculate final force - Vector2 finalForce = { distance.x*force, distance.y*force }; - - // Apply force to the physic object - ApplyForce(physicObjects[i], finalForce); - } - } - } -} - -// Convert Transform data type to Rectangle (position and scale) -Rectangle TransformToRectangle(Transform transform) -{ - return (Rectangle){transform.position.x, transform.position.y, transform.scale.x, transform.scale.y}; -} - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- - -// Returns the dot product of two Vector2 -static float Vector2DotProduct(Vector2 v1, Vector2 v2) -{ - float result; - - result = v1.x*v2.x + v1.y*v2.y; - - return result; -} - -static float Vector2Length(Vector2 v) -{ - float result; - - result = sqrt(v.x*v.x + v.y*v.y); - - return result; -} diff --git a/src/physac.h b/src/physac.h index b2ae2766..c8466a12 100644 --- a/src/physac.h +++ b/src/physac.h @@ -1,8 +1,45 @@ /********************************************************************************************** * -* [physac] raylib physics module - Basic functions to apply physics to 2D objects +* physac 1.0 - 2D Physics library for raylib (https://github.com/raysan5/raylib) * -* Copyright (c) 2016 Victor Fisac and Ramon Santamaria +* // TODO: Description... +* +* CONFIGURATION: +* +* #define PHYSAC_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define PHYSAC_STATIC (defined by default) +* The generated implementation will stay private inside implementation file and all +* internal symbols and functions will only be visible inside that file. +* +* #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 +* the user (check library implementation for further details). +* +* #define PHYSAC_MALLOC() +* #define PHYSAC_FREE() +* You can define your own malloc/free implementation replacing stdlib.h malloc()/free() functions. +* Otherwise it will include stdlib.h and use the C standard library malloc()/free() function. +* +* LIMITATIONS: +* +* // TODO. +* +* 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. +* 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. +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2016 Victor Fisac (main developer) and Ramon Santamaria * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -24,6 +61,21 @@ #ifndef PHYSAC_H #define PHYSAC_H +#if !defined(RAYGUI_STANDALONE) + #include "raylib.h" +#endif + +#define PHYSAC_STATIC +#ifdef PHYSAC_STATIC + #define PHYSACDEF static // Functions just visible to module including this file +#else + #ifdef __cplusplus + #define PHYSACDEF extern "C" // Functions visible from other files (no name mangling of functions in C++) + #else + #define PHYSACDEF extern // Functions visible from other files + #endif +#endif + //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -33,12 +85,28 @@ // Types and Structures Definition // NOTE: Below types are required for PHYSAC_STANDALONE usage //---------------------------------------------------------------------------------- +#if defined(PHYSAC_STANDALONE) + #ifndef __cplusplus + // Boolean type + #ifndef true + typedef enum { false, true } bool; + #endif + #endif -// Vector2 type -typedef struct Vector2 { - float x; - float y; -} Vector2; + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Rectangle type + typedef struct Rectangle { + int x; + int y; + int width; + int height; + } Rectangle; +#endif typedef enum { COLLIDER_CIRCLE, COLLIDER_RECTANGLE } ColliderType; @@ -66,13 +134,13 @@ typedef struct Collider { int radius; // Used for COLLIDER_CIRCLE } Collider; -typedef struct PhysicObjectData { +typedef struct PhysicBodyData { unsigned int id; Transform transform; Rigidbody rigidbody; Collider collider; bool enabled; -} PhysicObjectData, *PhysicObject; +} PhysicBodyData, *PhysicBody; #ifdef __cplusplus extern "C" { // Prevents name mangling of functions @@ -81,20 +149,602 @@ extern "C" { // Prevents name mangling of functions //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- -void InitPhysics(Vector2 gravity); // Initializes pointers array (just pointers, fixed size) -void UpdatePhysics(); // Update physic objects, calculating physic behaviours and collisions detection -void ClosePhysics(); // Unitialize all physic objects and empty the objects pool +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 ClosePhysics(); // Unitialize all physic objects and empty the objects pool -PhysicObject CreatePhysicObject(Vector2 position, float rotation, Vector2 scale); // Create a new physic object dinamically, initialize it and add to pool -void DestroyPhysicObject(PhysicObject pObj); // Destroy a specific physic object and take it out of the list +PHYSACDEF PhysicBody CreatePhysicBody(Vector2 position, float rotation, Vector2 scale); // Create a new physic body dinamically, initialize it and add to pool +PHYSACDEF void DestroyPhysicBody(PhysicBody pbody); // Destroy a specific physic body and take it out of the list -void ApplyForce(PhysicObject pObj, Vector2 force); // Apply directional force to a physic object -void ApplyForceAtPosition(Vector2 position, float force, float radius); // Apply radial force to all physic objects in range +PHYSACDEF void ApplyForce(PhysicBody pbody, Vector2 force); // Apply directional force to a physic body +PHYSACDEF void ApplyForceAtPosition(Vector2 position, float force, float radius); // Apply radial force to all physic objects in range -Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale) +PHYSACDEF Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale) #ifdef __cplusplus } #endif #endif // PHYSAC_H + + +/*********************************************************************************** +* +* PHYSAC IMPLEMENTATION +* +************************************************************************************/ + +#if defined(PHYSAC_IMPLEMENTATION) + +// Check if custom malloc/free functions defined, if not, using standard ones +#if !defined(PHYSAC_MALLOC) + #include // Required for: malloc(), free() + + #define PHYSAC_MALLOC(size) malloc(size) + #define PHYSAC_FREE(ptr) free(ptr) +#endif + +#include // Required for: cos(), sin(), abs(), fminf() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define MAX_PHYSIC_BODIES 256 // Maximum available physic bodies slots in bodies pool +#define PHYSICS_STEPS 450 // Physics update steps number (divided calculations in steps per frame) to get more accurately collisions detections +#define PHYSICS_ACCURACY 0.0001f // Velocity subtract operations round filter (friction) +#define PHYSICS_ERRORPERCENT 0.001f // Collision resolve position fix + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Below types are required for PHYSAC_STANDALONE usage +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static PhysicBody physicBodies[MAX_PHYSIC_BODIES]; // Physic bodies pool +static int physicBodiesCount; // Counts current enabled physic bodies +static Vector2 gravityForce; // Gravity force + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +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 + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Initializes pointers array (just pointers, fixed size) +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); + } + } + } + } + } + } + } + } + } +} + +// Unitialize all physic objects and empty the objects pool +PHYSACDEF void ClosePhysics() +{ + // Free all dynamic memory allocations + for (int i = 0; i < physicBodiesCount; i++) PHYSAC_FREE(physicBodies[i]); + + // Reset enabled physic objects count + physicBodiesCount = 0; +} + +// Create a new physic body dinamically, initialize it and add to pool +PHYSACDEF PhysicBody CreatePhysicBody(Vector2 position, float rotation, Vector2 scale) +{ + // Allocate dynamic memory + PhysicBody obj = (PhysicBody)PHYSAC_MALLOC(sizeof(PhysicBodyData)); + + // Initialize physic body values with generic values + obj->id = physicBodiesCount; + obj->enabled = true; + + obj->transform = (Transform){ (Vector2){ position.x - scale.x/2, position.y - scale.y/2 }, rotation, scale }; + + obj->rigidbody.enabled = false; + obj->rigidbody.mass = 1.0f; + obj->rigidbody.acceleration = (Vector2){ 0.0f, 0.0f }; + obj->rigidbody.velocity = (Vector2){ 0.0f, 0.0f }; + obj->rigidbody.applyGravity = false; + obj->rigidbody.isGrounded = false; + obj->rigidbody.friction = 0.0f; + obj->rigidbody.bounciness = 0.0f; + + obj->collider.enabled = true; + obj->collider.type = COLLIDER_RECTANGLE; + obj->collider.bounds = TransformToRectangle(obj->transform); + obj->collider.radius = 0.0f; + + // Add new physic body to the pointers array + physicBodies[physicBodiesCount] = obj; + + // Increase enabled physic bodies count + physicBodiesCount++; + + return obj; +} + +// Destroy a specific physic body and take it out of the list +PHYSACDEF void DestroyPhysicBody(PhysicBody pbody) +{ + // Free dynamic memory allocation + PHYSAC_FREE(physicBodies[pbody->id]); + + // Remove *obj from the pointers array + for (int i = pbody->id; i < physicBodiesCount; i++) + { + // Resort all the following pointers of the array + if ((i + 1) < physicBodiesCount) + { + physicBodies[i] = physicBodies[i + 1]; + physicBodies[i]->id = physicBodies[i + 1]->id; + } + else PHYSAC_FREE(physicBodies[i]); + } + + // Decrease enabled physic bodies count + physicBodiesCount--; +} + +// Apply directional force to a physic body +PHYSACDEF void ApplyForce(PhysicBody pbody, Vector2 force) +{ + if (pbody->rigidbody.enabled) + { + pbody->rigidbody.velocity.x += force.x/pbody->rigidbody.mass; + pbody->rigidbody.velocity.y += force.y/pbody->rigidbody.mass; + } +} + +// Apply radial force to all physic objects in range +PHYSACDEF void ApplyForceAtPosition(Vector2 position, float force, float radius) +{ + for (int i = 0; i < physicBodiesCount; i++) + { + if (physicBodies[i]->rigidbody.enabled) + { + // Calculate direction and distance between force and physic body position + Vector2 distance = (Vector2){ physicBodies[i]->transform.position.x - position.x, physicBodies[i]->transform.position.y - position.y }; + + if (physicBodies[i]->collider.type == COLLIDER_RECTANGLE) + { + distance.x += physicBodies[i]->transform.scale.x/2; + distance.y += physicBodies[i]->transform.scale.y/2; + } + + float distanceLength = Vector2Length(distance); + + // Check if physic body is in force range + if (distanceLength <= radius) + { + // Normalize force direction + distance.x /= distanceLength; + distance.y /= -distanceLength; + + // Calculate final force + Vector2 finalForce = { distance.x*force, distance.y*force }; + + // Apply force to the physic body + ApplyForce(physicBodies[i], finalForce); + } + } + } +} + +// Convert Transform data type to Rectangle (position and scale) +PHYSACDEF Rectangle TransformToRectangle(Transform transform) +{ + return (Rectangle){transform.position.x, transform.position.y, transform.scale.x, transform.scale.y}; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Returns the dot product of two Vector2 +static float Vector2DotProduct(Vector2 v1, Vector2 v2) +{ + float result; + + result = v1.x*v2.x + v1.y*v2.y; + + return result; +} + +static float Vector2Length(Vector2 v) +{ + float result; + + result = sqrt(v.x*v.x + v.y*v.y); + + return result; +} + +#endif // PHYSAC_IMPLEMENTATION \ No newline at end of file -- cgit v1.2.3 From e2cfc6b838c3e8579f406b6ef18978fc9d958b1c Mon Sep 17 00:00:00 2001 From: raysan5 Date: Thu, 9 Jun 2016 21:00:21 +0200 Subject: Reduced physic steps resolution --- src/physac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index c8466a12..f576ad8a 100644 --- a/src/physac.h +++ b/src/physac.h @@ -190,7 +190,7 @@ PHYSACDEF Rectangle TransformToRectangle(Transform transform); // Defines and Macros //---------------------------------------------------------------------------------- #define MAX_PHYSIC_BODIES 256 // Maximum available physic bodies slots in bodies pool -#define PHYSICS_STEPS 450 // Physics update steps number (divided calculations in steps per frame) to get more accurately collisions detections +#define PHYSICS_STEPS 64 // Physics update steps per frame for improved collision-detection #define PHYSICS_ACCURACY 0.0001f // Velocity subtract operations round filter (friction) #define PHYSICS_ERRORPERCENT 0.001f // Collision resolve position fix -- cgit v1.2.3 From c46c0fc6520914d6e9488282c02c96395d8bea9f Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 11 Jun 2016 12:18:08 +0200 Subject: Corrected keywords usage --- src/physac.h | 8 -------- src/raygui.h | 8 -------- src/raymath.h | 18 ++++++++---------- 3 files changed, 8 insertions(+), 26 deletions(-) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index f576ad8a..6a90dc29 100644 --- a/src/physac.h +++ b/src/physac.h @@ -142,10 +142,6 @@ typedef struct PhysicBodyData { bool enabled; } PhysicBodyData, *PhysicBody; -#ifdef __cplusplus -extern "C" { // Prevents name mangling of functions -#endif - //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- @@ -161,10 +157,6 @@ PHYSACDEF void ApplyForceAtPosition(Vector2 position, float force, float radius) PHYSACDEF Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale) -#ifdef __cplusplus -} -#endif - #endif // PHYSAC_H diff --git a/src/raygui.h b/src/raygui.h index f13b68cd..74f131d5 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -239,10 +239,6 @@ typedef enum GuiProperty { TEXTBOX_TEXT_FONTSIZE } GuiProperty; -#ifdef __cplusplus -extern "C" { -#endif - //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- @@ -369,10 +365,6 @@ RAYGUIDEF void LoadGuiStyle(const char *fileName); // Loa RAYGUIDEF void SetStyleProperty(int guiProperty, int value); // Set one style property RAYGUIDEF int GetStyleProperty(int guiProperty); // Get one style property -#ifdef __cplusplus -} -#endif - #endif // RAYGUI_H diff --git a/src/raymath.h b/src/raymath.h index 4075a1a9..10eabb6b 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -47,10 +47,16 @@ #include "raylib.h" // Required for structs: Vector3, Matrix #endif +#ifdef __cplusplus + #define RMEXTERN extern "C" // Functions visible from other files (no name mangling of functions in C++) +#else + #define RMEXTERN extern // Functions visible from other files +#endif + #if defined(RAYMATH_EXTERN_INLINE) - #define RMDEF extern inline + #define RMDEF RMEXTERN inline // Functions are embeded inline (compiler generated code) #else - #define RMDEF extern + #define RMDEF RMEXTERN #endif //---------------------------------------------------------------------------------- @@ -105,10 +111,6 @@ typedef struct Quaternion { #ifndef RAYMATH_EXTERN_INLINE -#ifdef __cplusplus -extern "C" { -#endif - //------------------------------------------------------------------------------------ // Functions Declaration to work with Vector3 //------------------------------------------------------------------------------------ @@ -166,10 +168,6 @@ RMDEF Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle); // Returns RMDEF void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle); // Returns the rotation angle and axis for a given quaternion RMDEF void QuaternionTransform(Quaternion *q, Matrix mat); // Transform a quaternion given a transformation matrix -#ifdef __cplusplus -} -#endif - #endif // notdef RAYMATH_EXTERN_INLINE #endif // RAYMATH_H -- cgit v1.2.3 From c10c49e44f31ddac4b544ddf8c973774afd288c6 Mon Sep 17 00:00:00 2001 From: victorfisac Date: Sat, 11 Jun 2016 18:35:46 +0200 Subject: Convert physac module from static steps to fixed time steps Old physics update system used a static number of steps to calculate physics (450 for desktop and 64 for android). It was too much and it was limited by target frame time... Now physics update runs in a secondary thread using a fixed delta time value to update steps. Collisions are perfectly detected and resolved and performance has been improved so much. --- src/physac.h | 601 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 296 insertions(+), 305 deletions(-) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index 6a90dc29..dd2b1628 100644 --- a/src/physac.h +++ b/src/physac.h @@ -146,7 +146,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 UpdatePhysics(double deltaTime); // Update physic objects, calculating physic behaviours and collisions detection 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 @@ -182,7 +182,7 @@ PHYSACDEF Rectangle TransformToRectangle(Transform transform); // 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 @@ -218,376 +218,367 @@ PHYSACDEF void InitPhysics(Vector2 gravity) } // Update physic objects, calculating physic behaviours and collisions detection -PHYSACDEF void UpdatePhysics() +PHYSACDEF void UpdatePhysics(double deltaTime) { - // 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++) { - for (int i = 0; i < physicBodiesCount; i++) + if (physicBodies[i]->enabled) { - if (physicBodies[i]->enabled) + // Update physic behaviour + if (physicBodies[i]->rigidbody.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) { - // 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; + physicBodies[i]->rigidbody.velocity.x += gravityForce.x*deltaTime; + physicBodies[i]->rigidbody.velocity.y += gravityForce.y*deltaTime; } - // Update collision detection - if (physicBodies[i]->collider.enabled) + // 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++) { - // 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) { - 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) { - // 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: { - case COLLIDER_RECTANGLE: + switch (physicBodies[k]->collider.type) { - switch (physicBodies[k]->collider.type) + case COLLIDER_RECTANGLE: { - case COLLIDER_RECTANGLE: + // Check if colliders are overlapped + if (CheckCollisionRecs(physicBodies[i]->collider.bounds, physicBodies[k]->collider.bounds)) { - // 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 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); + // Calculate overlap on Y axis + overlap.y = (physicBodies[i]->transform.scale.y + physicBodies[k]->transform.scale.y)/2 - abs(direction.y); - // SAT test on X axis - if (overlap.x > 0.0f) + // SAT test on Y axis + if (overlap.y > 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 { - // 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; - } + // 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: + } + } break; + case COLLIDER_CIRCLE: + { + if (CheckCollisionCircleRec(physicBodies[k]->transform.position, physicBodies[k]->collider.radius, physicBodies[i]->collider.bounds)) { - 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)) { - // 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; + // 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); - 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; + // Calculate final contact normal + contactNormal.x = direction.x/distance; + contactNormal.y = -direction.y/distance; - // Check if the closest point is inside the circle - if (CheckCollisionPointCircle(closestPoint, physicBodies[k]->transform.position, physicBodies[k]->collider.radius)) + // Calculate penetration depth + penetrationDepth = physicBodies[k]->collider.radius - distance; + } + else + { + if (abs(direction.y) < abs(direction.x)) { - // 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; + 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 { - if (abs(direction.y) < abs(direction.x)) + // Calculate final contact normal + if (direction.x > 0.0f) { - // 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); - } + contactNormal = (Vector2){ 1.0f, 0.0f }; + penetrationDepth = fabs(physicBodies[k]->transform.position.x + physicBodies[k]->collider.radius - physicBodies[i]->collider.bounds.x); } - else + 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); - } + 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: + } + } break; + } + } break; + case COLLIDER_CIRCLE: + { + switch (physicBodies[k]->collider.type) { - switch (physicBodies[k]->collider.type) + case COLLIDER_RECTANGLE: { - case COLLIDER_RECTANGLE: + if (CheckCollisionCircleRec(physicBodies[i]->transform.position, physicBodies[i]->collider.radius, physicBodies[k]->collider.bounds)) { - 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)) { - // 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; + // 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); - 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; + // Calculate final contact normal + contactNormal.x = direction.x/distance; + contactNormal.y = -direction.y/distance; - // Check if the closest point is inside the circle - if (CheckCollisionPointCircle(closestPoint, physicBodies[i]->transform.position, physicBodies[i]->collider.radius)) + // Calculate penetration depth + penetrationDepth = physicBodies[k]->collider.radius - distance; + } + else + { + if (abs(direction.y) < abs(direction.x)) { - // 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; + 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 { - if (abs(direction.y) < abs(direction.x)) + // Calculate final contact normal and penetration depth + if (direction.x > 0.0f) { - // 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); - } + contactNormal = (Vector2){ 1.0f, 0.0f }; + penetrationDepth = fabs(physicBodies[i]->transform.position.x + physicBodies[i]->collider.radius - physicBodies[k]->collider.bounds.x); } - else + 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); - } + 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: + } + } 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)) { - // 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 + // 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; } - } break; - default: break; - } - } break; - default: break; - } + 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); - // Update rigidbody grounded state - if (physicBodies[i]->rigidbody.enabled) - { - if (contactNormal.y < 0.0f) physicBodies[i]->rigidbody.isGrounded = true; - } + // Calculate impulse scalar value + float j = -(1.0f + e)*velAlongNormal; + j /= 1.0f/physicBodies[i]->rigidbody.mass + 1.0f/physicBodies[k]->rigidbody.mass; - // 2. Calculate collision impulse - // ------------------------------------------------------------------------------------------------------------------------------------- + // Calculate final impulse vector + Vector2 impulse = { j*contactNormal.x, j*contactNormal.y }; - // 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 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 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 inverted mass ration + ratio = physicBodies[i]->rigidbody.mass/massSum; - // Calculate final impulse vector - Vector2 impulse = { j*contactNormal.x, j*contactNormal.y }; + // 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; - // Calculate collision mass ration - float massSum = physicBodies[i]->rigidbody.mass + physicBodies[k]->rigidbody.mass; - float ratio = 0.0f; + // 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; - // 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); - } + // Update collider bounds + physicBodies[i]->collider.bounds = TransformToRectangle(physicBodies[i]->transform); - if (physicBodies[k]->rigidbody.enabled) + 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; + 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[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); - } + physicBodies[k]->collider.bounds = TransformToRectangle(physicBodies[k]->transform); } } } -- cgit v1.2.3 From 5625c11e9982838498722c33d832289f3e79fa6e Mon Sep 17 00:00:00 2001 From: victorfisac Date: Sun, 12 Jun 2016 22:07:06 +0200 Subject: Added internal hi-resolution timer to physac... ... and now physac thread creation is done in InitPhysics() and it is destroyed in ClosePhysics(). User just need to call these functions to use physac module. --- src/physac.h | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index dd2b1628..ea8801c3 100644 --- a/src/physac.h +++ b/src/physac.h @@ -177,6 +177,18 @@ 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 +#include // Required for: pthread_create() +#include "utils.h" // Required for: TraceLog() + +#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 @@ -195,6 +207,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 +217,9 @@ static Vector2 gravityForce; // Gravity f //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- +static void* PhysicsThread(void *arg); // Physics calculations thread function +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,6 +233,10 @@ PHYSACDEF void InitPhysics(Vector2 gravity) // Initialize physics variables physicBodiesCount = 0; gravityForce = gravity; + + // Create physics thread + pthread_t tid; + pthread_create(&tid, NULL, &PhysicsThread, NULL); } // Update physic objects, calculating physic behaviours and collisions detection @@ -592,6 +614,9 @@ PHYSACDEF void UpdatePhysics(double deltaTime) // 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]); @@ -710,6 +735,65 @@ PHYSACDEF Rectangle TransformToRectangle(Transform transform) //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- +// Physics calculations thread function +static 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; +} + +// 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; + } + else TraceLog(WARNING, "No hi-resolution timer available"); +#endif + + previousTime = GetCurrentTime(); // Get time as double +} + +// Time measure returned are microseconds +static double GetCurrentTime(void) +{ +#if defined(PLATFORM_DESKTOP) + unsigned long long int clockFrequency, currentTime; + + QueryPerformanceFrequency(&clockFrequency); + QueryPerformanceCounter(¤tTime); + + return (double)(currentTime/clockFrequency); +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t time = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec; + + return (double)(time - baseTime)*1e-9; +#endif +} // Returns the dot product of two Vector2 static float Vector2DotProduct(Vector2 v1, Vector2 v2) -- cgit v1.2.3 From 54537e8f0b57df2f3f15d8e46309672f46e4775a Mon Sep 17 00:00:00 2001 From: victorfisac Date: Tue, 14 Jun 2016 20:23:46 +0200 Subject: Fixed bug in delta time calculation... and added PHYSAC_NO_THREADS define. Improved physac example drawing frames per second in screen. --- examples/physics_basic_rigidbody.c | 2 ++ examples/physics_forces.c | 4 +++- src/physac.h | 26 ++++++++++++++++---------- 3 files changed, 21 insertions(+), 11 deletions(-) (limited to 'src/physac.h') diff --git a/examples/physics_basic_rigidbody.c b/examples/physics_basic_rigidbody.c index 811ab982..5223f46a 100644 --- a/examples/physics_basic_rigidbody.c +++ b/examples/physics_basic_rigidbody.c @@ -110,6 +110,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(); //---------------------------------------------------------------------------------- } diff --git a/examples/physics_forces.c b/examples/physics_forces.c index 28566753..87510552 100644 --- a/examples/physics_forces.c +++ b/examples/physics_forces.c @@ -164,7 +164,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(); //---------------------------------------------------------------------------------- diff --git a/src/physac.h b/src/physac.h index ea8801c3..5ce3970e 100644 --- a/src/physac.h +++ b/src/physac.h @@ -178,8 +178,10 @@ PHYSACDEF Rectangle TransformToRectangle(Transform transform); #include // Required for: cos(), sin(), abs(), fminf() #include // Required for typedef unsigned long long int uint64_t, used by hi-res timer -#include // Required for: pthread_create() -#include "utils.h" // Required for: TraceLog() + +#ifndef PHYSAC_NO_THREADS + #include // Required for: pthread_create() +#endif #if defined(PLATFORM_DESKTOP) // Functions required to query time on Windows @@ -234,9 +236,11 @@ PHYSACDEF void InitPhysics(Vector2 gravity) physicBodiesCount = 0; gravityForce = gravity; - // Create physics thread - pthread_t tid; - pthread_create(&tid, NULL, &PhysicsThread, NULL); + #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 } // Update physic objects, calculating physic behaviours and collisions detection @@ -768,7 +772,6 @@ static void InitTimer(void) { baseTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; } - else TraceLog(WARNING, "No hi-resolution timer available"); #endif previousTime = GetCurrentTime(); // Get time as double @@ -777,22 +780,25 @@ static void InitTimer(void) // 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); - - return (double)(currentTime/clockFrequency); + #endif #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - uint64_t time = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec; + uint64_t temp = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec; - return (double)(time - baseTime)*1e-9; + time = (double)(temp - baseTime)*1e-9; #endif + + return time; } // Returns the dot product of two Vector2 -- cgit v1.2.3 From 5a1cbb2842f6801f7a86086e87f4821fd0b09229 Mon Sep 17 00:00:00 2001 From: victorfisac Date: Tue, 14 Jun 2016 20:25:08 +0200 Subject: Fix current time value --- src/physac.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index 5ce3970e..4f9b736f 100644 --- a/src/physac.h +++ b/src/physac.h @@ -788,6 +788,7 @@ static double GetCurrentTime(void) QueryPerformanceFrequency(&clockFrequency); QueryPerformanceCounter(¤tTime); + time = (double)((double)currentTime/(double)clockFrequency); #endif #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) -- cgit v1.2.3 From 4e84ded7ef3b165081e08a83d95bf54387a413ca Mon Sep 17 00:00:00 2001 From: victorfisac Date: Tue, 14 Jun 2016 20:38:49 +0200 Subject: Fixed spacing and set UpdatePhysics() function as static... and remove static from PhysicsThread(). --- examples/physics_basic_rigidbody.c | 8 +- examples/physics_forces.c | 2 +- src/physac.h | 418 ++++++++++++++++++------------------- 3 files changed, 213 insertions(+), 215 deletions(-) (limited to 'src/physac.h') diff --git a/examples/physics_basic_rigidbody.c b/examples/physics_basic_rigidbody.c index b85f7543..084bfb0e 100644 --- a/examples/physics_basic_rigidbody.c +++ b/examples/physics_basic_rigidbody.c @@ -21,7 +21,6 @@ #define MOVE_VELOCITY 5 #define JUMP_VELOCITY 30 - int main() { // Initialization @@ -30,7 +29,7 @@ int main() int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [physac] example - basic rigidbody"); - InitPhysics((Vector2){ 0.0f, -9.81f/2 }); // Initialize physics module + // InitPhysics((Vector2){ 0.0f, -9.81f/2 }); // Initialize physics module // Debug variables bool isDebug = false; @@ -64,8 +63,7 @@ int main() while (!WindowShouldClose()) // Detect window close button or ESC key { // Update - //---------------------------------------------------------------------------------- - + //---------------------------------------------------------------------------------- // Check rectangle movement inputs if (IsKeyPressed('W')) rectangle->rigidbody.velocity.y = JUMP_VELOCITY; if (IsKeyDown('A')) rectangle->rigidbody.velocity.x = -MOVE_VELOCITY; @@ -121,7 +119,7 @@ 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 7de85483..efe8e240 100644 --- a/examples/physics_forces.c +++ b/examples/physics_forces.c @@ -178,7 +178,7 @@ int main() } // De-Initialization - //-------------------------------------------------------------------------------------- + //-------------------------------------------------------------------------------------- ClosePhysics(); // Unitialize physics module CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/src/physac.h b/src/physac.h index 4f9b736f..b8cc8f15 100644 --- a/src/physac.h +++ b/src/physac.h @@ -146,7 +146,7 @@ typedef struct PhysicBodyData { // Module Functions Declaration //---------------------------------------------------------------------------------- PHYSACDEF void InitPhysics(Vector2 gravity); // Initializes pointers array (just pointers, fixed size) -PHYSACDEF void UpdatePhysics(double deltaTime); // 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 @@ -219,7 +219,7 @@ static Vector2 gravityForce; // Gravity f //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -static void* PhysicsThread(void *arg); // Physics calculations thread function +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 @@ -243,8 +243,214 @@ PHYSACDEF void InitPhysics(Vector2 gravity) #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]); + + // Reset enabled physic objects count + physicBodiesCount = 0; +} + +// Create a new physic body dinamically, initialize it and add to pool +PHYSACDEF PhysicBody CreatePhysicBody(Vector2 position, float rotation, Vector2 scale) +{ + // Allocate dynamic memory + PhysicBody obj = (PhysicBody)PHYSAC_MALLOC(sizeof(PhysicBodyData)); + + // Initialize physic body values with generic values + obj->id = physicBodiesCount; + obj->enabled = true; + + obj->transform = (Transform){ (Vector2){ position.x - scale.x/2, position.y - scale.y/2 }, rotation, scale }; + + obj->rigidbody.enabled = false; + obj->rigidbody.mass = 1.0f; + obj->rigidbody.acceleration = (Vector2){ 0.0f, 0.0f }; + obj->rigidbody.velocity = (Vector2){ 0.0f, 0.0f }; + obj->rigidbody.applyGravity = false; + obj->rigidbody.isGrounded = false; + obj->rigidbody.friction = 0.0f; + obj->rigidbody.bounciness = 0.0f; + + obj->collider.enabled = true; + obj->collider.type = COLLIDER_RECTANGLE; + obj->collider.bounds = TransformToRectangle(obj->transform); + obj->collider.radius = 0.0f; + + // Add new physic body to the pointers array + physicBodies[physicBodiesCount] = obj; + + // Increase enabled physic bodies count + physicBodiesCount++; + + return obj; +} + +// Destroy a specific physic body and take it out of the list +PHYSACDEF void DestroyPhysicBody(PhysicBody pbody) +{ + // Free dynamic memory allocation + PHYSAC_FREE(physicBodies[pbody->id]); + + // Remove *obj from the pointers array + for (int i = pbody->id; i < physicBodiesCount; i++) + { + // Resort all the following pointers of the array + if ((i + 1) < physicBodiesCount) + { + physicBodies[i] = physicBodies[i + 1]; + physicBodies[i]->id = physicBodies[i + 1]->id; + } + else PHYSAC_FREE(physicBodies[i]); + } + + // Decrease enabled physic bodies count + physicBodiesCount--; +} + +// Apply directional force to a physic body +PHYSACDEF void ApplyForce(PhysicBody pbody, Vector2 force) +{ + if (pbody->rigidbody.enabled) + { + pbody->rigidbody.velocity.x += force.x/pbody->rigidbody.mass; + pbody->rigidbody.velocity.y += force.y/pbody->rigidbody.mass; + } +} + +// Apply radial force to all physic objects in range +PHYSACDEF void ApplyForceAtPosition(Vector2 position, float force, float radius) +{ + for (int i = 0; i < physicBodiesCount; i++) + { + if (physicBodies[i]->rigidbody.enabled) + { + // Calculate direction and distance between force and physic body position + Vector2 distance = (Vector2){ physicBodies[i]->transform.position.x - position.x, physicBodies[i]->transform.position.y - position.y }; + + if (physicBodies[i]->collider.type == COLLIDER_RECTANGLE) + { + distance.x += physicBodies[i]->transform.scale.x/2; + distance.y += physicBodies[i]->transform.scale.y/2; + } + + float distanceLength = Vector2Length(distance); + + // Check if physic body is in force range + if (distanceLength <= radius) + { + // Normalize force direction + distance.x /= distanceLength; + distance.y /= -distanceLength; + + // Calculate final force + Vector2 finalForce = { distance.x*force, distance.y*force }; + + // Apply force to the physic body + ApplyForce(physicBodies[i], finalForce); + } + } + } +} + +// Convert Transform data type to Rectangle (position and scale) +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) +{ + float result; + + result = v1.x*v2.x + v1.y*v2.y; + + return result; +} + +static float Vector2Length(Vector2 v) +{ + float result; + + result = sqrt(v.x*v.x + v.y*v.y); + + return result; +} + // Update physic objects, calculating physic behaviours and collisions detection -PHYSACDEF void UpdatePhysics(double deltaTime) +static void UpdatePhysics(double deltaTime) { for (int i = 0; i < physicBodiesCount; i++) { @@ -615,210 +821,4 @@ PHYSACDEF void UpdatePhysics(double deltaTime) } } -// 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]); - - // Reset enabled physic objects count - physicBodiesCount = 0; -} - -// Create a new physic body dinamically, initialize it and add to pool -PHYSACDEF PhysicBody CreatePhysicBody(Vector2 position, float rotation, Vector2 scale) -{ - // Allocate dynamic memory - PhysicBody obj = (PhysicBody)PHYSAC_MALLOC(sizeof(PhysicBodyData)); - - // Initialize physic body values with generic values - obj->id = physicBodiesCount; - obj->enabled = true; - - obj->transform = (Transform){ (Vector2){ position.x - scale.x/2, position.y - scale.y/2 }, rotation, scale }; - - obj->rigidbody.enabled = false; - obj->rigidbody.mass = 1.0f; - obj->rigidbody.acceleration = (Vector2){ 0.0f, 0.0f }; - obj->rigidbody.velocity = (Vector2){ 0.0f, 0.0f }; - obj->rigidbody.applyGravity = false; - obj->rigidbody.isGrounded = false; - obj->rigidbody.friction = 0.0f; - obj->rigidbody.bounciness = 0.0f; - - obj->collider.enabled = true; - obj->collider.type = COLLIDER_RECTANGLE; - obj->collider.bounds = TransformToRectangle(obj->transform); - obj->collider.radius = 0.0f; - - // Add new physic body to the pointers array - physicBodies[physicBodiesCount] = obj; - - // Increase enabled physic bodies count - physicBodiesCount++; - - return obj; -} - -// Destroy a specific physic body and take it out of the list -PHYSACDEF void DestroyPhysicBody(PhysicBody pbody) -{ - // Free dynamic memory allocation - PHYSAC_FREE(physicBodies[pbody->id]); - - // Remove *obj from the pointers array - for (int i = pbody->id; i < physicBodiesCount; i++) - { - // Resort all the following pointers of the array - if ((i + 1) < physicBodiesCount) - { - physicBodies[i] = physicBodies[i + 1]; - physicBodies[i]->id = physicBodies[i + 1]->id; - } - else PHYSAC_FREE(physicBodies[i]); - } - - // Decrease enabled physic bodies count - physicBodiesCount--; -} - -// Apply directional force to a physic body -PHYSACDEF void ApplyForce(PhysicBody pbody, Vector2 force) -{ - if (pbody->rigidbody.enabled) - { - pbody->rigidbody.velocity.x += force.x/pbody->rigidbody.mass; - pbody->rigidbody.velocity.y += force.y/pbody->rigidbody.mass; - } -} - -// Apply radial force to all physic objects in range -PHYSACDEF void ApplyForceAtPosition(Vector2 position, float force, float radius) -{ - for (int i = 0; i < physicBodiesCount; i++) - { - if (physicBodies[i]->rigidbody.enabled) - { - // Calculate direction and distance between force and physic body position - Vector2 distance = (Vector2){ physicBodies[i]->transform.position.x - position.x, physicBodies[i]->transform.position.y - position.y }; - - if (physicBodies[i]->collider.type == COLLIDER_RECTANGLE) - { - distance.x += physicBodies[i]->transform.scale.x/2; - distance.y += physicBodies[i]->transform.scale.y/2; - } - - float distanceLength = Vector2Length(distance); - - // Check if physic body is in force range - if (distanceLength <= radius) - { - // Normalize force direction - distance.x /= distanceLength; - distance.y /= -distanceLength; - - // Calculate final force - Vector2 finalForce = { distance.x*force, distance.y*force }; - - // Apply force to the physic body - ApplyForce(physicBodies[i], finalForce); - } - } - } -} - -// Convert Transform data type to Rectangle (position and scale) -PHYSACDEF Rectangle TransformToRectangle(Transform transform) -{ - return (Rectangle){transform.position.x, transform.position.y, transform.scale.x, transform.scale.y}; -} - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- -// Physics calculations thread function -static 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; -} - -// 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) -{ - float result; - - result = v1.x*v2.x + v1.y*v2.y; - - return result; -} - -static float Vector2Length(Vector2 v) -{ - float result; - - result = sqrt(v.x*v.x + v.y*v.y); - - return result; -} - #endif // PHYSAC_IMPLEMENTATION \ No newline at end of file -- cgit v1.2.3 From 1b0996fb0bcf68e2a14bc6260c6f2c5366ab033f Mon Sep 17 00:00:00 2001 From: victorfisac Date: Tue, 14 Jun 2016 20:54:20 +0200 Subject: Updated physac header documentation --- src/physac.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index b8cc8f15..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. -- cgit v1.2.3 From aa9353feb4bab7fbbc058afdce634c0ae24e6110 Mon Sep 17 00:00:00 2001 From: victorfisac Date: Mon, 21 Nov 2016 20:30:46 +0100 Subject: Updated Physac library --- src/physac.h | 2529 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 1887 insertions(+), 642 deletions(-) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index dd4c4126..e807ffa6 100644 --- a/src/physac.h +++ b/src/physac.h @@ -1,8 +1,11 @@ /********************************************************************************************** * -* physac 1.0 - 2D Physics library for raylib (https://github.com/raysan5/raylib) +* Physac - 2D Physics library for videogames * -* // TODO: Description... +* Description: Physac is a small 2D physics engine written in pure C. The engine uses a fixed time-step thread loop +* to simluate physics. A physics step contains the following phases: get collision information, apply dynamics, +* collision solving and position correction. It uses a very simple struct for physic bodies with a position vector +* to be used in any 3D rendering API. * * CONFIGURATION: * @@ -24,30 +27,21 @@ * internally in the library and input management and drawing functions must be provided by * the user (check library implementation for further details). * +* #define PHYSAC_DEBUG +* Traces log messages when creating and destroying physics bodies and detects errors in physics +* calculations and reference exceptions; it is useful for debug purposes +* * #define PHYSAC_MALLOC() * #define PHYSAC_FREE() * You can define your own malloc/free implementation replacing stdlib.h malloc()/free() functions. * Otherwise it will include stdlib.h and use the C standard library malloc()/free() function. -* -* LIMITATIONS: -* -* - 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 (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. +* VERY THANKS TO: +* - Ramón Santamaria (@raysan5) * * LICENSE: zlib/libpng * -* Copyright (c) 2016 Victor Fisac (main developer) and Ramon Santamaria +* Copyright (c) 2016 Victor Fisac * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -66,18 +60,18 @@ * **********************************************************************************************/ -#ifndef PHYSAC_H +#if !defined(PHYSAC_H) #define PHYSAC_H -#if !defined(RAYGUI_STANDALONE) - #include "raylib.h" -#endif - #define PHYSAC_STATIC -#ifdef PHYSAC_STATIC +// #define PHYSAC_NO_THREADS +// #define PHYSAC_STANDALONE +// #define PHYSAC_DEBUG + +#if defined(PHYSAC_STATIC) #define PHYSACDEF static // Functions just visible to module including this file #else - #ifdef __cplusplus + #if defined(__cplusplus) #define PHYSACDEF extern "C" // Functions visible from other files (no name mangling of functions in C++) #else #define PHYSACDEF extern // Functions visible from other files @@ -87,87 +81,141 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -// ... +#define PHYSAC_MAX_BODIES 64 +#define PHYSAC_MAX_MANIFOLDS 4096 +#define PHYSAC_MAX_VERTICES 24 +#define PHYSAC_CIRCLE_VERTICES 24 + +#define PHYSAC_DESIRED_DELTATIME 1.0/60.0 +#define PHYSAC_MAX_TIMESTEP 0.02 +#define PHYSAC_COLLISION_ITERATIONS 100 +#define PHYSAC_PENETRATION_ALLOWANCE 0.05f +#define PHYSAC_PENETRATION_CORRECTION 0.4f + +#define PHYSAC_PI 3.14159265358979323846 +#define PHYSAC_DEG2RAD (PHYSAC_PI/180.0f) + +#define PHYSAC_MALLOC(size) malloc(size) +#define PHYSAC_FREE(ptr) free(ptr) //---------------------------------------------------------------------------------- // Types and Structures Definition // NOTE: Below types are required for PHYSAC_STANDALONE usage //---------------------------------------------------------------------------------- #if defined(PHYSAC_STANDALONE) - #ifndef __cplusplus - // Boolean type - #ifndef true - typedef enum { false, true } bool; - #endif - #endif - // Vector2 type typedef struct Vector2 { float x; float y; } Vector2; - // Rectangle type - typedef struct Rectangle { - int x; - int y; - int width; - int height; - } Rectangle; + // Boolean type + #if !defined(_STDBOOL_H) + typedef enum { false, true } bool; + #define _STDBOOL_H + #endif +#endif + +typedef enum PhysicsShapeType { PHYSICS_CIRCLE, PHYSICS_POLYGON } PhysicsShapeType; + +// Previously defined to be used in PhysicsShape struct as circular dependencies +typedef struct PhysicsBodyData *PhysicsBody; + +// Mat2 type (used for polygon shape rotation matrix) +typedef struct Mat2 +{ + float m00; + float m01; + float m10; + float m11; +} Mat2; + +typedef struct PolygonData { + unsigned int vertexCount; // Current used vertex and normals count + Vector2 vertices[PHYSAC_MAX_VERTICES]; // Polygon vertex positions vectors + Vector2 normals[PHYSAC_MAX_VERTICES]; // Polygon vertex normals vectors + Mat2 transform; // Vertices transform matrix 2x2 +} PolygonData; + +typedef struct PhysicsShape { + PhysicsShapeType type; // Physics shape type (circle or polygon) + PhysicsBody body; // Shape physics body reference + float radius; // Circle shape radius (used for circle shapes) + PolygonData vertexData; // Polygon shape vertices position and normals data (just used for polygon shapes) +} PhysicsShape; + +typedef struct PhysicsBodyData { + unsigned int id; // Reference unique identifier + bool enabled; // Enabled dynamics state (collisions are calculated anyway) + Vector2 position; // Physics body shape pivot + Vector2 velocity; // Current linear velocity applied to position + Vector2 force; // Current linear force (reset to 0 every step) + float angularVelocity; // Current angular velocity applied to orient + float torque; // Current angular force (reset to 0 every step) + float orient; // Rotation in radians + float inertia; // Moment of inertia + float inverseInertia; // Inverse value of inertia + float mass; // Physics body mass + float inverseMass; // Inverse value of mass + float staticFriction; // Friction when the body has not movement (0 to 1) + float dynamicFriction; // Friction when the body has movement (0 to 1) + float restitution; // Restitution coefficient of the body (0 to 1) + bool useGravity; // Apply gravity force to dynamics + bool isGrounded; // Physics grounded on other body state + bool freezeOrient; // Physics rotation constraint + PhysicsShape shape; // Physics body shape information (type, radius, vertices, normals) +} PhysicsBodyData; + +typedef struct PhysicsManifoldData { + unsigned int id; // Reference unique identifier + PhysicsBody bodyA; // Manifold first physics body reference + PhysicsBody bodyB; // Manifold second physics body reference + float penetration; // Depth of penetration from collision + Vector2 normal; // Normal direction vector from 'a' to 'b' + Vector2 contacts[2]; // Points of contact during collision + unsigned int contactsCount; // Current collision number of contacts + float restitution; // Mixed restitution during collision + float dynamicFriction; // Mixed dynamic friction during collision + float staticFriction; // Mixed static friction during collision +} PhysicsManifoldData, *PhysicsManifold; + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions #endif -typedef enum { COLLIDER_CIRCLE, COLLIDER_RECTANGLE } ColliderType; - -typedef struct Transform { - Vector2 position; - float rotation; // Radians (not used) - Vector2 scale; // Just for rectangle physic objects, for circle physic objects use collider radius and keep scale as { 0, 0 } -} Transform; - -typedef struct Rigidbody { - bool enabled; // Acts as kinematic state (collisions are calculated anyway) - float mass; - Vector2 acceleration; - Vector2 velocity; - bool applyGravity; - bool isGrounded; - float friction; // Normalized value - float bounciness; -} Rigidbody; - -typedef struct Collider { - bool enabled; - ColliderType type; - Rectangle bounds; // Used for COLLIDER_RECTANGLE - int radius; // Used for COLLIDER_CIRCLE -} Collider; - -typedef struct PhysicBodyData { - unsigned int id; - Transform transform; - Rigidbody rigidbody; - Collider collider; - bool enabled; -} PhysicBodyData, *PhysicBody; +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- -PHYSACDEF void InitPhysics(Vector2 gravity); // Initializes pointers array (just pointers, fixed size) -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 -PHYSACDEF void DestroyPhysicBody(PhysicBody pbody); // Destroy a specific physic body and take it out of the list +PHYSACDEF void InitPhysics(void); // Initializes physics values, pointers and creates physics loop thread +PHYSACDEF bool IsPhysicsEnabled(void); // Returns true if physics thread is currently enabled +PHYSACDEF void SetPhysicsGravity(float x, float y); // Sets physics global gravity force +PHYSACDEF PhysicsBody CreatePhysicsBodyCircle(Vector2 pos, float radius, float density); // Creates a new circle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyRectangle(Vector2 pos, float width, float height, float density); // Creates a new rectangle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyPolygon(Vector2 pos, float radius, int sides, float density); // Creates a new polygon physics body with generic parameters +PHYSACDEF void PhysicsAddForce(PhysicsBody body, Vector2 force); // Adds a force to a physics body +PHYSACDEF void PhysicsAddTorque(PhysicsBody body, float amount); // Adds an angular force to a physics body +PHYSACDEF void PhysicsShatter(PhysicsBody body, Vector2 position, float force); // Shatters a polygon shape physics body to little physics bodies with explosion force +PHYSACDEF int GetPhysicsBodiesCount(void); // Returns the current amount of created physics bodies +PHYSACDEF PhysicsBody GetPhysicsBody(int index); // Returns a physics body of the bodies pool at a specific index +PHYSACDEF int GetPhysicsShapeType(int index); // Returns the physics body shape type (PHYSICS_CIRCLE or PHYSICS_POLYGON) +PHYSACDEF int GetPhysicsShapeVerticesCount(int index); // Returns the amount of vertices of a physics body shape +PHYSACDEF Vector2 GetPhysicsShapeVertex(PhysicsBody body, int vertex); // Returns transformed position of a body shape (body position + vertex transformed position) +PHYSACDEF void SetPhysicsBodyRotation(PhysicsBody body, float radians); // Sets physics body shape transform based on radians parameter +PHYSACDEF void DestroyPhysicsBody(PhysicsBody body); // Unitializes and destroy a physics body +PHYSACDEF void ResetPhysics(void); // Destroys created physics bodies and manifolds and resets global values +PHYSACDEF void ClosePhysics(void); // Unitializes physics pointers and closes physics loop thread -PHYSACDEF void ApplyForce(PhysicBody pbody, Vector2 force); // Apply directional force to a physic body -PHYSACDEF void ApplyForceAtPosition(Vector2 position, float force, float radius); // Apply radial force to all physic objects in range - -PHYSACDEF Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale) +#if defined(__cplusplus) +} +#endif #endif // PHYSAC_H - /*********************************************************************************** * * PHYSAC IMPLEMENTATION @@ -176,657 +224,1854 @@ PHYSACDEF Rectangle TransformToRectangle(Transform transform); #if defined(PHYSAC_IMPLEMENTATION) -// Check if custom malloc/free functions defined, if not, using standard ones -#if !defined(PHYSAC_MALLOC) - #include // Required for: malloc(), free() - - #define PHYSAC_MALLOC(size) malloc(size) - #define PHYSAC_FREE(ptr) free(ptr) +#if !defined(PHYSAC_NO_THREADS) + #include // Required for: pthread_t, pthread_create() #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() +#if defined(PHYSAC_DEBUG) + #include // Required for: printf() #endif -#if defined(PLATFORM_DESKTOP) +#include // Required for: malloc(), free(), srand(), rand() +#include // Required for: cosf(), sinf(), fabs(), sqrtf() + +#if defined(_WIN32) // 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() +#elif defined(__linux) + #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_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 +#define min(a,b) (((a)<(b))?(a):(b)) +#define max(a,b) (((a)>(b))?(a):(b)) +#define PHYSAC_FLT_MAX 3.402823466e+38f +#define PHYSAC_EPSILON 0.000001f //---------------------------------------------------------------------------------- // Types and Structures Definition -// NOTE: Below types are required for PHYSAC_STANDALONE usage //---------------------------------------------------------------------------------- // ... //---------------------------------------------------------------------------------- // 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 +#if !defined(PHYSAC_NO_THREADS) + static pthread_t physicsThreadId; // Physics thread id +#endif +static unsigned int usedMemory = 0; // Total allocated dynamic memory +static bool physicsThreadEnabled = false; // Physics thread enabled state +static double currentTime = 0; // Current time in milliseconds +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + static double baseTime = 0; // Android and RPI platforms base time +#endif +static double startTime = 0; // Start time in milliseconds +static double deltaTime = 0; // Delta time used for physics steps +static double accumulator = 0; // Physics time step delta time accumulator +static unsigned int stepsCount = 0; // Total physics steps processed +static Vector2 gravityForce = { 0, 9.81f/1000 }; // Physics world gravity force +static PhysicsBody bodies[PHYSAC_MAX_BODIES]; // Physics bodies pointers array +static unsigned int physicsBodiesCount = 0; // Physics world current bodies counter +static PhysicsManifold contacts[PHYSAC_MAX_MANIFOLDS]; // Physics bodies pointers array +static unsigned int physicsManifoldsCount = 0; // Physics world current manifolds counter //---------------------------------------------------------------------------------- -// Module specific Functions Declaration +// Module Internal 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 +static PolygonData CreateRandomPolygon(float radius, int sides); // Creates a random polygon shape with max vertex distance from polygon pivot +static PolygonData CreateRectanglePolygon(Vector2 pos, Vector2 size); // Creates a rectangle polygon shape based on a min and max positions +static void *PhysicsLoop(void *arg); // Physics loop thread function +static void PhysicsStep(void); // Physics steps calculations (dynamics, collisions and position corrections) +static PhysicsManifold CreatePhysicsManifold(PhysicsBody a, PhysicsBody b); // Creates a new physics manifold to solve collision +static void DestroyPhysicsManifold(PhysicsManifold manifold); // Unitializes and destroys a physics manifold +static void SolvePhysicsManifold(PhysicsManifold manifold); // Solves a created physics manifold between two physics bodies +static void SolveCircleToCircle(PhysicsManifold manifold); // Solves collision between two circle shape physics bodies +static void SolveCircleToPolygon(PhysicsManifold manifold); // Solves collision between a circle to a polygon shape physics bodies +static void SolvePolygonToCircle(PhysicsManifold manifold); // Solves collision between a polygon to a circle shape physics bodies +static void SolvePolygonToPolygon(PhysicsManifold manifold); // Solves collision between two polygons shape physics bodies +static void IntegratePhysicsForces(PhysicsBody body); // Integrates physics forces into velocity +static void InitializePhysicsManifolds(PhysicsManifold manifold); // Initializes physics manifolds to solve collisions +static void IntegratePhysicsImpulses(PhysicsManifold manifold); // Integrates physics collisions impulses to solve collisions +static void IntegratePhysicsVelocity(PhysicsBody body); // Integrates physics velocity into position and forces +static void CorrectPhysicsPositions(PhysicsManifold manifold); // Corrects physics bodies positions based on manifolds collision information +static float FindAxisLeastPenetration(int *faceIndex, PhysicsShape shapeA, PhysicsShape shapeB); // Finds polygon shapes axis least penetration +static void FindIncidentFace(Vector2 *v0, Vector2 *v1, PhysicsShape ref, PhysicsShape inc, int index); // Finds two polygon shapes incident face +static int Clip(Vector2 normal, float clip, Vector2 *faceA, Vector2 *faceB); // Calculates clipping based on a normal and two faces +static bool BiasGreaterThan(float valueA, float valueB); // Check if values are between bias range +static Vector2 TriangleBarycenter(Vector2 v1, Vector2 v2, Vector2 v3); // Returns the barycenter of a triangle given by 3 points +static void InitTimer(void); // Initializes hi-resolution timer +static double GetCurrentTime(void); // Get current time in milliseconds +static int GetRandomNumber(int min, int max); // Returns a random number between min and max (both included) + +static void MathClamp(double *value, double min, double max); // Clamp a value in a range +static Vector2 MathCross(float value, Vector2 vector); // Returns the cross product of a vector and a value +static float MathCrossVector2(Vector2 v1, Vector2 v2); // Returns the cross product of two vectors +static float MathLenSqr(Vector2 vector); // Returns the len square root of a vector +static float MathDot(Vector2 v1, Vector2 v2); // Returns the dot product of two vectors +static inline float DistSqr(Vector2 v1, Vector2 v2); // Returns the square root of distance between two vectors +static void MathNormalize(Vector2 *vector); // Returns the normalized values of a vector +static Vector2 Vector2Add(Vector2 v1, Vector2 v2); // Returns the sum of two given vectors +static Vector2 Vector2Subtract(Vector2 v1, Vector2 v2); // Returns the subtract of two given vectors + +static Mat2 Mat2Radians(float radians); // Creates a matrix 2x2 from a given radians value +static void Mat2Set(Mat2 *matrix, float radians); // Set values from radians to a created matrix 2x2 +static Mat2 Mat2Transpose(Mat2 matrix); // Returns the transpose of a given matrix 2x2 +static Vector2 Mat2MultiplyVector2(Mat2 matrix, Vector2 vector); // Multiplies a vector by a matrix 2x2 //---------------------------------------------------------------------------------- // Module Functions Definition //---------------------------------------------------------------------------------- +// Initializes physics values, pointers and creates physics loop thread +PHYSACDEF void InitPhysics(void) +{ + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] physics module initialized successfully\n"); + #endif -// Initializes pointers array (just pointers, fixed size) -PHYSACDEF void InitPhysics(Vector2 gravity) -{ - // Initialize physics variables - physicBodiesCount = 0; - gravityForce = gravity; - - #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); + #if !defined(PHYSAC_NO_THREADS) + // NOTE: if defined, user will need to create a thread for PhysicsThread function manually + // Create physics thread using POSIXS thread libraries + pthread_create(&physicsThreadId, NULL, &PhysicsLoop, NULL); #endif } -// Unitialize all physic objects and empty the objects pool -PHYSACDEF void ClosePhysics() +// Returns true if physics thread is currently enabled +PHYSACDEF bool IsPhysicsEnabled(void) { - // Exit physics thread loop - physicsThreadEnabled = false; - - // Free all dynamic memory allocations - for (int i = 0; i < physicBodiesCount; i++) PHYSAC_FREE(physicBodies[i]); - - // Reset enabled physic objects count - physicBodiesCount = 0; + return physicsThreadEnabled; +} + +// Sets physics global gravity force +PHYSACDEF void SetPhysicsGravity(float x, float y) +{ + gravityForce.x = x; + gravityForce.y = y; } -// Create a new physic body dinamically, initialize it and add to pool -PHYSACDEF PhysicBody CreatePhysicBody(Vector2 position, float rotation, Vector2 scale) +// Creates a new circle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyCircle(Vector2 pos, float radius, float density) { - // Allocate dynamic memory - PhysicBody obj = (PhysicBody)PHYSAC_MALLOC(sizeof(PhysicBodyData)); - - // Initialize physic body values with generic values - obj->id = physicBodiesCount; - obj->enabled = true; - - obj->transform = (Transform){ (Vector2){ position.x - scale.x/2, position.y - scale.y/2 }, rotation, scale }; - - obj->rigidbody.enabled = false; - obj->rigidbody.mass = 1.0f; - obj->rigidbody.acceleration = (Vector2){ 0.0f, 0.0f }; - obj->rigidbody.velocity = (Vector2){ 0.0f, 0.0f }; - obj->rigidbody.applyGravity = false; - obj->rigidbody.isGrounded = false; - obj->rigidbody.friction = 0.0f; - obj->rigidbody.bounciness = 0.0f; - - obj->collider.enabled = true; - obj->collider.type = COLLIDER_RECTANGLE; - obj->collider.bounds = TransformToRectangle(obj->transform); - obj->collider.radius = 0.0f; - - // Add new physic body to the pointers array - physicBodies[physicBodiesCount] = obj; - - // Increase enabled physic bodies count - physicBodiesCount++; - - return obj; + PhysicsBody newBody = CreatePhysicsBodyPolygon(pos, radius, PHYSAC_CIRCLE_VERTICES, density); + return newBody; + + /*PhysicsBody newBody = (PhysicsBody)PHYSAC_MALLOC(sizeof(PhysicsBodyData)); + usedMemory += sizeof(PhysicsBodyData); + + int newId = -1; + for (int i = 0; i < PHYSAC_MAX_BODIES; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (int k = 0; k < physicsBodiesCount; k++) + { + if (bodies[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == i) + { + newId = i; + break; + } + } + + if (newId != -1) + { + // Initialize new body with generic values + newBody->id = newId; + newBody->enabled = true; + newBody->position = pos; + newBody->velocity = (Vector2){ 0 }; + newBody->force = (Vector2){ 0 }; + newBody->angularVelocity = 0; + newBody->torque = 0; + newBody->orient = 0; + newBody->mass = PHYSAC_PI*radius*radius*density; + newBody->inverseMass = ((newBody->mass != 0.0f) ? 1.0f/newBody->mass : 0.0f); + newBody->inertia = newBody->mass*radius*radius; + newBody->inverseInertia = ((newBody->inertia != 0.0f) ? 1.0f/newBody->inertia : 0.0f); + newBody->staticFriction = 0; + newBody->dynamicFriction = 0; + newBody->restitution = 0; + newBody->useGravity = true; + newBody->freezeOrient = false; + newBody->shape.type = PHYSICS_CIRCLE; + newBody->shape.body = newBody; + newBody->shape.radius = radius; + + // Add new body to bodies pointers array and update bodies count + bodies[physicsBodiesCount] = newBody; + physicsBodiesCount++; + + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] created circle physics body id %i\n", newBody->id); + #endif + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] new physics body creation failed because there is any available id to use\n"); + #endif + + return newBody;*/ } -// Destroy a specific physic body and take it out of the list -PHYSACDEF void DestroyPhysicBody(PhysicBody pbody) +// Creates a new rectangle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyRectangle(Vector2 pos, float width, float height, float density) { - // Free dynamic memory allocation - PHYSAC_FREE(physicBodies[pbody->id]); - - // Remove *obj from the pointers array - for (int i = pbody->id; i < physicBodiesCount; i++) + PhysicsBody newBody = (PhysicsBody)PHYSAC_MALLOC(sizeof(PhysicsBodyData)); + usedMemory += sizeof(PhysicsBodyData); + + int newId = -1; + for (int i = 0; i < PHYSAC_MAX_BODIES; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (int k = 0; k < physicsBodiesCount; k++) + { + if (bodies[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == i) + { + newId = i; + break; + } + } + + if (newId != -1) { - // Resort all the following pointers of the array - if ((i + 1) < physicBodiesCount) + // Initialize new body with generic values + newBody->id = newId; + newBody->enabled = true; + newBody->position = pos; + newBody->velocity = (Vector2){ 0 }; + newBody->force = (Vector2){ 0 }; + newBody->angularVelocity = 0; + newBody->torque = 0; + newBody->orient = 0; + newBody->shape.type = PHYSICS_POLYGON; + newBody->shape.body = newBody; + newBody->shape.vertexData = CreateRectanglePolygon(pos, (Vector2){ width, height }); + + // Calculate centroid and moment of inertia + Vector2 center = { 0 }; + float area = 0; + float inertia = 0; + const float k = 1.0f/3.0f; + + for (int i = 0; i < newBody->shape.vertexData.vertexCount; i++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 p1 = newBody->shape.vertexData.vertices[i]; + int nextIndex = (((i + 1) < newBody->shape.vertexData.vertexCount) ? (i + 1) : 0); + Vector2 p2 = newBody->shape.vertexData.vertices[nextIndex]; + + float D = MathCrossVector2(p1, p2); + float triangleArea = D/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*k*(p1.x + p2.x); + center.y += triangleArea*k*(p1.y + p2.y); + + float intx2 = p1.x*p1.x + p2.x*p1.x + p2.x*p2.x; + float inty2 = p1.y*p1.y + p2.y*p1.y + p2.y*p2.y; + inertia += (0.25f*k*D)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + // Translate vertices to centroid (make the centroid (0, 0) for the polygon in model space) + // Note: this is not really necessary + for (int i = 0; i < newBody->shape.vertexData.vertexCount; i++) { - physicBodies[i] = physicBodies[i + 1]; - physicBodies[i]->id = physicBodies[i + 1]->id; + newBody->shape.vertexData.vertices[i].x -= center.x; + newBody->shape.vertexData.vertices[i].y -= center.y; } - else PHYSAC_FREE(physicBodies[i]); + + newBody->mass = density*area; + newBody->inverseMass = ((newBody->mass != 0.0f) ? 1.0f/newBody->mass : 0.0f); + newBody->inertia = density*inertia; + newBody->inverseInertia = ((newBody->inertia != 0.0f) ? 1.0f/newBody->inertia : 0.0f); + newBody->staticFriction = 0.4f; + newBody->dynamicFriction = 0.2f; + newBody->restitution = 0; + newBody->useGravity = true; + newBody->isGrounded = false; + newBody->freezeOrient = false; + + // Add new body to bodies pointers array and update bodies count + bodies[physicsBodiesCount] = newBody; + physicsBodiesCount++; + + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] created polygon physics body id %i\n", newBody->id); + #endif } - - // Decrease enabled physic bodies count - physicBodiesCount--; + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] new physics body creation failed because there is any available id to use\n"); + #endif + + return newBody; } -// Apply directional force to a physic body -PHYSACDEF void ApplyForce(PhysicBody pbody, Vector2 force) +// Creates a new polygon physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyPolygon(Vector2 pos, float radius, int sides, float density) { - if (pbody->rigidbody.enabled) + PhysicsBody newBody = (PhysicsBody)PHYSAC_MALLOC(sizeof(PhysicsBodyData)); + usedMemory += sizeof(PhysicsBodyData); + + int newId = -1; + for (int i = 0; i < PHYSAC_MAX_BODIES; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (int k = 0; k < physicsBodiesCount; k++) + { + if (bodies[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == i) + { + newId = i; + break; + } + } + + if (newId != -1) { - pbody->rigidbody.velocity.x += force.x/pbody->rigidbody.mass; - pbody->rigidbody.velocity.y += force.y/pbody->rigidbody.mass; + // Initialize new body with generic values + newBody->id = newId; + newBody->enabled = true; + newBody->position = pos; + newBody->velocity = (Vector2){ 0 }; + newBody->force = (Vector2){ 0 }; + newBody->angularVelocity = 0; + newBody->torque = 0; + newBody->orient = 0; + newBody->shape.type = PHYSICS_POLYGON; + newBody->shape.body = newBody; + newBody->shape.vertexData = CreateRandomPolygon(radius, sides); + + // Calculate centroid and moment of inertia + Vector2 center = { 0 }; + float area = 0; + float inertia = 0; + const float alpha = 1.0f/3.0f; + + for (int i = 0; i < newBody->shape.vertexData.vertexCount; i++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 position1 = newBody->shape.vertexData.vertices[i]; + int nextIndex = (((i + 1) < newBody->shape.vertexData.vertexCount) ? (i + 1) : 0); + Vector2 position2 = newBody->shape.vertexData.vertices[nextIndex]; + + float cross = MathCrossVector2(position1, position2); + float triangleArea = cross/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*alpha*(position1.x + position2.x); + center.y += triangleArea*alpha*(position1.y + position2.y); + + float intx2 = position1.x*position1.x + position2.x*position1.x + position2.x*position2.x; + float inty2 = position1.y*position1.y + position2.y*position1.y + position2.y*position2.y; + inertia += (0.25f*alpha*cross)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + // Translate vertices to centroid (make the centroid (0, 0) for the polygon in model space) + // Note: this is not really necessary + for (int i = 0; i < newBody->shape.vertexData.vertexCount; i++) + { + newBody->shape.vertexData.vertices[i].x -= center.x; + newBody->shape.vertexData.vertices[i].y -= center.y; + } + + newBody->mass = density*area; + newBody->inverseMass = ((newBody->mass != 0.0f) ? 1.0f/newBody->mass : 0.0f); + newBody->inertia = density*inertia; + newBody->inverseInertia = ((newBody->inertia != 0.0f) ? 1.0f/newBody->inertia : 0.0f); + newBody->staticFriction = 0.4f; + newBody->dynamicFriction = 0.2f; + newBody->restitution = 0; + newBody->useGravity = true; + newBody->isGrounded = false; + newBody->freezeOrient = false; + + // Add new body to bodies pointers array and update bodies count + bodies[physicsBodiesCount] = newBody; + physicsBodiesCount++; + + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] created polygon physics body id %i\n", newBody->id); + #endif } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] new physics body creation failed because there is any available id to use\n"); + #endif + + return newBody; +} + +// Adds a force to a physics body +PHYSACDEF void PhysicsAddForce(PhysicsBody body, Vector2 force) +{ + if (body != NULL) body->force = Vector2Add(body->force, force); +} + +// Adds an angular force to a physics body +PHYSACDEF void PhysicsAddTorque(PhysicsBody body, float amount) +{ + if (body != NULL) body->torque += amount; } -// Apply radial force to all physic objects in range -PHYSACDEF void ApplyForceAtPosition(Vector2 position, float force, float radius) +// Shatters a polygon shape physics body to little physics bodies with explosion force +PHYSACDEF void PhysicsShatter(PhysicsBody body, Vector2 position, float force) { - for (int i = 0; i < physicBodiesCount; i++) + if (body != NULL) { - if (physicBodies[i]->rigidbody.enabled) + if (body->shape.type == PHYSICS_POLYGON) { - // Calculate direction and distance between force and physic body position - Vector2 distance = (Vector2){ physicBodies[i]->transform.position.x - position.x, physicBodies[i]->transform.position.y - position.y }; + PolygonData vertexData = body->shape.vertexData; + bool collision = false; - if (physicBodies[i]->collider.type == COLLIDER_RECTANGLE) + for (int i = 0; i < vertexData.vertexCount; i++) { - distance.x += physicBodies[i]->transform.scale.x/2; - distance.y += physicBodies[i]->transform.scale.y/2; + Vector2 positionA = body->position; + Vector2 positionB = Mat2MultiplyVector2(vertexData.transform, Vector2Add(body->position, vertexData.vertices[i])); + int nextIndex = (((i + 1) < vertexData.vertexCount) ? (i + 1) : 0); + Vector2 positionC = Mat2MultiplyVector2(vertexData.transform, Vector2Add(body->position, vertexData.vertices[nextIndex])); + + // Check collision between each triangle + float alpha = ((positionB.y - positionC.y)*(position.x - positionC.x) + (positionC.x - positionB.x)*(position.y - positionC.y))/ + ((positionB.y - positionC.y)*(positionA.x - positionC.x) + (positionC.x - positionB.x)*(positionA.y - positionC.y)); + + float beta = ((positionC.y - positionA.y)*(position.x - positionC.x) + (positionA.x - positionC.x)*(position.y - positionC.y))/ + ((positionB.y - positionC.y)*(positionA.x - positionC.x) + (positionC.x - positionB.x)*(positionA.y - positionC.y)); + + float gamma = 1.0f - alpha - beta; + + if ((alpha > 0) && (beta > 0) & (gamma > 0)) + { + collision = true; + break; + } } - - float distanceLength = Vector2Length(distance); - - // Check if physic body is in force range - if (distanceLength <= radius) + + if (collision) { - // Normalize force direction - distance.x /= distanceLength; - distance.y /= -distanceLength; - - // Calculate final force - Vector2 finalForce = { distance.x*force, distance.y*force }; - - // Apply force to the physic body - ApplyForce(physicBodies[i], finalForce); + int count = vertexData.vertexCount; + Vector2 bodyPos = body->position; + Vector2 vertices[count]; + Mat2 trans = vertexData.transform; + for (int i = 0; i < count; i++) vertices[i] = vertexData.vertices[i]; + + // Destroy shattered physics body + DestroyPhysicsBody(body); + + for (int i = 0; i < count; i++) + { + int nextIndex = (((i + 1) < count) ? (i + 1) : 0); + Vector2 center = TriangleBarycenter(vertices[i], vertices[nextIndex], (Vector2){ 0, 0 }); + center = Vector2Add(bodyPos, center); + Vector2 offset = Vector2Subtract(center, bodyPos); + + PhysicsBody newBody = CreatePhysicsBodyPolygon(center, 10, 3, 10); // Create polygon physics body with relevant values + + PolygonData newData = { 0 }; + newData.vertexCount = 3; + newData.transform = trans; + + newData.vertices[0] = Vector2Subtract(vertices[i], offset); + newData.vertices[1] = Vector2Subtract(vertices[nextIndex], offset); + newData.vertices[2] = Vector2Subtract(position, center); + + // Separate vertices to avoid unnecessary physics collisions + newData.vertices[0].x *= 0.95f; + newData.vertices[0].y *= 0.95f; + newData.vertices[1].x *= 0.95f; + newData.vertices[1].y *= 0.95f; + newData.vertices[2].x *= 0.95f; + newData.vertices[2].y *= 0.95f; + + // Calculate polygon faces normals + for (int j = 0; j < newData.vertexCount; j++) + { + int nextVertex = (((j + 1) < newData.vertexCount) ? (j + 1) : 0); + Vector2 face = Vector2Subtract(newData.vertices[nextVertex], newData.vertices[j]); + + newData.normals[j] = (Vector2){ face.y, -face.x }; + MathNormalize(&newData.normals[j]); + } + + // Apply computed vertex data to new physics body shape + newBody->shape.vertexData = newData; + + // Calculate centroid and moment of inertia + center = (Vector2){ 0 }; + float area = 0; + float inertia = 0; + const float k = 1.0f/3.0f; + + for (int j = 0; j < newBody->shape.vertexData.vertexCount; j++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 p1 = newBody->shape.vertexData.vertices[j]; + int nextVertex = (((j + 1) < newBody->shape.vertexData.vertexCount) ? (j + 1) : 0); + Vector2 p2 = newBody->shape.vertexData.vertices[nextVertex]; + + float D = MathCrossVector2(p1, p2); + float triangleArea = D/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*k*(p1.x + p2.x); + center.y += triangleArea*k*(p1.y + p2.y); + + float intx2 = p1.x*p1.x + p2.x*p1.x + p2.x*p2.x; + float inty2 = p1.y*p1.y + p2.y*p1.y + p2.y*p2.y; + inertia += (0.25f*k*D)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + newBody->mass = area; + newBody->inverseMass = ((newBody->mass != 0.0f) ? 1.0f/newBody->mass : 0.0f); + newBody->inertia = inertia; + newBody->inverseInertia = ((newBody->inertia != 0.0f) ? 1.0f/newBody->inertia : 0.0f); + + // Calculate explosion force direction + Vector2 pointA = newBody->position; + Vector2 pointB = Vector2Subtract(newData.vertices[1], newData.vertices[0]); + pointB.x /= 2; + pointB.y /= 2; + Vector2 forceDirection = Vector2Subtract(Vector2Add(pointA, Vector2Add(newData.vertices[0], pointB)), newBody->position); + MathNormalize(&forceDirection); + forceDirection.x *= force; + forceDirection.y *= force; + + // Apply force to new physics body + PhysicsAddForce(newBody, forceDirection); + } } } } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] error when trying to shatter a null reference physics body"); + #endif } -// Convert Transform data type to Rectangle (position and scale) -PHYSACDEF Rectangle TransformToRectangle(Transform transform) +// Returns the current amount of created physics bodies +PHYSACDEF int GetPhysicsBodiesCount(void) { - return (Rectangle){transform.position.x, transform.position.y, transform.scale.x, transform.scale.y}; + return physicsBodiesCount; } -// Physics calculations thread function -PHYSACDEF void* PhysicsThread(void *arg) +// Returns a physics body of the bodies pool at a specific index +PHYSACDEF PhysicsBody GetPhysicsBody(int index) { - // Initialize thread loop state - physicsThreadEnabled = true; - - // Initialize hi-resolution timer - InitTimer(); - - // Physics update loop - while (physicsThreadEnabled) + if (index < physicsBodiesCount) { - currentTime = GetCurrentTime(); - double deltaTime = (double)(currentTime - previousTime); - previousTime = currentTime; + PhysicsBody body = bodies[index]; + if (body != NULL) return body; + else + { + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] error when trying to get a null reference physics body"); + #endif - // Delta time value needs to be inverse multiplied by physics time step value (1/target fps) - UpdatePhysics(deltaTime/PHYSICS_TIMESTEP); + return NULL; + } } - - return NULL; + #if defined(PHYSAC_DEBUG) + else + { + printf("[PHYSAC] physics body index is out of bounds"); + return NULL; + } + #endif } -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- -// Initialize hi-resolution timer -static void InitTimer(void) +// Returns the physics body shape type (PHYSICS_CIRCLE or PHYSICS_POLYGON) +PHYSACDEF int GetPhysicsShapeType(int index) { -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) - struct timespec now; - - if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success + if (index < physicsBodiesCount) { - baseTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; + PhysicsBody body = bodies[index]; + if (body != NULL) return body->shape.type; + #if defined(PHYSAC_DEBUG) + else + { + printf("[PHYSAC] error when trying to get a null reference physics body"); + return -1; + } + #endif } -#endif - - previousTime = GetCurrentTime(); // Get time as double + #if defined(PHYSAC_DEBUG) + else + { + printf("[PHYSAC] physics body index is out of bounds"); + return -1; + } + #endif } -// Time measure returned are microseconds -static double GetCurrentTime(void) +// Returns the amount of vertices of a physics body shape +PHYSACDEF int GetPhysicsShapeVerticesCount(int index) { - 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; + if (index < physicsBodiesCount) + { + PhysicsBody body = bodies[index]; + if (body != NULL) + { + switch (body->shape.type) + { + case PHYSICS_CIRCLE: return PHYSAC_CIRCLE_VERTICES; break; + case PHYSICS_POLYGON: return body->shape.vertexData.vertexCount; break; + default: break; + } + } + #if defined(PHYSAC_DEBUG) + else + { + printf("[PHYSAC] error when trying to get a null reference physics body"); + return 0; + } + #endif + } + #if defined(PHYSAC_DEBUG) + else + { + printf("[PHYSAC] physics body index is out of bounds"); + return 0; + } + #endif } -// Returns the dot product of two Vector2 -static float Vector2DotProduct(Vector2 v1, Vector2 v2) +// Returns transformed position of a body shape (body position + vertex transformed position) +PHYSACDEF Vector2 GetPhysicsShapeVertex(PhysicsBody body, int vertex) { - float result; + Vector2 position = { 0 }; - result = v1.x*v2.x + v1.y*v2.y; + if (body != NULL) + { + switch (body->shape.type) + { + case PHYSICS_CIRCLE: + { + position.x = body->position.x + cosf(360/PHYSAC_CIRCLE_VERTICES*vertex*PHYSAC_DEG2RAD)*body->shape.radius; + position.y = body->position.y + sinf(360/PHYSAC_CIRCLE_VERTICES*vertex*PHYSAC_DEG2RAD)*body->shape.radius; + } break; + case PHYSICS_POLYGON: + { + PolygonData vertexData = body->shape.vertexData; + position = Vector2Add(body->position, Mat2MultiplyVector2(vertexData.transform, vertexData.vertices[vertex])); + } break; + default: break; + } + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] error when trying to get a null reference physics body"); + #endif - return result; + return position; } -static float Vector2Length(Vector2 v) +// Sets physics body shape transform based on radians parameter +PHYSACDEF void SetPhysicsBodyRotation(PhysicsBody body, float radians) { - float result; - - result = sqrt(v.x*v.x + v.y*v.y); - - return result; + if (body != NULL) + { + body->orient = radians; + + if (body->shape.type == PHYSICS_POLYGON) body->shape.vertexData.transform = Mat2Radians(radians); + } } -// Update physic objects, calculating physic behaviours and collisions detection -static void UpdatePhysics(double deltaTime) +// Unitializes and destroys a physics body +PHYSACDEF void DestroyPhysicsBody(PhysicsBody body) { - for (int i = 0; i < physicBodiesCount; i++) + if (body != NULL) { - if (physicBodies[i]->enabled) + int id = body->id; + int index = -1; + + for (int i = 0; i < physicsBodiesCount; i++) { - // 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) + if (bodies[i]->id == id) { - // 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); - } - } - } - } - } + index = i; + break; } } + + #if defined(PHYSAC_DEBUG) + if (index == -1) printf("[PHYSAC] cannot find body id %i in pointers array\n", id); + #endif + + // Free body allocated memory + PHYSAC_FREE(bodies[index]); + usedMemory -= sizeof(PhysicsBodyData); + bodies[index] = NULL; + + // Reorder physics bodies pointers array and its catched index + for (int i = index; i < physicsBodiesCount; i++) + { + if ((i + 1) < physicsBodiesCount) bodies[i] = bodies[i + 1]; + } + + // Update physics bodies count + physicsBodiesCount--; + + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] destroyed physics body id %i\n", id); + #endif + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] error trying to destroy a null referenced body\n"); + #endif +} + +// Destroys created physics bodies and manifolds and resets global values +PHYSACDEF void ResetPhysics(void) +{ + // Unitialize physics bodies dynamic memory allocations + for (int i = physicsBodiesCount - 1; i >= 0; i--) + { + PhysicsBody body = bodies[i]; + + if (body != NULL) + { + PHYSAC_FREE(body); + body = NULL; + usedMemory -= sizeof(PhysicsBodyData); + } + } + + physicsBodiesCount = 0; + + // Unitialize physics manifolds dynamic memory allocations + for (int i = physicsManifoldsCount - 1; i >= 0; i--) + { + PhysicsManifold manifold = contacts[i]; + + if (manifold != NULL) + { + PHYSAC_FREE(manifold); + manifold = NULL; + usedMemory -= sizeof(PhysicsManifoldData); + } } + + physicsManifoldsCount = 0; + + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] physics module reset successfully\n"); + #endif +} + +// Unitializes physics pointers and exits physics loop thread +PHYSACDEF void ClosePhysics(void) +{ + // Exit physics loop thread + physicsThreadEnabled = false; + + #if !defined(PHYSAC_NO_THREADS) + pthread_join(physicsThreadId, NULL); + #endif +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +// Creates a random polygon shape with max vertex distance from polygon pivot +static PolygonData CreateRandomPolygon(float radius, int sides) +{ + PolygonData data = { 0 }; + data.vertexCount = sides; + + float orient = GetRandomNumber(0, 360); + data.transform = Mat2Radians(orient*PHYSAC_DEG2RAD); + + // Calculate polygon vertices positions + for (int i = 0; i < data.vertexCount; i++) + { + data.vertices[i].x = cosf(360/sides*i*PHYSAC_DEG2RAD)*radius; + data.vertices[i].y = sinf(360/sides*i*PHYSAC_DEG2RAD)*radius; + } + + // Calculate polygon faces normals + for (int i = 0; i < data.vertexCount; i++) + { + int nextIndex = (((i + 1) < sides) ? (i + 1) : 0); + Vector2 face = Vector2Subtract(data.vertices[nextIndex], data.vertices[i]); + + data.normals[i] = (Vector2){ face.y, -face.x }; + MathNormalize(&data.normals[i]); + } + + return data; +} + +// Creates a rectangle polygon shape based on a min and max positions +static PolygonData CreateRectanglePolygon(Vector2 pos, Vector2 size) +{ + PolygonData data = { 0 }; + + data.vertexCount = 4; + data.transform = Mat2Radians(0); + + // Calculate polygon vertices positions + data.vertices[0] = (Vector2){ pos.x + size.x/2, pos.y - size.y/2 }; + data.vertices[1] = (Vector2){ pos.x + size.x/2, pos.y + size.y/2 }; + data.vertices[2] = (Vector2){ pos.x - size.x/2, pos.y + size.y/2 }; + data.vertices[3] = (Vector2){ pos.x - size.x/2, pos.y - size.y/2 }; + + // Calculate polygon faces normals + for (int i = 0; i < data.vertexCount; i++) + { + int nextIndex = (((i + 1) < data.vertexCount) ? (i + 1) : 0); + Vector2 face = Vector2Subtract(data.vertices[nextIndex], data.vertices[i]); + + data.normals[i] = (Vector2){ face.y, -face.x }; + MathNormalize(&data.normals[i]); + } + + return data; +} + +// Physics loop thread function +static void *PhysicsLoop(void *arg) +{ + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] physics thread created with successfully\n"); + #endif + + // Initialize physics loop thread values + physicsThreadEnabled = true; + accumulator = 0; + + // Initialize high resolution timer + InitTimer(); + + // Physics update loop + while (physicsThreadEnabled) + { + // Calculate current time + currentTime = GetCurrentTime(); + + // Calculate current delta time + deltaTime = currentTime - startTime; + + // Store the time elapsed since the last frame began + accumulator += deltaTime; + + // Clamp accumulator to max time step to avoid bad performance + MathClamp(&accumulator, 0, PHYSAC_MAX_TIMESTEP); + + // Fixed time stepping loop + while (accumulator >= PHYSAC_DESIRED_DELTATIME) + { + PhysicsStep(); + accumulator -= deltaTime; + } + + // Record the starting of this frame + startTime = currentTime; + } + + // Unitialize physics manifolds dynamic memory allocations + for (int i = physicsManifoldsCount - 1; i >= 0; i--) DestroyPhysicsManifold(contacts[i]); + + // Unitialize physics bodies dynamic memory allocations + for (int i = physicsBodiesCount - 1; i >= 0; i--) DestroyPhysicsBody(bodies[i]); + + #if defined(PHYSAC_DEBUG) + if (physicsBodiesCount > 0 || usedMemory != 0) printf("[PHYSAC] physics module closed with %i still allocated bodies [MEMORY: %i bytes]\n", physicsBodiesCount, usedMemory); + else if (physicsManifoldsCount > 0 || usedMemory != 0) printf("[PHYSAC] physics module closed with %i still allocated manifolds [MEMORY: %i bytes]\n", physicsManifoldsCount, usedMemory); + else printf("[PHYSAC] physics module closed successfully\n"); + #endif + + return NULL; +} + +// Physics steps calculations (dynamics, collisions and position corrections) +static void PhysicsStep(void) +{ + stepsCount++; + + // Clear previous generated collisions information + for (int i = physicsManifoldsCount - 1; i >= 0; i--) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) DestroyPhysicsManifold(manifold); + } + + // Generate new collision information + for (int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody bodyA = bodies[i]; + + if (bodyA != NULL) + { + for (int j = i + 1; j < physicsBodiesCount; j++) + { + PhysicsBody bodyB = bodies[j]; + + if (bodyB != NULL) + { + if ((bodyA->inverseMass == 0) && (bodyB->inverseMass == 0)) continue; + + PhysicsManifold manifold = NULL; + if (bodyA->shape.type == PHYSICS_POLYGON && bodyB->shape.type == PHYSICS_CIRCLE) manifold = CreatePhysicsManifold(bodyB, bodyA); + else manifold = CreatePhysicsManifold(bodyA, bodyB); + SolvePhysicsManifold(manifold); + + if (manifold->contactsCount > 0) + { + // Create a new manifold with same information as previously solved manifold and add it to the manifolds pool last slot + PhysicsManifold newManifold = CreatePhysicsManifold(bodyA, bodyB); + newManifold->penetration = manifold->penetration; + newManifold->normal = manifold->normal; + newManifold->contacts[0] = manifold->contacts[0]; + newManifold->contacts[1] = manifold->contacts[1]; + newManifold->contactsCount = manifold->contactsCount; + newManifold->restitution = manifold->restitution; + newManifold->dynamicFriction = manifold->dynamicFriction; + newManifold->staticFriction = manifold->staticFriction; + } + } + } + } + } + + // Integrate forces to physics bodies + for (int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) IntegratePhysicsForces(body); + } + + // Initialize physics manifolds to solve collisions + for (int i = 0; i < physicsManifoldsCount; i++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) InitializePhysicsManifolds(manifold); + } + + // Integrate physics collisions impulses to solve collisions + for (int i = 0; i < PHYSAC_COLLISION_ITERATIONS; i++) + { + for (int j = 0; j < physicsManifoldsCount; j++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) IntegratePhysicsImpulses(manifold); + } + } + + // Integrate velocity to physics bodies + for (int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) IntegratePhysicsVelocity(body); + } + + // Correct physics bodies positions based on manifolds collision information + for (int i = 0; i < physicsManifoldsCount; i++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) CorrectPhysicsPositions(manifold); + } + + // Clear physics bodies forces + for (int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) + { + body->force = (Vector2){ 0 }; + body->torque = 0; + } + } +} + +// Creates a new physics manifold to solve collision +static PhysicsManifold CreatePhysicsManifold(PhysicsBody a, PhysicsBody b) +{ + PhysicsManifold newManifold = (PhysicsManifold)PHYSAC_MALLOC(sizeof(PhysicsManifoldData)); + usedMemory += sizeof(PhysicsManifoldData); + + int newId = -1; + for (int i = 0; i < PHYSAC_MAX_MANIFOLDS; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (int k = 0; k < physicsManifoldsCount; k++) + { + if (contacts[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == i) + { + newId = i; + break; + } + } + + if (newId != -1) + { + // Initialize new manifold with generic values + newManifold->id = newId; + newManifold->bodyA = a; + newManifold->bodyB = b; + newManifold->penetration = 0; + newManifold->normal = (Vector2){ 0 }; + newManifold->contacts[0] = (Vector2){ 0 }; + newManifold->contacts[1] = (Vector2){ 0 }; + newManifold->contactsCount = 0; + newManifold->restitution = 0; + newManifold->dynamicFriction = 0; + newManifold->staticFriction = 0; + + // Add new body to bodies pointers array and update bodies count + contacts[physicsManifoldsCount] = newManifold; + physicsManifoldsCount++; + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] new physics manifold creation failed because there is any available id to use\n"); + #endif + + return newManifold; +} + +// Unitializes and destroys a physics manifold +static void DestroyPhysicsManifold(PhysicsManifold manifold) +{ + if (manifold != NULL) + { + int id = manifold->id; + int index = -1; + + for (int i = 0; i < physicsManifoldsCount; i++) + { + if (contacts[i]->id == id) + { + index = i; + break; + } + } + + #if defined(PHYSAC_DEBUG) + if (index == -1) printf("[PHYSAC] cannot find manifold id %i in pointers array\n", id); + #endif + + // Free manifold allocated memory + PHYSAC_FREE(contacts[index]); + usedMemory -= sizeof(PhysicsManifoldData); + contacts[index] = NULL; + + // Reorder physics manifolds pointers array and its catched index + for (int i = index; i < physicsManifoldsCount; i++) + { + if ((i + 1) < physicsManifoldsCount) contacts[i] = contacts[i + 1]; + } + + // Update physics manifolds count + physicsManifoldsCount--; + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] error trying to destroy a null referenced manifold\n"); + #endif +} + +// Solves a created physics manifold between two physics bodies +static void SolvePhysicsManifold(PhysicsManifold manifold) +{ + switch (manifold->bodyA->shape.type) + { + case PHYSICS_CIRCLE: + { + switch (manifold->bodyB->shape.type) + { + case PHYSICS_CIRCLE: SolveCircleToCircle(manifold); break; + case PHYSICS_POLYGON: SolveCircleToPolygon(manifold); break; + default: break; + } + } break; + case PHYSICS_POLYGON: + { + switch (manifold->bodyB->shape.type) + { + case PHYSICS_CIRCLE: SolvePolygonToCircle(manifold); break; + case PHYSICS_POLYGON: SolvePolygonToPolygon(manifold); break; + default: break; + } + } break; + default: break; + } + + // Update physics body grounded state if normal direction is downside + manifold->bodyB->isGrounded = (manifold->normal.y < 0); +} + +// Solves collision between two circle shape physics bodies +static void SolveCircleToCircle(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + // Calculate translational vector, which is normal + Vector2 normal = Vector2Subtract(bodyB->position, bodyA->position); + + float distSqr = MathLenSqr(normal); + float radius = bodyA->shape.radius + bodyB->shape.radius; + + // Check if circles are not in contact + if (distSqr >= radius*radius) + { + manifold->contactsCount = 0; + return; + } + + float distance = sqrtf(distSqr); + manifold->contactsCount = 1; + + if (distance == 0) + { + manifold->penetration = bodyA->shape.radius; + manifold->normal = (Vector2){ 1, 0 }; + manifold->contacts[0] = bodyA->position; + } + else + { + manifold->penetration = radius - distance; + manifold->normal = (Vector2){ normal.x/distance, normal.y/distance }; // Faster than using MathNormalize() due to sqrt is already performed + manifold->contacts[0] = (Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + } + + // Update physics body grounded state if normal direction is down + if (manifold->normal.y < 0) bodyA->isGrounded = true; +} + +// Solves collision between a circle to a polygon shape physics bodies +static void SolveCircleToPolygon(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + manifold->contactsCount = 0; + + // Transform circle center to polygon transform space + Vector2 center = bodyA->position; + center = Mat2MultiplyVector2(Mat2Transpose(bodyB->shape.vertexData.transform), Vector2Subtract(center, bodyB->position)); + + // Find edge with minimum penetration + // It is the same concept as using support points in SolvePolygonToPolygon + float separation = -PHYSAC_FLT_MAX; + int faceNormal = 0; + PolygonData vertexData = bodyB->shape.vertexData; + + for (int i = 0; i < vertexData.vertexCount; i++) + { + float currentSeparation = MathDot(vertexData.normals[i], Vector2Subtract(center, vertexData.vertices[i])); + + if (currentSeparation > bodyA->shape.radius) return; + + if (currentSeparation > separation) + { + separation = currentSeparation; + faceNormal = i; + } + } + + // Grab face's vertices + Vector2 v1 = vertexData.vertices[faceNormal]; + int nextIndex = (((faceNormal + 1) < vertexData.vertexCount) ? (faceNormal + 1) : 0); + Vector2 v2 = vertexData.vertices[nextIndex]; + + // Check to see if center is within polygon + if (separation < PHYSAC_EPSILON) + { + manifold->contactsCount = 1; + Vector2 normal = Mat2MultiplyVector2(vertexData.transform, vertexData.normals[faceNormal]); + manifold->normal = (Vector2){ -normal.x, -normal.y }; + manifold->contacts[0] = (Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + manifold->penetration = bodyA->shape.radius; + return; + } + + // Determine which voronoi region of the edge center of circle lies within + float dot1 = MathDot(Vector2Subtract(center, v1), Vector2Subtract(v2, v1)); + float dot2 = MathDot(Vector2Subtract(center, v2), Vector2Subtract(v1, v2)); + manifold->penetration = bodyA->shape.radius - separation; + + if (dot1 <= 0) // Closest to v1 + { + if (DistSqr(center, v1) > bodyA->shape.radius*bodyA->shape.radius) return; + + manifold->contactsCount = 1; + Vector2 normal = Vector2Subtract(v1, center); + normal = Mat2MultiplyVector2(vertexData.transform, normal); + MathNormalize(&normal); + manifold->normal = normal; + v1 = Mat2MultiplyVector2(vertexData.transform, v1); + v1 = Vector2Add(v1, bodyB->position); + manifold->contacts[0] = v1; + } + else if (dot2 <= 0) // Closest to v2 + { + if (DistSqr(center, v2) > bodyA->shape.radius*bodyA->shape.radius) return; + + manifold->contactsCount = 1; + Vector2 normal = Vector2Subtract(v2, center); + v2 = Mat2MultiplyVector2(vertexData.transform, v2); + v2 = Vector2Add(v2, bodyB->position); + manifold->contacts[0] = v2; + normal = Mat2MultiplyVector2(vertexData.transform, normal); + MathNormalize(&normal); + manifold->normal = normal; + } + else // Closest to face + { + Vector2 normal = vertexData.normals[faceNormal]; + + if (MathDot(Vector2Subtract(center, v1), normal) > bodyA->shape.radius) return; + + normal = Mat2MultiplyVector2(vertexData.transform, normal); + manifold->normal = (Vector2){ -normal.x, -normal.y }; + manifold->contacts[0] = (Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + manifold->contactsCount = 1; + } +} + +// Solves collision between a polygon to a circle shape physics bodies +static void SolvePolygonToCircle(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + manifold->bodyA = bodyB; + manifold->bodyB = bodyA; + SolveCircleToPolygon(manifold); + + manifold->normal.x *= -1; + manifold->normal.y *= -1; +} + +// Solves collision between two polygons shape physics bodies +static void SolvePolygonToPolygon(PhysicsManifold manifold) +{ + PhysicsShape bodyA = manifold->bodyA->shape; + PhysicsShape bodyB = manifold->bodyB->shape; + manifold->contactsCount = 0; + + // Check for separating axis with A shape's face planes + int faceA = 0; + float penetrationA = FindAxisLeastPenetration(&faceA, bodyA, bodyB); + if (penetrationA >= 0) return; + + // Check for separating axis with B shape's face planes + int faceB = 0; + float penetrationB = FindAxisLeastPenetration(&faceB, bodyB, bodyA); + if (penetrationB >= 0) return; + + int referenceIndex = 0; + bool flip = false; // Always point from A shape to B shape + + PhysicsShape refPoly; // Reference + PhysicsShape incPoly; // Incident + + // Determine which shape contains reference face + if (BiasGreaterThan(penetrationA, penetrationB)) + { + refPoly = bodyA; + incPoly = bodyB; + referenceIndex = faceA; + } + else + { + refPoly = bodyB; + incPoly = bodyA; + referenceIndex = faceB; + flip = true; + } + + // World space incident face + Vector2 incidentFace[2]; + FindIncidentFace(&incidentFace[0], &incidentFace[1], refPoly, incPoly, referenceIndex); + + // Setup reference face vertices + PolygonData refData = refPoly.vertexData; + Vector2 v1 = refData.vertices[referenceIndex]; + referenceIndex = (((referenceIndex + 1) < refData.vertexCount) ? (referenceIndex + 1) : 0); + Vector2 v2 = refData.vertices[referenceIndex]; + + // Transform vertices to world space + v1 = Mat2MultiplyVector2(refData.transform, v1); + v1 = Vector2Add(v1, refPoly.body->position); + v2 = Mat2MultiplyVector2(refData.transform, v2); + v2 = Vector2Add(v2, refPoly.body->position); + + // Calculate reference face side normal in world space + Vector2 sidePlaneNormal = Vector2Subtract(v2, v1); + MathNormalize(&sidePlaneNormal); + + // Orthogonalize + Vector2 refFaceNormal = { sidePlaneNormal.y, -sidePlaneNormal.x }; + float refC = MathDot(refFaceNormal, v1); + float negSide = MathDot(sidePlaneNormal, v1)*-1; + float posSide = MathDot(sidePlaneNormal, v2); + + // Clip incident face to reference face side planes (due to floating point error, possible to not have required points + if (Clip((Vector2){ -sidePlaneNormal.x, -sidePlaneNormal.y }, negSide, &incidentFace[0], &incidentFace[1]) < 2) return; + if (Clip(sidePlaneNormal, posSide, &incidentFace[0], &incidentFace[1]) < 2) return; + + // Flip normal if required + manifold->normal = (flip ? (Vector2){ -refFaceNormal.x, -refFaceNormal.y } : refFaceNormal); + + // Keep points behind reference face + int currentPoint = 0; // Clipped points behind reference face + float separation = MathDot(refFaceNormal, incidentFace[0]) - refC; + if (separation <= 0) + { + manifold->contacts[currentPoint] = incidentFace[0]; + manifold->penetration = -separation; + currentPoint++; + } + else manifold->penetration = 0; + + separation = MathDot(refFaceNormal, incidentFace[1]) - refC; + + if (separation <= 0) + { + manifold->contacts[currentPoint] = incidentFace[1]; + manifold->penetration += -separation; + currentPoint++; + + // Calculate total penetration average + manifold->penetration /= currentPoint; + } + + manifold->contactsCount = currentPoint; +} + +// Integrates physics forces into velocity +static void IntegratePhysicsForces(PhysicsBody body) +{ + if (body->inverseMass == 0 || !body->enabled) return; + + body->velocity.x += (body->force.x*body->inverseMass)*(deltaTime/2); + body->velocity.y += (body->force.y*body->inverseMass)*(deltaTime/2); + + if (body->useGravity) + { + body->velocity.x += gravityForce.x*(deltaTime/2); + body->velocity.y += gravityForce.y*(deltaTime/2); + } + + if (!body->freezeOrient) body->angularVelocity += body->torque*body->inverseInertia*(deltaTime/2); +} + +// Initializes physics manifolds to solve collisions +static void InitializePhysicsManifolds(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + // Calculate average restitution, static and dynamic friction + manifold->restitution = sqrtf(bodyA->restitution*bodyB->restitution); + manifold->staticFriction = sqrtf(bodyA->staticFriction*bodyB->staticFriction); + manifold->dynamicFriction = sqrtf(bodyA->dynamicFriction*bodyB->dynamicFriction); + + for (int i = 0; i < 2; i++) + { + // Caculate radius from center of mass to contact + Vector2 radiusA = Vector2Subtract(manifold->contacts[i], bodyA->position); + Vector2 radiusB = Vector2Subtract(manifold->contacts[i], bodyB->position); + + Vector2 crossA = MathCross(bodyA->angularVelocity, radiusA); + Vector2 crossB = MathCross(bodyB->angularVelocity, radiusB); + + Vector2 radiusV = { 0 }; + radiusV.x = bodyB->velocity.x + crossB.x - bodyA->velocity.x - crossA.x; + radiusV.y = bodyB->velocity.y + crossB.y - bodyA->velocity.y - crossA.y; + + // Determine if we should perform a resting collision or not; + // The idea is if the only thing moving this object is gravity, then the collision should be performed without any restitution + if (MathLenSqr(radiusV) < (MathLenSqr((Vector2){ gravityForce.x*deltaTime, gravityForce.y*deltaTime }) + PHYSAC_EPSILON)) manifold->restitution = 0; + } +} + +// Integrates physics collisions impulses to solve collisions +static void IntegratePhysicsImpulses(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + // Early out and positional correct if both objects have infinite mass + if (fabs(bodyA->inverseMass + bodyB->inverseMass) <= PHYSAC_EPSILON) + { + bodyA->velocity = (Vector2){ 0 }; + bodyB->velocity = (Vector2){ 0 }; + return; + } + + for (int i = 0; i < manifold->contactsCount; i++) + { + // Calculate radius from center of mass to contact + Vector2 radiusA = Vector2Subtract(manifold->contacts[i], bodyA->position); + Vector2 radiusB = Vector2Subtract(manifold->contacts[i], bodyB->position); + + // Calculate relative velocity + Vector2 radiusV = { 0 }; + radiusV.x = bodyB->velocity.x + MathCross(bodyB->angularVelocity, radiusB).x - bodyA->velocity.x - MathCross(bodyA->angularVelocity, radiusA).x; + radiusV.y = bodyB->velocity.y + MathCross(bodyB->angularVelocity, radiusB).y - bodyA->velocity.y - MathCross(bodyA->angularVelocity, radiusA).y; + + // Relative velocity along the normal + float contactVelocity = MathDot(radiusV, manifold->normal); + + // Do not resolve if velocities are separating + if (contactVelocity > 0) return; + + float raCrossN = MathCrossVector2(radiusA, manifold->normal); + float rbCrossN = MathCrossVector2(radiusB, manifold->normal); + + float inverseMassSum = bodyA->inverseMass + bodyB->inverseMass + (raCrossN*raCrossN)*bodyA->inverseInertia + (rbCrossN*rbCrossN)*bodyB->inverseInertia; + + // Calculate impulse scalar value + float impulse = -(1.0f + manifold->restitution)*contactVelocity; + impulse /= inverseMassSum; + impulse /= (float)manifold->contactsCount; + + // Apply impulse to each physics body + Vector2 impulseV = { manifold->normal.x*impulse, manifold->normal.y*impulse }; + + if (bodyA->enabled) + { + bodyA->velocity.x += bodyA->inverseMass*(-impulseV.x); + bodyA->velocity.y += bodyA->inverseMass*(-impulseV.y); + if (!bodyA->freezeOrient) bodyA->angularVelocity += bodyA->inverseInertia*MathCrossVector2(radiusA, (Vector2){ -impulseV.x, -impulseV.y }); + } + + if (bodyB->enabled) + { + bodyB->velocity.x += bodyB->inverseMass*(impulseV.x); + bodyB->velocity.y += bodyB->inverseMass*(impulseV.y); + if (!bodyB->freezeOrient) bodyB->angularVelocity += bodyB->inverseInertia*MathCrossVector2(radiusB, impulseV); + } + + // Apply friction impulse to each physics body + radiusV.x = bodyB->velocity.x + MathCross(bodyB->angularVelocity, radiusB).x - bodyA->velocity.x - MathCross(bodyA->angularVelocity, radiusA).x; + radiusV.y = bodyB->velocity.y + MathCross(bodyB->angularVelocity, radiusB).y - bodyA->velocity.y - MathCross(bodyA->angularVelocity, radiusA).y; + + Vector2 tangent = { radiusV.x - (manifold->normal.x*MathDot(radiusV, manifold->normal)), radiusV.y - (manifold->normal.y*MathDot(radiusV, manifold->normal)) }; + MathNormalize(&tangent); + + // Calculate impulse tangent magnitude + float impulseTangent = -MathDot(radiusV, tangent); + impulseTangent /= inverseMassSum; + impulseTangent /= (float)manifold->contactsCount; + + float absImpulseTangent = fabs(impulseTangent); + + // Don't apply tiny friction impulses + if (absImpulseTangent <= PHYSAC_EPSILON) return; + + // Apply coulumb's law + Vector2 tangentImpulse = { 0 }; + if (absImpulseTangent < impulse*manifold->staticFriction) tangentImpulse = (Vector2){ tangent.x*impulseTangent, tangent.y*impulseTangent }; + else tangentImpulse = (Vector2){ tangent.x*-impulse*manifold->dynamicFriction, tangent.y*-impulse*manifold->dynamicFriction }; + + // Apply friction impulse + if (bodyA->enabled) + { + bodyA->velocity.x += bodyA->inverseMass*(-tangentImpulse.x); + bodyA->velocity.y += bodyA->inverseMass*(-tangentImpulse.y); + + if (!bodyA->freezeOrient) bodyA->angularVelocity += bodyA->inverseInertia*MathCrossVector2(radiusA, (Vector2){ -tangentImpulse.x, -tangentImpulse.y }); + } + + if (bodyB->enabled) + { + bodyB->velocity.x += bodyB->inverseMass*(tangentImpulse.x); + bodyB->velocity.y += bodyB->inverseMass*(tangentImpulse.y); + + if (!bodyB->freezeOrient) bodyB->angularVelocity += bodyB->inverseInertia*MathCrossVector2(radiusB, tangentImpulse); + } + } +} + +// Integrates physics velocity into position and forces +static void IntegratePhysicsVelocity(PhysicsBody body) +{ + if (!body->enabled) return; + + body->position.x += body->velocity.x*deltaTime; + body->position.y += body->velocity.y*deltaTime; + + if (!body->freezeOrient) body->orient += body->angularVelocity*deltaTime; + Mat2Set(&body->shape.vertexData.transform, body->orient); + + IntegratePhysicsForces(body); +} + +// Corrects physics bodies positions based on manifolds collision information +static void CorrectPhysicsPositions(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + Vector2 correction = { 0 }; + correction.x = (max(manifold->penetration - PHYSAC_PENETRATION_ALLOWANCE, 0)/(bodyA->inverseMass + bodyB->inverseMass))*manifold->normal.x*PHYSAC_PENETRATION_CORRECTION; + correction.y = (max(manifold->penetration - PHYSAC_PENETRATION_ALLOWANCE, 0)/(bodyA->inverseMass + bodyB->inverseMass))*manifold->normal.y*PHYSAC_PENETRATION_CORRECTION; + + if (bodyA->enabled) + { + bodyA->position.x -= correction.x*bodyA->inverseMass; + bodyA->position.y -= correction.y*bodyA->inverseMass; + } + + if (bodyB->enabled) + { + bodyB->position.x += correction.x*bodyB->inverseMass; + bodyB->position.y += correction.y*bodyB->inverseMass; + } +} + +// Returns the extreme point along a direction within a polygon +static Vector2 GetSupport(PhysicsShape shape, Vector2 dir) +{ + float bestProjection = -PHYSAC_FLT_MAX; + Vector2 bestVertex = { 0 }; + PolygonData data = shape.vertexData; + + for (int i = 0; i < data.vertexCount; i++) + { + Vector2 vertex = data.vertices[i]; + float projection = MathDot(vertex, dir); + + if (projection > bestProjection) + { + bestVertex = vertex; + bestProjection = projection; + } + } + + return bestVertex; +} + +// Finds polygon shapes axis least penetration +static float FindAxisLeastPenetration(int *faceIndex, PhysicsShape shapeA, PhysicsShape shapeB) +{ + float bestDistance = -PHYSAC_FLT_MAX; + int bestIndex = 0; + + PolygonData dataA = shapeA.vertexData; + PolygonData dataB = shapeB.vertexData; + + for (int i = 0; i < dataA.vertexCount; i++) + { + // Retrieve a face normal from A shape + Vector2 normal = dataA.normals[i]; + Vector2 transNormal = Mat2MultiplyVector2(dataA.transform, normal); + + // Transform face normal into B shape's model space + Mat2 buT = Mat2Transpose(dataB.transform); + normal = Mat2MultiplyVector2(buT, transNormal); + + // Retrieve support point from B shape along -n + Vector2 support = GetSupport(shapeB, (Vector2){ -normal.x, -normal.y }); + + // Retrieve vertex on face from A shape, transform into B shape's model space + Vector2 vertex = dataA.vertices[i]; + vertex = Mat2MultiplyVector2(dataA.transform, vertex); + vertex = Vector2Add(vertex, shapeA.body->position); + vertex = Vector2Subtract(vertex, shapeB.body->position); + vertex = Mat2MultiplyVector2(buT, vertex); + + // Compute penetration distance in B shape's model space + float distance = MathDot(normal, Vector2Subtract(support, vertex)); + + // Store greatest distance + if (distance > bestDistance) + { + bestDistance = distance; + bestIndex = i; + } + } + + *faceIndex = bestIndex; + return bestDistance; +} + +// Finds two polygon shapes incident face +static void FindIncidentFace(Vector2 *v0, Vector2 *v1, PhysicsShape ref, PhysicsShape inc, int index) +{ + PolygonData refData = ref.vertexData; + PolygonData incData = inc.vertexData; + + Vector2 referenceNormal = refData.normals[index]; + + // Calculate normal in incident's frame of reference + referenceNormal = Mat2MultiplyVector2(refData.transform, referenceNormal); // To world space + referenceNormal = Mat2MultiplyVector2(Mat2Transpose(incData.transform), referenceNormal); // To incident's model space + + // Find most anti-normal face on polygon + int incidentFace = 0; + float minDot = PHYSAC_FLT_MAX; + + for (int i = 0; i < incData.vertexCount; i++) + { + float dot = MathDot(referenceNormal, incData.normals[i]); + + if (dot < minDot) + { + minDot = dot; + incidentFace = i; + } + } + + // Assign face vertices for incident face + *v0 = Mat2MultiplyVector2(incData.transform, incData.vertices[incidentFace]); + *v0 = Vector2Add(*v0, inc.body->position); + incidentFace = (((incidentFace + 1) < incData.vertexCount) ? (incidentFace + 1) : 0); + *v1 = Mat2MultiplyVector2(incData.transform, incData.vertices[incidentFace]); + *v1 = Vector2Add(*v1, inc.body->position); +} + +// Calculates clipping based on a normal and two faces +static int Clip(Vector2 normal, float clip, Vector2 *faceA, Vector2 *faceB) +{ + int sp = 0; + Vector2 out[2] = { *faceA, *faceB }; + + // Retrieve distances from each endpoint to the line + float distanceA = MathDot(normal, *faceA) - clip; + float distanceB = MathDot(normal, *faceB) - clip; + + // If negative (behind plane) + if (distanceA <= 0) out[sp++] = *faceA; + if (distanceB <= 0) out[sp++] = *faceB; + + // If the points are on different sides of the plane + if ((distanceA*distanceB) < 0) + { + // Push intersection point + float alpha = distanceA/(distanceA - distanceB); + out[sp] = *faceA; + Vector2 delta = Vector2Subtract(*faceB, *faceA); + delta.x *= alpha; + delta.y *= alpha; + out[sp] = Vector2Add(out[sp], delta); + sp++; + } + + // Assign the new converted values + *faceA = out[0]; + *faceB = out[1]; + + return sp; +} + +// Check if values are between bias range +static bool BiasGreaterThan(float valueA, float valueB) +{ + return (valueA >= (valueB*0.95f + valueA*0.01f)); +} + +// Returns the barycenter of a triangle given by 3 points +static Vector2 TriangleBarycenter(Vector2 v1, Vector2 v2, Vector2 v3) +{ + Vector2 result = { 0 }; + + result.x = (v1.x + v2.x + v3.x)/3; + result.y = (v1.y + v2.y + v3.y)/3; + + return result; +} + +// Initializes hi-resolution timer +static void InitTimer(void) +{ + srand(time(NULL)); // Initialize random seed + + #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) baseTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; + #endif + + startTime = GetCurrentTime(); +} + +// Get current time in milliseconds +static double GetCurrentTime(void) +{ + double time = 0; + + #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + unsigned long long int clockFrequency, currentTime; + + QueryPerformanceFrequency(&clockFrequency); + QueryPerformanceCounter(¤tTime); + + time = (double)((double)currentTime/clockFrequency)*1000; + #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)((double)(temp - baseTime)*1e-6); + #endif + + return time; +} + +// Returns a random number between min and max (both included) +static int GetRandomNumber(int min, int max) +{ + if (min > max) + { + int tmp = max; + max = min; + min = tmp; + } + + return (rand()%(abs(max - min) + 1) + min); +} + +// Clamp a value in a range +static inline void MathClamp(double *value, double min, double max) +{ + if (*value < min) *value = min; + else if (*value > max) *value = max; +} + +// Returns the cross product of a vector and a value +static inline Vector2 MathCross(float value, Vector2 vector) +{ + return (Vector2){ -value*vector.y, value*vector.x }; +} + +// Returns the cross product of two vectors +static inline float MathCrossVector2(Vector2 v1, Vector2 v2) +{ + return (v1.x*v2.y - v1.y*v2.x); +} + +// Returns the len square root of a vector +static inline float MathLenSqr(Vector2 vector) +{ + return (vector.x*vector.x + vector.y*vector.y); +} + +// Returns the dot product of two vectors +static inline float MathDot(Vector2 v1, Vector2 v2) +{ + return (v1.x*v2.x + v1.y*v2.y); +} + +// Returns the square root of distance between two vectors +static inline float DistSqr(Vector2 v1, Vector2 v2) +{ + Vector2 dir = Vector2Subtract(v1, v2); + return MathDot(dir, dir); +} + +// Returns the normalized values of a vector +static void MathNormalize(Vector2 *vector) +{ + float length, ilength; + + Vector2 aux = *vector; + length = sqrtf(aux.x*aux.x + aux.y*aux.y); + + if (length == 0) length = 1.0f; + + ilength = 1.0f/length; + + vector->x *= ilength; + vector->y *= ilength; +} + +// Returns the sum of two given vectors +static inline Vector2 Vector2Add(Vector2 v1, Vector2 v2) +{ + return (Vector2){ v1.x + v2.x, v1.y + v2.y }; +} + +// Returns the subtract of two given vectors +static inline Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) +{ + return (Vector2){ v1.x - v2.x, v1.y - v2.y }; +} + +// Creates a matrix 2x2 from a given radians value +static inline Mat2 Mat2Radians(float radians) +{ + float c = cosf(radians); + float s = sinf(radians); + + return (Mat2){ c, -s, s, c }; +} + +// Set values from radians to a created matrix 2x2 +static void Mat2Set(Mat2 *matrix, float radians) +{ + float cos = cosf(radians); + float sin = sinf(radians); + + matrix->m00 = cos; + matrix->m01 = -sin; + matrix->m10 = sin; + matrix->m11 = cos; +} + +// Returns the transpose of a given matrix 2x2 +static inline Mat2 Mat2Transpose(Mat2 matrix) +{ + return (Mat2){ matrix.m00, matrix.m10, matrix.m01, matrix.m11 }; +} + +// Multiplies a vector by a matrix 2x2 +static inline Vector2 Mat2MultiplyVector2(Mat2 matrix, Vector2 vector) +{ + return (Vector2){ matrix.m00*vector.x + matrix.m01*vector.y, matrix.m10*vector.x + matrix.m11*vector.y }; } -#endif // PHYSAC_IMPLEMENTATION \ No newline at end of file +#endif // PHYSAC_IMPLEMENTATION -- cgit v1.2.3 From 1aa775eca8d5fbacb3d5b19143ca08c6b9eca65d Mon Sep 17 00:00:00 2001 From: Saggi Mizrahi Date: Thu, 22 Dec 2016 03:21:04 +0200 Subject: Fix physac.h building on linux Signed-off-by: Saggi Mizrahi --- src/physac.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/physac.h') diff --git a/src/physac.h b/src/physac.h index e807ffa6..d958c701 100644 --- a/src/physac.h +++ b/src/physac.h @@ -239,9 +239,10 @@ PHYSACDEF void ClosePhysics(void); // 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(__linux) +#elif defined(__linux) || defined(PLATFORM_WEB) #include // Required for: timespec #include // Required for: clock_gettime() + #include #endif //---------------------------------------------------------------------------------- @@ -266,7 +267,7 @@ PHYSACDEF void ClosePhysics(void); static unsigned int usedMemory = 0; // Total allocated dynamic memory static bool physicsThreadEnabled = false; // Physics thread enabled state static double currentTime = 0; // Current time in milliseconds -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(__linux) || defined(PLATFORM_WEB) static double baseTime = 0; // Android and RPI platforms base time #endif static double startTime = 0; // Start time in milliseconds @@ -1942,7 +1943,7 @@ static double GetCurrentTime(void) { double time = 0; - #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + #if defined(_WIN32) unsigned long long int clockFrequency, currentTime; QueryPerformanceFrequency(&clockFrequency); @@ -1951,7 +1952,7 @@ static double GetCurrentTime(void) time = (double)((double)currentTime/clockFrequency)*1000; #endif - #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(__linux) || defined(PLATFORM_WEB) struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); uint64_t temp = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec; -- cgit v1.2.3 From 05cff44d0a9af5bcb41ddd0baa6b7693f6ac116b Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 16 Feb 2017 00:50:02 +0100 Subject: Improved modules description -IN PROGRESS- Working in modules configuration flags... --- src/audio.c | 56 +++++++++++++++++++++++++++++++++++++------------------- src/audio.h | 4 +++- src/camera.h | 14 +++++++++++--- src/core.c | 55 +++++++++++++++++++++++++++++++++++++------------------ src/gestures.h | 17 +++++++++++++---- src/models.c | 13 +++++++------ src/physac.h | 15 +++++++++------ src/raylib.h | 44 +++++++++++++++++++++++++++----------------- src/raymath.h | 25 +++++++++++++------------ src/rlgl.c | 46 ++++++++++++++++++++++++++++++++++++---------- src/rres.h | 18 +++++++++++------- src/shapes.c | 11 ++++------- src/text.c | 36 +++++++++++++++++++++--------------- src/textures.c | 35 +++++++++++++++++++++++++++-------- src/utils.c | 19 +++++++++++++------ 15 files changed, 269 insertions(+), 139 deletions(-) (limited to 'src/physac.h') diff --git a/src/audio.c b/src/audio.c index 58699035..690a41eb 100644 --- a/src/audio.c +++ b/src/audio.c @@ -3,32 +3,50 @@ * raylib.audio * * This module provides basic functionality to work with audio: -* Manage audio device (init/close) -* Load and Unload audio files (WAV, OGG, FLAC, XM, MOD) -* Play/Stop/Pause/Resume loaded audio -* Manage mixing channels -* Manage raw audio context +* Manage audio device (init/close) +* Load and Unload audio files (WAV, OGG, FLAC, XM, MOD) +* Play/Stop/Pause/Resume loaded audio +* Manage mixing channels +* Manage raw audio context * -* External libs: +* NOTES: +* +* Only up to two channels supported: MONO and STEREO (for additional channels, use AL_EXT_MCFORMATS) +* Only the following sample sizes supported: 8bit PCM, 16bit PCM, 32-bit float PCM (using AL_EXT_FLOAT32) +* +* CONFIGURATION: +* +* #define AUDIO_STANDALONE +* If defined, the module can be used as standalone library (independently of raylib). +* Required types and functions are defined in the same module. +* +* #define SUPPORT_FILEFORMAT_WAV / SUPPORT_LOAD_WAV / ENABLE_LOAD_WAV +* #define SUPPORT_FILEFORMAT_OGG +* #define SUPPORT_FILEFORMAT_XM +* #define SUPPORT_FILEFORMAT_MOD +* #define SUPPORT_FILEFORMAT_FLAC +* Selected desired fileformats to be supported for loading. Some of those formats are +* supported by default, to remove support, just comment unrequired #define in this module +* +* #define SUPPORT_RAW_AUDIO_BUFFERS +* +* DEPENDENCIES: * OpenAL Soft - Audio device management (http://kcat.strangesoft.net/openal.html) * stb_vorbis - OGG audio files loading (http://www.nothings.org/stb_vorbis/) * jar_xm - XM module file loading * jar_mod - MOD audio file loading * dr_flac - FLAC audio file loading * -* Module Configuration Flags: -* AUDIO_STANDALONE - Use this module as standalone library (independently of raylib) -* -* Some design decisions: -* Support only up to two channels: MONO and STEREO (for additional channels, AL_EXT_MCFORMATS) -* Support only the following sample sizes: 8bit PCM, 16bit PCM, 32-bit float PCM (using AL_EXT_FLOAT32) +* CONTRIBUTORS: * * Many thanks to Joshua Reisenauer (github: @kd7tck) for the following additions: -* XM audio module support (jar_xm) -* MOD audio module support (jar_mod) -* Mixing channels support -* Raw audio context support +* XM audio module support (jar_xm) +* MOD audio module support (jar_mod) +* Mixing channels support +* Raw audio context support +* * +* LICENSE: zlib/libpng * * Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * @@ -246,11 +264,11 @@ Wave LoadWave(const char *fileName) else if (strcmp(GetExtension(fileName), "flac") == 0) wave = LoadFLAC(fileName); else if (strcmp(GetExtension(fileName),"rres") == 0) { - RRESData rres = LoadResource(fileName); + RRES rres = LoadResource(fileName, 0); - // NOTE: Parameters for RRES_WAVE type are: sampleCount, sampleRate, sampleSize, channels + // NOTE: Parameters for RRES_TYPE_WAVE are: sampleCount, sampleRate, sampleSize, channels - if (rres.type == RRES_WAVE) wave = LoadWaveEx(rres.data, rres.param1, rres.param2, rres.param3, rres.param4); + if (rres[0].type == RRES_TYPE_WAVE) wave = LoadWaveEx(rres[0].data, rres[0].param1, rres[0].param2, rres[0].param3, rres[0].param4); else TraceLog(WARNING, "[%s] Resource file does not contain wave data", fileName); UnloadResource(rres); diff --git a/src/audio.h b/src/audio.h index 99b58e15..01ed9f72 100644 --- a/src/audio.h +++ b/src/audio.h @@ -9,7 +9,7 @@ * Manage mixing channels * Manage raw audio context * -* External libs: +* DEPENDENCIES: * OpenAL Soft - Audio device management (http://kcat.strangesoft.net/openal.html) * stb_vorbis - OGG audio files loading (http://www.nothings.org/stb_vorbis/) * jar_xm - XM module file loading @@ -23,6 +23,8 @@ * Raw audio context support * * +* LICENSE: zlib/libpng +* * Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event diff --git a/src/camera.h b/src/camera.h index cf542288..87ba1942 100644 --- a/src/camera.h +++ b/src/camera.h @@ -2,6 +2,10 @@ * * raylib Camera System - Camera Modes Setup and Control Functions * +* NOTE: Memory footprint of this library is aproximately 52 bytes (global variables) +* +* CONFIGURATION: +* * #define CAMERA_IMPLEMENTATION * Generates the implementation of the library into the included file. * If not defined, the library is in header only mode and can be included in other headers @@ -11,10 +15,14 @@ * If defined, the library can be used as standalone as a camera system but some * functions must be redefined to manage inputs accordingly. * -* NOTE: Memory footprint of this library is aproximately 52 bytes (global variables) +* CONTRIBUTORS: +* Marc Palau: Initial implementation (2014) +* Ramon Santamaria: Supervision, review, update and maintenance +* +* +* LICENSE: zlib/libpng * -* Initial design by Marc Palau (2014) -* Reviewed by Ramon Santamaria (2015-2016) +* Copyright (c) 2015-2016 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/core.c b/src/core.c index 28f73345..9d40edcc 100644 --- a/src/core.c +++ b/src/core.c @@ -1,27 +1,46 @@ /********************************************************************************************** * -* raylib.core -* -* Basic functions to manage windows, OpenGL context and input on multiple platforms +* raylib.core - Basic functions to manage windows, OpenGL context and input on multiple platforms * * The following platforms are supported: Windows, Linux, Mac (OSX), Android, Raspberry Pi, HTML5, Oculus Rift CV1 * -* External libs: +* CONFIGURATION: +* +* #define PLATFORM_DESKTOP +* Windowing and input system configured for desktop platforms: Windows, Linux, OSX (managed by GLFW3 library) +* NOTE: Oculus Rift CV1 requires PLATFORM_DESKTOP for mirror rendering - View [rlgl] module to enable it +* +* #define PLATFORM_ANDROID +* Windowing and input system configured for Android device, app activity managed internally in this module. +* NOTE: OpenGL ES 2.0 is required and graphic device is managed by EGL +* +* #define PLATFORM_RPI +* Windowing and input system configured for Raspberry Pi (tested on Raspbian), graphic device is managed by EGL +* and inputs are processed is raw mode, reading from /dev/input/ +* +* #define PLATFORM_WEB +* Windowing and input system configured for HTML5 (run on browser), code converted from C to asm.js +* using emscripten compiler. OpenGL ES 2.0 required for direct translation to WebGL equivalent code. +* +* #define LOAD_DEFAULT_FONT (defined by default) +* Default font is loaded on window initialization to be available for the user to render simple text. +* NOTE: If enabled, uses external module functions to load default raylib font (module: text) +* +* #define INCLUDE_CAMERA_SYSTEM / SUPPORT_CAMERA_SYSTEM +* +* #define INCLUDE_GESTURES_SYSTEM / SUPPORT_GESTURES_SYSTEM +* +* #define SUPPORT_MOUSE_GESTURES +* Mouse gestures are directly mapped like touches and processed by gestures system. +* +* DEPENDENCIES: * GLFW3 - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX) * raymath - 3D math functionality (Vector3, Matrix, Quaternion) * camera - Multiple 3D camera modes (free, orbital, 1st person, 3rd person) * gestures - Gestures system for touch-ready devices (or simulated from mouse inputs) * -* Module Configuration Flags: -* PLATFORM_DESKTOP - Windows, Linux, Mac (OSX) -* PLATFORM_ANDROID - Android (only OpenGL ES 2.0 devices), graphic device is managed by EGL and input system by Android activity. -* PLATFORM_RPI - Rapsberry Pi (tested on Raspbian), graphic device is managed by EGL and input system is coded in raw mode. -* PLATFORM_WEB - HTML5 (using emscripten compiler) -* -* RL_LOAD_DEFAULT_FONT - Use external module functions to load default raylib font (module: text) -* -* NOTE: Oculus Rift CV1 requires PLATFORM_DESKTOP for render mirror - View [rlgl] module to enable it * +* LICENSE: zlib/libpng * * Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * @@ -140,7 +159,7 @@ #define MAX_GAMEPAD_BUTTONS 32 // Max bumber of buttons supported (per gamepad) #define MAX_GAMEPAD_AXIS 8 // Max number of axis supported (per gamepad) -#define RL_LOAD_DEFAULT_FONT // Load default font on window initialization (module: text) +#define LOAD_DEFAULT_FONT // Load default font on window initialization (module: text) //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -256,7 +275,7 @@ static bool showLogo = false; // Track if showing logo at init is //---------------------------------------------------------------------------------- // Other Modules Functions Declaration (required by core) //---------------------------------------------------------------------------------- -#if defined(RL_LOAD_DEFAULT_FONT) +#if defined(LOAD_DEFAULT_FONT) extern void LoadDefaultFont(void); // [Module: text] Loads default font on InitWindow() extern void UnloadDefaultFont(void); // [Module: text] Unloads default font from GPU memory #endif @@ -338,7 +357,7 @@ void InitWindow(int width, int height, const char *title) // Init graphics device (display device and OpenGL context) InitGraphicsDevice(width, height); -#if defined(RL_LOAD_DEFAULT_FONT) +#if defined(LOAD_DEFAULT_FONT) // Load default font // NOTE: External function (defined in module: text) LoadDefaultFont(); @@ -450,7 +469,7 @@ void InitWindow(int width, int height, void *state) // Close Window and Terminate Context void CloseWindow(void) { -#if defined(RL_LOAD_DEFAULT_FONT) +#if defined(LOAD_DEFAULT_FONT) UnloadDefaultFont(); #endif @@ -2410,7 +2429,7 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) // Init graphics device (display device and OpenGL context) InitGraphicsDevice(screenWidth, screenHeight); - #if defined(RL_LOAD_DEFAULT_FONT) + #if defined(LOAD_DEFAULT_FONT) // Load default font // NOTE: External function (defined in module: text) LoadDefaultFont(); diff --git a/src/gestures.h b/src/gestures.h index f4dcb133..99f49d2a 100644 --- a/src/gestures.h +++ b/src/gestures.h @@ -2,6 +2,10 @@ * * raylib Gestures System - Gestures Processing based on input gesture events (touch/mouse) * +* NOTE: Memory footprint of this library is aproximately 128 bytes (global variables) +* +* CONFIGURATION: +* * #define GESTURES_IMPLEMENTATION * Generates the implementation of the library into the included file. * If not defined, the library is in header only mode and can be included in other headers @@ -11,11 +15,16 @@ * If defined, the library can be used as standalone to process gesture events with * no external dependencies. * -* NOTE: Memory footprint of this library is aproximately 128 bytes +* CONTRIBUTORS: +* Marc Palau: Initial implementation (2014) +* Albert Martos: Complete redesign and testing (2015) +* Ian Eito: Complete redesign and testing (2015) +* Ramon Santamaria: Supervision, review, update and maintenance +* +* +* LICENSE: zlib/libpng * -* Initial design by Marc Palau (2014) -* Redesigned by Albert Martos and Ian Eito (2015) -* Reviewed by Ramon Santamaria (2015-2016) +* Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/models.c b/src/models.c index 43821691..bef19e10 100644 --- a/src/models.c +++ b/src/models.c @@ -1,14 +1,15 @@ /********************************************************************************************** * -* raylib.models +* raylib.models - Basic functions to draw 3d shapes and 3d models * -* Basic functions to draw 3d shapes and load/draw 3d models (.OBJ) +* CONFIGURATION: * -* External libs: -* rlgl - raylib OpenGL abstraction layer +* #define SUPPORT_FILEFORMAT_OBJ / SUPPORT_LOAD_OBJ * -* Module Configuration Flags: -* ... +* #define SUPPORT_FILEFORMAT_MTL +* +* +* LICENSE: zlib/libpng * * Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * diff --git a/src/physac.h b/src/physac.h index d958c701..cb0e3f3c 100644 --- a/src/physac.h +++ b/src/physac.h @@ -1,11 +1,13 @@ /********************************************************************************************** * -* Physac - 2D Physics library for videogames +* Physac v1.0 - 2D Physics library for videogames * -* Description: Physac is a small 2D physics engine written in pure C. The engine uses a fixed time-step thread loop -* to simluate physics. A physics step contains the following phases: get collision information, apply dynamics, -* collision solving and position correction. It uses a very simple struct for physic bodies with a position vector -* to be used in any 3D rendering API. +* DESCRIPTION: +* +* Physac is a small 2D physics engine written in pure C. The engine uses a fixed time-step thread loop +* to simluate physics. A physics step contains the following phases: get collision information, +* apply dynamics, collision solving and position correction. It uses a very simple struct for physic +* bodies with a position vector to be used in any 3D rendering API. * * CONFIGURATION: * @@ -37,7 +39,8 @@ * Otherwise it will include stdlib.h and use the C standard library malloc()/free() function. * * VERY THANKS TO: -* - Ramón Santamaria (@raysan5) +* Ramón Santamaria (@raysan5) +* * * LICENSE: zlib/libpng * diff --git a/src/raylib.h b/src/raylib.h index 800ab2be..3ff0d28f 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1,10 +1,10 @@ /********************************************************************************************** * -* raylib 1.7.0 (www.raylib.com) +* raylib v1.7.0 (www.raylib.com) * * A simple and easy-to-use library to learn videogames programming * -* Features: +* FEATURES: * Library written in plain C code (C99) * Uses PascalCase/camelCase notation * Hardware accelerated with OpenGL (1.1, 2.1, 3.3 or ES 2.0) @@ -20,7 +20,13 @@ * Minimal external dependencies (GLFW3, OpenGL, OpenAL) * Complete binding for Lua [rlua] * -* External libs: +* NOTES: +* 32bit Colors - All defined color are always RGBA (struct Color is 4 byte) +* One custom default font could be loaded automatically when InitWindow() [core] +* If using OpenGL 3.3 or ES2, several vertex buffers (VAO/VBO) are created to manage lines-triangles-quads +* If using OpenGL 3.3 or ES2, two default shaders could be loaded automatically (internally defined) +* +* DEPENDENCIES: * GLFW3 (www.glfw.org) for window/context management and input [core] * GLAD for OpenGL extensions loading (3.3 Core profile, only PLATFORM_DESKTOP) [rlgl] * stb_image (Sean Barret) for images loading (JPEG, PNG, BMP, TGA) [textures] @@ -33,13 +39,8 @@ * OpenAL Soft for audio device/context management [audio] * tinfl for data decompression (DEFLATE algorithm) [utils] * -* Some design decisions: -* 32bit Colors - All defined color are always RGBA (struct Color is 4 byte) -* One custom default font could be loaded automatically when InitWindow() [core] -* If using OpenGL 3.3 or ES2, several vertex buffers (VAO/VBO) are created to manage lines-triangles-quads -* If using OpenGL 3.3 or ES2, two default shaders could be loaded automatically (internally defined) * -* -- LICENSE -- +* LICENSE: zlib/libpng * * raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, * BSD-like license that allows static linking with closed source software: @@ -592,8 +593,9 @@ typedef enum { HMD_FOVE_VR, } VrDevice; -// rRES data returned when reading a resource, it contains all required data for user (24 byte) -typedef struct { +// rRES data returned when reading a resource, +// it contains all required data for user (24 byte) +typedef struct RRESData { unsigned int type; // Resource type (4 byte) unsigned int param1; // Resouce parameter 1 (4 byte) @@ -604,14 +606,21 @@ typedef struct { void *data; // Resource data pointer (4 byte) } RRESData; -typedef enum { - RRES_RAW = 0, - RRES_IMAGE, - RRES_WAVE, - RRES_VERTEX, - RRES_TEXT +// RRESData type +typedef enum { + RRES_TYPE_RAW = 0, + RRES_TYPE_IMAGE, + RRES_TYPE_WAVE, + RRES_TYPE_VERTEX, + RRES_TYPE_TEXT, + RRES_TYPE_FONT_IMAGE, + RRES_TYPE_FONT_CHARDATA, // CharInfo data array + RRES_TYPE_DIRECTORY } RRESDataType; +// RRES type (pointer to RRESData array) +typedef struct RRESData *RRES; + #ifdef __cplusplus extern "C" { // Prevents name mangling of functions #endif @@ -758,6 +767,7 @@ RLAPI void DrawCircleV(Vector2 center, float radius, Color color); RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters RLAPI void DrawRectangleGradient(int posX, int posY, int width, int height, Color color1, Color color2); // Draw a gradient-filled rectangle RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline diff --git a/src/raymath.h b/src/raymath.h index c073b72d..a2263f19 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -1,22 +1,23 @@ /********************************************************************************************** * -* raymath (header only file) +* raymath v1.0 - Some useful functions to work with Vector3, Matrix and Quaternions * -* Some useful functions to work with Vector3, Matrix and Quaternions +* CONFIGURATION: * -* You must: -* #define RAYMATH_IMPLEMENTATION -* before you include this file in *only one* C or C++ file to create the implementation. +* #define RAYMATH_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. * -* Example: -* #define RAYMATH_IMPLEMENTATION -* #include "raymath.h" +* #define RAYMATH_EXTERN_INLINE +* Inlines all functions code, so it runs faster. This requires lots of memory on system. +* +* #define RAYMATH_STANDALONE +* Avoid raylib.h header inclusion in this file. +* Vector3 and Matrix data types are defined internally in raymath module. * -* You can also use: -* #define RAYMATH_EXTERN_INLINE // Inlines all functions code, so it runs faster. -* // This requires lots of memory on system. -* #define RAYMATH_STANDALONE // Not dependent on raylib.h structs: Vector3, Matrix. * +* LICENSE: zlib/libpng * * Copyright (c) 2015 Ramon Santamaria (@raysan5) * diff --git a/src/rlgl.c b/src/rlgl.c index ce17adc3..ffc9d741 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -2,6 +2,8 @@ * * rlgl - raylib OpenGL abstraction layer * +* DESCRIPTION: +* * rlgl allows usage of OpenGL 1.1 style functions (rlVertex) that are internally mapped to * selected OpenGL version (1.1, 2.1, 3.3 Core, ES 2.0). * @@ -11,20 +13,44 @@ * rlglDraw() - Process internal buffers and send required draw calls * rlglClose() - De-initialize internal buffers data and other auxiliar resources * -* External libs: +* CONFIGURATION: +* +* #define GRAPHICS_API_OPENGL_11 +* Use OpenGL 1.1 backend +* +* #define GRAPHICS_API_OPENGL_21 +* Use OpenGL 2.1 backend +* +* #define GRAPHICS_API_OPENGL_33 +* Use OpenGL 3.3 Core profile backend +* +* #define GRAPHICS_API_OPENGL_ES2 +* Use OpenGL ES 2.0 backend +* +* #define RLGL_STANDALONE +* Use rlgl as standalone library (no raylib dependency) +* +* #define RLGL_NO_DISTORTION_SHADER +* Avoid stereo rendering distortion sahder (shader_distortion.h) inclusion +* +* #define SUPPORT_SHADER_DEFAULT / ENABLE_SHADER_DEFAULT +* +* #define SUPPORT_SHADER_DISTORTION +* +* +* #define SUPPORT_OCULUS_RIFT_CV1 / RLGL_OCULUS_SUPPORT +* Enable Oculus Rift CV1 functionality +* +* #define SUPPORT_STEREO_RENDERING +* +* #define RLGL_NO_DEFAULT_SHADER +* +* DEPENDENCIES: * raymath - 3D math functionality (Vector3, Matrix, Quaternion) * GLAD - OpenGL extensions loading (OpenGL 3.3 Core only) * -* Module Configuration Flags: -* GRAPHICS_API_OPENGL_11 - Use OpenGL 1.1 backend -* GRAPHICS_API_OPENGL_21 - Use OpenGL 2.1 backend -* GRAPHICS_API_OPENGL_33 - Use OpenGL 3.3 Core profile backend -* GRAPHICS_API_OPENGL_ES2 - Use OpenGL ES 2.0 backend -* -* RLGL_STANDALONE - Use rlgl as standalone library (no raylib dependency) -* RLGL_NO_DISTORTION_SHADER - Avoid stereo rendering distortion sahder (shader_distortion.h) inclusion -* RLGL_OCULUS_SUPPORT - Enable Oculus Rift CV1 functionality * +* LICENSE: zlib/libpng * * Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * diff --git a/src/rres.h b/src/rres.h index bed28723..362da10d 100644 --- a/src/rres.h +++ b/src/rres.h @@ -4,14 +4,18 @@ * * Basic functions to load/save rRES resource files * -* External libs: -* tinfl - DEFLATE decompression functions +* CONFIGURATION: +* +* #define RREM_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. * -* Module Configuration Flags: +* DEPENDENCIES: +* tinfl - DEFLATE decompression functions * -* #define RREM_IMPLEMENTATION -* Generates the implementation of the library into the included file. * +* LICENSE: zlib/libpng * * Copyright (c) 2016-2017 Ramon Santamaria (@raysan5) * @@ -81,7 +85,7 @@ RRES_TYPE_VERTEX, RRES_TYPE_TEXT, RRES_TYPE_FONT_IMAGE, - RRES_TYPE_FONT_DATA, // Character { int value, recX, recY, recWidth, recHeight, offsetX, offsetY, xAdvance } + RRES_TYPE_FONT_CHARDATA, // Character { int value, recX, recY, recWidth, recHeight, offsetX, offsetY, xAdvance } RRES_TYPE_DIRECTORY } RRESDataType; @@ -243,7 +247,7 @@ static void *DecompressData(const unsigned char *data, unsigned long compSize, i // NOTE: Returns uncompressed data with parameters, search resource by id RRESDEF RRES LoadResource(const char *fileName, int rresId) { - RRES rres; + RRES rres = { 0 }; RRESFileHeader fileHeader; RRESInfoHeader infoHeader; diff --git a/src/shapes.c b/src/shapes.c index 83b80182..a42b0551 100644 --- a/src/shapes.c +++ b/src/shapes.c @@ -1,17 +1,14 @@ /********************************************************************************************** * -* raylib.shapes -* -* Basic functions to draw 2d Shapes and check collisions -* -* DEPENDENCIES: -* rlgl - raylib OpenGL abstraction layer +* raylib.shapes - Basic functions to draw 2d Shapes and check collisions * * CONFIGURATION: * * #define SUPPORT_QUADS_ONLY +* Draw shapes using only QUADS, vertex are accumulated in QUADS arrays (like textures) * - #define SUPPORT_TRIANGLES_ONLY +* #define SUPPORT_TRIANGLES_ONLY +* Draw shapes using only TRIANGLES, vertex are accumulated in TRIANGLES arrays * * * LICENSE: zlib/libpng diff --git a/src/text.c b/src/text.c index 4deae25c..6f18b391 100644 --- a/src/text.c +++ b/src/text.c @@ -1,14 +1,22 @@ /********************************************************************************************** * -* raylib.text +* raylib.text - Basic functions to load SpriteFonts and draw Text * -* Basic functions to load SpriteFonts and draw Text +* CONFIGURATION: * -* External libs: +* #define SUPPORT_FILEFORMAT_FNT +* #define SUPPORT_FILEFORMAT_TTF / INCLUDE_STB_TRUETYPE +* #define SUPPORT_FILEFORMAT_IMAGE_FONT +* Selected desired fileformats to be supported for loading. Some of those formats are +* supported by default, to remove support, just comment unrequired #define in this module +* +* #define INCLUDE_DEFAULT_FONT / SUPPORT_DEFAULT_FONT +* +* DEPENDENCIES: * stb_truetype - Load TTF file and rasterize characters data * -* Module Configuration Flags: -* ... +* +* LICENSE: zlib/libpng * * Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * @@ -262,30 +270,28 @@ SpriteFont LoadSpriteFont(const char *fileName) else if (strcmp(GetExtension(fileName),"rres") == 0) { // TODO: Read multiple resource blocks from file (RRES_FONT_IMAGE, RRES_FONT_CHARDATA) - RRESData rres = LoadResource(fileName); + RRES rres = LoadResource(fileName, 0); // Load sprite font texture - /* - if (rres.type == RRES_FONT_IMAGE) + if (rres[0].type == RRES_TYPE_FONT_IMAGE) { // NOTE: Parameters for RRES_FONT_IMAGE type are: width, height, format, mipmaps - Image image = LoadImagePro(rres.data, rres.param1, rres.param2, rres.param3); + Image image = LoadImagePro(rres[0].data, rres[0].param1, rres[0].param2, rres[0].param3); spriteFont.texture = LoadTextureFromImage(image); UnloadImage(image); } // Load sprite characters data - if (rres.type == RRES_FONT_CHARDATA) + if (rres[1].type == RRES_TYPE_FONT_CHARDATA) { // NOTE: Parameters for RRES_FONT_CHARDATA type are: fontSize, charsCount - spriteFont.baseSize = rres.param1; - spriteFont.charsCount = rres.param2; - spriteFont.chars = rres.data; + spriteFont.baseSize = rres[1].param1; + spriteFont.charsCount = rres[1].param2; + spriteFont.chars = rres[1].data; } - */ // TODO: Do not free rres.data memory (chars info data!) - UnloadResource(rres); + //UnloadResource(rres[0]); } else { diff --git a/src/textures.c b/src/textures.c index 5b2e4775..7db3bf56 100644 --- a/src/textures.c +++ b/src/textures.c @@ -1,16 +1,35 @@ /********************************************************************************************** * -* raylib.textures +* raylib.textures - Basic functions to load and draw Textures (2d) * -* Basic functions to load and draw Textures (2d) +* CONFIGURATION: * -* External libs: +* #define SUPPORT_STB_IMAGE / INCLUDE_STB_IMAGE +* +* #define SUPPORT_FILEFORMAT_BMP / SUPPORT_LOAD_BMP +* #define SUPPORT_FILEFORMAT_PNG / SUPPORT_LOAD_PNG +* #define SUPPORT_FILEFORMAT_TGA +* #define SUPPORT_FILEFORMAT_JPG / ENABLE_LOAD_JPG +* #define SUPPORT_FILEFORMAT_GIF +* #define SUPPORT_FILEFORMAT_HDR +* #define SUPPORT_FILEFORMAT_DDS / ENABLE_LOAD_DDS +* #define SUPPORT_FILEFORMAT_PKM +* #define SUPPORT_FILEFORMAT_KTX +* #define SUPPORT_FILEFORMAT_PVR +* #define SUPPORT_FILEFORMAT_ASTC +* Selected desired fileformats to be supported for loading. Some of those formats are +* supported by default, to remove support, just comment unrequired #define in this module +* +* #define SUPPORT_IMAGE_RESIZE / INCLUDE_STB_IMAGE_RESIZE +* #define SUPPORT_IMAGE_MANIPULATION +* +* DEPENDENCIES: * stb_image - Multiple image formats loading (JPEG, PNG, BMP, TGA, PSD, GIF, PIC) * NOTE: stb_image has been slightly modified to support Android platform. * stb_image_resize - Multiple image resize algorythms * -* Module Configuration Flags: -* ... +* +* LICENSE: zlib/libpng * * Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * @@ -143,11 +162,11 @@ Image LoadImage(const char *fileName) else if (strcmp(GetExtension(fileName),"astc") == 0) image = LoadASTC(fileName); else if (strcmp(GetExtension(fileName),"rres") == 0) { - RRESData rres = LoadResource(fileName); + RRES rres = LoadResource(fileName, 0); - // NOTE: Parameters for RRES_IMAGE type are: width, height, format, mipmaps + // NOTE: Parameters for RRES_TYPE_IMAGE are: width, height, format, mipmaps - if (rres.type == RRES_IMAGE) image = LoadImagePro(rres.data, rres.param1, rres.param2, rres.param3); + if (rres[0].type == RRES_TYPE_IMAGE) image = LoadImagePro(rres[0].data, rres[0].param1, rres[0].param2, rres[0].param3); else TraceLog(WARNING, "[%s] Resource file does not contain image data", fileName); UnloadResource(rres); diff --git a/src/utils.c b/src/utils.c index e5e05955..9a2a723a 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,16 +1,23 @@ /********************************************************************************************** * -* raylib.utils +* raylib.utils - Some common utility functions * -* Some utility functions +* CONFIGURATION: * -* External libs: -* tinfl - zlib DEFLATE algorithm decompression +* #define SUPPORT_SAVE_PNG +* Enable saving PNG fileformat +* NOTE: Requires stb_image_write library +* +* #define SUPPORT_SAVE_BMP +* +* #define DO_NOT_TRACE_DEBUG_MSGS +* Avoid showing DEBUG TraceLog() messages +* +* DEPENDENCIES: * stb_image_write - PNG writting functions * -* Module Configuration Flags: -* DO_NOT_TRACE_DEBUG_MSGS - Avoid showing DEBUG TraceLog() messages * +* LICENSE: zlib/libpng * * Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * -- cgit v1.2.3