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.c | 532 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 273 insertions(+), 259 deletions(-) (limited to 'src/physac.c') diff --git a/src/physac.c b/src/physac.c index 4c50dd41..13247117 100644 --- a/src/physac.c +++ b/src/physac.c @@ -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 * @@ -29,329 +29,343 @@ #include "raylib.h" #endif -#include -#include // Required for: malloc(), free() +#include // Declares malloc() and free() for memory management +#include // abs() and fminf() //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define DECIMAL_FIX 0.26f // Decimal margin for collision checks (avoid rigidbodies shake) +#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 //---------------------------------------------------------------------------------- // Types and Structures Definition +// NOTE: Below types are required for PHYSAC_STANDALONE usage //---------------------------------------------------------------------------------- // ... //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static Collider *colliders; // Colliders array, dynamically allocated at runtime -static Rigidbody *rigidbodies; // Rigitbody array, dynamically allocated at runtime -static bool collisionChecker; - -static int maxElements; // Max physic elements to compute -static bool enabled; // Physics enabled? (true by default) -static Vector2 gravity; // Gravity value used for physic calculations +static PhysicObject *physicObjects[MAX_PHYSIC_OBJECTS]; // Physic objects pool +static int physicObjectsCount; // Counts current enabled physic objects //---------------------------------------------------------------------------------- -// Module specific Functions Declarations +// Module specific Functions Declaration //---------------------------------------------------------------------------------- -static float Vector2Length(Vector2 vector); -static float Vector2Distance(Vector2 a, Vector2 b); -static void Vector2Normalize(Vector2 *vector); +static float Vector2DotProduct(Vector2 v1, Vector2 v2); // Returns the dot product of two Vector2 //---------------------------------------------------------------------------------- -// Module Functions Definitions +// Module Functions Definition //---------------------------------------------------------------------------------- -void InitPhysics(int maxPhysicElements) -{ - maxElements = maxPhysicElements; - - colliders = (Collider *)malloc(maxElements*sizeof(Collider)); - rigidbodies = (Rigidbody *)malloc(maxElements*sizeof(Rigidbody)); - - for (int i = 0; i < maxElements; i++) - { - colliders[i].enabled = false; - colliders[i].bounds = (Rectangle){ 0, 0, 0, 0 }; - colliders[i].radius = 0; - - rigidbodies[i].enabled = false; - rigidbodies[i].mass = 0.0f; - rigidbodies[i].velocity = (Vector2){ 0.0f, 0.0f }; - rigidbodies[i].acceleration = (Vector2){ 0.0f, 0.0f }; - rigidbodies[i].isGrounded = false; - rigidbodies[i].isContact = false; - rigidbodies[i].friction = 0.0f; - } - - collisionChecker = false; - enabled = true; - - // NOTE: To get better results, gravity needs to be 1:10 from original parameter - gravity = (Vector2){ 0.0f, -9.81f/10.0f }; // By default, standard gravity -} - -void UnloadPhysics() -{ - free(colliders); - free(rigidbodies); -} - -void AddCollider(int index, Collider collider) -{ - colliders[index] = collider; -} -void AddRigidbody(int index, Rigidbody rigidbody) +// Initializes pointers array (just pointers, fixed size) +void InitPhysics() { - rigidbodies[index] = rigidbody; + // Initialize physics variables + physicObjectsCount = 0; } -void ApplyPhysics(int index, Vector2 *position) +// Update physic objects, calculating physic behaviours and collisions detection +void UpdatePhysics() { - if (rigidbodies[index].enabled) + // Reset all physic objects is grounded state + for(int i = 0; i < physicObjectsCount; i++) { - // Apply friction to acceleration - if (rigidbodies[index].acceleration.x > DECIMAL_FIX) - { - rigidbodies[index].acceleration.x -= rigidbodies[index].friction; - } - else if (rigidbodies[index].acceleration.x < -DECIMAL_FIX) - { - rigidbodies[index].acceleration.x += rigidbodies[index].friction; - } - else - { - rigidbodies[index].acceleration.x = 0; - } - - if (rigidbodies[index].acceleration.y > DECIMAL_FIX / 2) - { - rigidbodies[index].acceleration.y -= rigidbodies[index].friction; - } - else if (rigidbodies[index].acceleration.y < -DECIMAL_FIX / 2) - { - rigidbodies[index].acceleration.y += rigidbodies[index].friction; - } - else - { - rigidbodies[index].acceleration.y = 0; - } - - // Apply friction to velocity - if (rigidbodies[index].isGrounded) - { - if (rigidbodies[index].velocity.x > DECIMAL_FIX) - { - rigidbodies[index].velocity.x -= rigidbodies[index].friction; - } - else if (rigidbodies[index].velocity.x < -DECIMAL_FIX) - { - rigidbodies[index].velocity.x += rigidbodies[index].friction; - } - else - { - rigidbodies[index].velocity.x = 0; - } - } - - if (rigidbodies[index].velocity.y > DECIMAL_FIX / 2) - { - rigidbodies[index].velocity.y -= rigidbodies[index].friction; - } - else if (rigidbodies[index].velocity.y < -DECIMAL_FIX / 2) - { - rigidbodies[index].velocity.y += rigidbodies[index].friction; - } - else - { - rigidbodies[index].velocity.y = 0; - } - - // Apply gravity - rigidbodies[index].velocity.y += gravity.y; - rigidbodies[index].velocity.x += gravity.x; - - // Apply acceleration - rigidbodies[index].velocity.y += rigidbodies[index].acceleration.y; - rigidbodies[index].velocity.x += rigidbodies[index].acceleration.x; - - // Update position vector - position->x += rigidbodies[index].velocity.x; - position->y -= rigidbodies[index].velocity.y; - - // Update collider bounds - colliders[index].bounds.x = position->x; - colliders[index].bounds.y = position->y; - - // Check collision with other colliders - collisionChecker = false; - rigidbodies[index].isContact = false; - for (int j = 0; j < maxElements; j++) + if(physicObjects[i]->rigidbody.enabled) physicObjects[i]->rigidbody.isGrounded = false; + } + + for(int steps = 0; steps < PHYSICS_STEPS; steps++) + { + for(int i = 0; i < physicObjectsCount; i++) { - if (index != j) + if(physicObjects[i]->enabled) { - if (colliders[index].enabled && colliders[j].enabled) + // Update physic behaviour + if(physicObjects[i]->rigidbody.enabled) { - if (colliders[index].type == COLLIDER_RECTANGLE) + // 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 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 gravity to velocity + if (physicObjects[i]->rigidbody.applyGravity) physicObjects[i]->rigidbody.velocity.y += PHYSICS_GRAVITY/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 (colliders[j].type == COLLIDER_RECTANGLE) + if (physicObjects[k]->collider.enabled && i != k) { - if (CheckCollisionRecs(colliders[index].bounds, colliders[j].bounds)) + // Check if colliders are overlapped + if (CheckCollisionRecs(physicObjects[i]->collider.bounds, physicObjects[k]->collider.bounds)) { - collisionChecker = true; + // 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 }; + + // 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); + + // Define overlapping and penetration attributes + Vector2 overlap; + float penetrationDepth = 0.0f; - if ((colliders[index].bounds.y + colliders[index].bounds.height <= colliders[j].bounds.y) == false) + // 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) { - rigidbodies[index].isContact = true; + // 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; + } + } + } + + // 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 = { 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; + + // 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; + } + + 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; + + // 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); + } + } } - } - } - else - { - if (CheckCollisionCircleRec((Vector2){colliders[j].bounds.x, colliders[j].bounds.y}, colliders[j].radius, colliders[index].bounds)) - { - collisionChecker = true; - } - } - } - else - { - if (colliders[j].type == COLLIDER_RECTANGLE) - { - if (CheckCollisionCircleRec((Vector2){colliders[index].bounds.x, colliders[index].bounds.y}, colliders[index].radius, colliders[j].bounds)) - { - collisionChecker = true; - } - } - else - { - if (CheckCollisionCircles((Vector2){colliders[j].bounds.x, colliders[j].bounds.y}, colliders[j].radius, (Vector2){colliders[index].bounds.x, colliders[index].bounds.y}, colliders[index].radius)) - { - collisionChecker = true; } } } } } } - - // Update grounded rigidbody state - rigidbodies[index].isGrounded = collisionChecker; - - // Set grounded state if needed (fix overlap and set y velocity) - if (collisionChecker && rigidbodies[index].velocity.y != 0) - { - position->y += rigidbodies[index].velocity.y; - rigidbodies[index].velocity.y = -rigidbodies[index].velocity.y * rigidbodies[index].bounciness; - } - - if (rigidbodies[index].isContact) - { - position->x -= rigidbodies[index].velocity.x; - rigidbodies[index].velocity.x = rigidbodies[index].velocity.x; - } } } -void SetRigidbodyEnabled(int index, bool state) -{ - rigidbodies[index].enabled = state; -} - -void SetRigidbodyVelocity(int index, Vector2 velocity) -{ - rigidbodies[index].velocity.x = velocity.x; - rigidbodies[index].velocity.y = velocity.y; -} - -void SetRigidbodyAcceleration(int index, Vector2 acceleration) +// Unitialize all physic objects and empty the objects pool +void ClosePhysics() { - rigidbodies[index].acceleration.x = acceleration.x; - rigidbodies[index].acceleration.y = acceleration.y; + // Free all dynamic memory allocations + for (int i = 0; i < physicObjectsCount; i++) free(physicObjects[i]); + + // Reset enabled physic objects count + physicObjectsCount = 0; } -void AddRigidbodyForce(int index, Vector2 force) +// Create a new physic object dinamically, initialize it and add to pool +PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale) { - rigidbodies[index].acceleration.x = force.x / rigidbodies[index].mass; - rigidbodies[index].acceleration.y = force.y / rigidbodies[index].mass; + // Allocate dynamic memory + PhysicObject *obj = (PhysicObject *)malloc(sizeof(PhysicObject)); + + // 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 = false; + 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; } -void AddForceAtPosition(Vector2 position, float intensity, float radius) +// Destroy a specific physic object and take it out of the list +void DestroyPhysicObject(PhysicObject *pObj) { - for(int i = 0; i < maxElements; i++) + // Free dynamic memory allocation + free(physicObjects[pObj->id]); + + // Remove *obj from the pointers array + for (int i = pObj->id; i < physicObjectsCount; i++) { - if(rigidbodies[i].enabled) + // Resort all the following pointers of the array + if ((i + 1) < physicObjectsCount) { - // Get position from its collider - Vector2 pos = {colliders[i].bounds.x, colliders[i].bounds.y}; - - // Get distance between rigidbody position and target position - float distance = Vector2Distance(position, pos); - - if(distance <= radius) - { - // Calculate force based on direction - Vector2 force = {colliders[i].bounds.x - position.x, colliders[i].bounds.y - position.y}; - - // Normalize the direction vector - Vector2Normalize(&force); - - // Invert y value - force.y *= -1; - - // Apply intensity and distance - force = (Vector2){force.x * intensity / distance, force.y * intensity / distance}; - - // Add calculated force to the rigidbodies - AddRigidbodyForce(i, force); - } + physicObjects[i] = physicObjects[i + 1]; + physicObjects[i]->id = physicObjects[i + 1]->id; } + else free(physicObjects[i]); } + + // Decrease enabled physic objects count + physicObjectsCount--; } -void SetColliderEnabled(int index, bool state) -{ - colliders[index].enabled = state; -} - -Collider GetCollider(int index) +// Convert Transform data type to Rectangle (position and scale) +Rectangle TransformToRectangle(Transform transform) { - return colliders[index]; + return (Rectangle){transform.position.x, transform.position.y, transform.scale.x, transform.scale.y}; } -Rigidbody GetRigidbody(int index) +// Draw physic object information at screen position +void DrawPhysicObjectInfo(PhysicObject *pObj, Vector2 position, int fontSize) { - return rigidbodies[index]; + // 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 Definitions +// Module specific Functions Definition //---------------------------------------------------------------------------------- -static float Vector2Length(Vector2 vector) -{ - return sqrt((vector.x * vector.x) + (vector.y * vector.y)); -} -static float Vector2Distance(Vector2 a, Vector2 b) +// Returns the dot product of two Vector2 +static float Vector2DotProduct(Vector2 v1, Vector2 v2) { - Vector2 vector = {b.x - a.x, b.y - a.y}; - return sqrt((vector.x * vector.x) + (vector.y * vector.y)); -} + float result; -static void Vector2Normalize(Vector2 *vector) -{ - float length = Vector2Length(*vector); - - if (length != 0.0f) - { - vector->x /= length; - vector->y /= length; - } - else - { - vector->x = 0.0f; - vector->y = 0.0f; - } + result = v1.x*v2.x + v1.y*v2.y; + + return result; } -- 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.c') 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 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.c') 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 ea7afc8ec835040d84d79ae318f7aebb9f1e189c Mon Sep 17 00:00:00 2001 From: victorfisac Date: Wed, 23 Mar 2016 22:47:39 +0100 Subject: Fix spacing and some comments --- src/physac.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/physac.c') diff --git a/src/physac.c b/src/physac.c index e3f956ba..ed707474 100644 --- a/src/physac.c +++ b/src/physac.c @@ -30,13 +30,13 @@ #endif #include // Declares malloc() and free() for memory management -#include // abs() and fminf() +#include // Declares cos(), sin(), abs() and fminf() for math operations //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define MAX_PHYSIC_OBJECTS 256 -#define PHYSICS_STEPS 450 +#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 @@ -145,11 +145,11 @@ void UpdatePhysics() Vector2 direction = { 0.0f, 0.0f }; float penetrationDepth = 0.0f; - switch(physicObjects[i]->collider.type) + switch (physicObjects[i]->collider.type) { case COLLIDER_RECTANGLE: { - switch(physicObjects[k]->collider.type) + switch (physicObjects[k]->collider.type) { case COLLIDER_RECTANGLE: { @@ -266,7 +266,7 @@ void UpdatePhysics() } break; case COLLIDER_CIRCLE: { - switch(physicObjects[k]->collider.type) + switch (physicObjects[k]->collider.type) { case COLLIDER_RECTANGLE: { @@ -532,14 +532,14 @@ void ApplyForce(PhysicObject *pObj, Vector2 force) // Apply radial force to all physic objects in range void ApplyForceAtPosition(Vector2 position, float force, float radius) { - for(int i = 0; i < physicObjectsCount; i++) + for (int i = 0; i < physicObjectsCount; i++) { - if(physicObjects[i]->rigidbody.enabled) + 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) + if (physicObjects[i]->collider.type == COLLIDER_RECTANGLE) { distance.x += physicObjects[i]->transform.scale.x/2; distance.y += physicObjects[i]->transform.scale.y/2; @@ -548,7 +548,7 @@ void ApplyForceAtPosition(Vector2 position, float force, float radius) float distanceLength = Vector2Length(distance); // Check if physic object is in force range - if(distanceLength <= radius) + if (distanceLength <= radius) { // Normalize force direction distance.x /= distanceLength; -- cgit v1.2.3 From c9e30f77540e9693ddecffc353964eb71e854842 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Fri, 20 May 2016 10:53:31 +0200 Subject: Review struct typedef to avoid pointers for users --- examples/physics_basic_rigidbody.c | 17 +++++++++-------- examples/physics_forces.c | 14 +++++++------- src/physac.c | 12 ++++++------ src/raylib.h | 12 ++++++------ 4 files changed, 28 insertions(+), 27 deletions(-) (limited to 'src/physac.c') diff --git a/examples/physics_basic_rigidbody.c b/examples/physics_basic_rigidbody.c index 917813ad..cd09f070 100644 --- a/examples/physics_basic_rigidbody.c +++ b/examples/physics_basic_rigidbody.c @@ -30,26 +30,26 @@ int main() bool isDebug = false; // Create rectangle physic object - PhysicObject *rectangle = CreatePhysicObject((Vector2){ screenWidth*0.25f, screenHeight/2 }, 0.0f, (Vector2){ 75, 50 }); + PhysicObject rectangle = CreatePhysicObject((Vector2){ screenWidth*0.25f, screenHeight/2 }, 0.0f, (Vector2){ 75, 50 }); rectangle->rigidbody.enabled = true; // Enable physic object rigidbody behaviour rectangle->rigidbody.applyGravity = true; rectangle->rigidbody.friction = 0.1f; rectangle->rigidbody.bounciness = 6.0f; // Create square physic object - PhysicObject *square = CreatePhysicObject((Vector2){ screenWidth*0.75f, screenHeight/2 }, 0.0f, (Vector2){ 50, 50 }); + PhysicObject square = CreatePhysicObject((Vector2){ screenWidth*0.75f, screenHeight/2 }, 0.0f, (Vector2){ 50, 50 }); square->rigidbody.enabled = true; // Enable physic object rigidbody behaviour square->rigidbody.applyGravity = true; square->rigidbody.friction = 0.1f; // Create walls physic objects - PhysicObject *floor = CreatePhysicObject((Vector2){ screenWidth/2, screenHeight*0.95f }, 0.0f, (Vector2){ screenWidth*0.9f, 100 }); - PhysicObject *leftWall = CreatePhysicObject((Vector2){ 0.0f, screenHeight/2 }, 0.0f, (Vector2){ screenWidth*0.1f, screenHeight }); - PhysicObject *rightWall = CreatePhysicObject((Vector2){ screenWidth, screenHeight/2 }, 0.0f, (Vector2){ screenWidth*0.1f, screenHeight }); - PhysicObject *roof = CreatePhysicObject((Vector2){ screenWidth/2, screenHeight*0.05f }, 0.0f, (Vector2){ screenWidth*0.9f, 100 }); + PhysicObject floor = CreatePhysicObject((Vector2){ screenWidth/2, screenHeight*0.95f }, 0.0f, (Vector2){ screenWidth*0.9f, 100 }); + PhysicObject leftWall = CreatePhysicObject((Vector2){ 0.0f, screenHeight/2 }, 0.0f, (Vector2){ screenWidth*0.1f, screenHeight }); + PhysicObject rightWall = CreatePhysicObject((Vector2){ screenWidth, screenHeight/2 }, 0.0f, (Vector2){ screenWidth*0.1f, screenHeight }); + PhysicObject roof = CreatePhysicObject((Vector2){ screenWidth/2, screenHeight*0.05f }, 0.0f, (Vector2){ screenWidth*0.9f, 100 }); // Create pplatform physic object - PhysicObject *platform = CreatePhysicObject((Vector2){ screenWidth/2, screenHeight*0.7f }, 0.0f, (Vector2){ screenWidth*0.25f, 20 }); + PhysicObject platform = CreatePhysicObject((Vector2){ screenWidth/2, screenHeight*0.7f }, 0.0f, (Vector2){ screenWidth*0.25f, 20 }); //-------------------------------------------------------------------------------------- @@ -114,7 +114,8 @@ int main() // De-Initialization //-------------------------------------------------------------------------------------- - ClosePhysics(); // Unitialize physics module + 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 74b40d57..f4eefa05 100644 --- a/examples/physics_forces.c +++ b/examples/physics_forces.c @@ -17,7 +17,7 @@ #define LINE_LENGTH 75 #define TRIANGLE_LENGTH 12 -void DrawRigidbodyCircle(PhysicObject *obj, Color color); +void DrawRigidbodyCircle(PhysicObject obj, Color color); int main() { @@ -36,7 +36,7 @@ int main() bool isDebug = false; // Create rectangle physic objects - PhysicObject *rectangles[3]; + PhysicObject rectangles[3]; for (int i = 0; i < 3; i++) { rectangles[i] = CreatePhysicObject((Vector2){ screenWidth/4*(i+1), (((i % 2) == 0) ? (screenHeight/3) : (screenHeight/1.5f)) }, 0.0f, (Vector2){ 50, 50 }); @@ -46,7 +46,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]; + PhysicObject circles[3]; for (int i = 0; i < 3; i++) { circles[i] = CreatePhysicObject((Vector2){ screenWidth/4*(i+1), (((i % 2) == 0) ? (screenHeight/1.5f) : (screenHeight/4)) }, 0.0f, (Vector2){ 0, 0 }); @@ -57,10 +57,10 @@ int main() } // Create walls physic objects - PhysicObject *leftWall = CreatePhysicObject((Vector2){ -25, screenHeight/2 }, 0.0f, (Vector2){ 50, screenHeight }); - PhysicObject *rightWall = CreatePhysicObject((Vector2){ screenWidth + 25, screenHeight/2 }, 0.0f, (Vector2){ 50, screenHeight }); - PhysicObject *topWall = CreatePhysicObject((Vector2){ screenWidth/2, -25 }, 0.0f, (Vector2){ screenWidth, 50 }); - PhysicObject *bottomWall = CreatePhysicObject((Vector2){ screenWidth/2, screenHeight + 25 }, 0.0f, (Vector2){ screenWidth, 50 }); + PhysicObject leftWall = CreatePhysicObject((Vector2){ -25, screenHeight/2 }, 0.0f, (Vector2){ 50, screenHeight }); + PhysicObject rightWall = CreatePhysicObject((Vector2){ screenWidth + 25, screenHeight/2 }, 0.0f, (Vector2){ 50, screenHeight }); + PhysicObject topWall = CreatePhysicObject((Vector2){ screenWidth/2, -25 }, 0.0f, (Vector2){ screenWidth, 50 }); + PhysicObject bottomWall = CreatePhysicObject((Vector2){ screenWidth/2, screenHeight + 25 }, 0.0f, (Vector2){ screenWidth, 50 }); //-------------------------------------------------------------------------------------- diff --git a/src/physac.c b/src/physac.c index ed707474..181488ac 100644 --- a/src/physac.c +++ b/src/physac.c @@ -49,7 +49,7 @@ //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static PhysicObject *physicObjects[MAX_PHYSIC_OBJECTS]; // Physic objects pool +static PhysicObject physicObjects[MAX_PHYSIC_OBJECTS]; // Physic objects pool static int physicObjectsCount; // Counts current enabled physic objects static Vector2 gravityForce; // Gravity force @@ -463,10 +463,10 @@ void ClosePhysics() } // Create a new physic object dinamically, initialize it and add to pool -PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale) +PhysicObject CreatePhysicObject(Vector2 position, float rotation, Vector2 scale) { // Allocate dynamic memory - PhysicObject *obj = (PhysicObject *)malloc(sizeof(PhysicObject)); + PhysicObject obj = (PhysicObject)malloc(sizeof(PhysicObjectData)); // Initialize physic object values with generic values obj->id = physicObjectsCount; @@ -498,7 +498,7 @@ PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale } // Destroy a specific physic object and take it out of the list -void DestroyPhysicObject(PhysicObject *pObj) +void DestroyPhysicObject(PhysicObject pObj) { // Free dynamic memory allocation free(physicObjects[pObj->id]); @@ -520,7 +520,7 @@ void DestroyPhysicObject(PhysicObject *pObj) } // Apply directional force to a physic object -void ApplyForce(PhysicObject *pObj, Vector2 force) +void ApplyForce(PhysicObject pObj, Vector2 force) { if (pObj->rigidbody.enabled) { @@ -571,7 +571,7 @@ Rectangle TransformToRectangle(Transform transform) } // Draw physic object information at screen position -void DrawPhysicObjectInfo(PhysicObject *pObj, Vector2 position, int fontSize) +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); diff --git a/src/raylib.h b/src/raylib.h index 32cd54a6..fc1914bb 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -535,13 +535,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 @@ -856,14 +856,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 //------------------------------------------------------------------------------------ // Audio Loading and Playing Functions (Module: audio) -- cgit v1.2.3 From 0a27525a4ba2ca9f8f6c4e723b50411549d6c558 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Wed, 1 Jun 2016 14:01:35 +0200 Subject: Dependencies review Checking some files to be converted to header-only --- src/camera.c | 2 +- src/gestures.c | 10 +++---- src/physac.c | 4 +-- src/raygui.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/raygui.h | 43 ++++++++++++++++++++++++++-- 5 files changed, 130 insertions(+), 17 deletions(-) (limited to 'src/physac.c') diff --git a/src/camera.c b/src/camera.c index 8e5c527e..11571cca 100644 --- a/src/camera.c +++ b/src/camera.c @@ -30,7 +30,7 @@ #include "raylib.h" #endif -#include +#include // Required for: sqrt(), sin(), cos() //---------------------------------------------------------------------------------- // Defines and Macros diff --git a/src/gestures.c b/src/gestures.c index d72aaf4e..d3b85d12 100644 --- a/src/gestures.c +++ b/src/gestures.c @@ -28,19 +28,19 @@ #if defined(GESTURES_STANDALONE) #include "gestures.h" #else - #include "raylib.h" // Required for typedef(s): Vector2, Gestures + #include "raylib.h" // Required for: Vector2, Gestures #endif -#include // Used for: atan2(), sqrt() -#include // Defines int32_t, int64_t +#include // Required for: atan2(), sqrt() +#include // Required for: uint64_t #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(__linux) - #include // Declares storage size of ‘now’ - #include // Used for clock functions + #include // Required for: timespec + #include // Required for: clock_gettime() #endif //---------------------------------------------------------------------------------- diff --git a/src/physac.c b/src/physac.c index 181488ac..eed2f26e 100644 --- a/src/physac.c +++ b/src/physac.c @@ -29,8 +29,8 @@ #include "raylib.h" #endif -#include // Declares malloc() and free() for memory management -#include // Declares cos(), sin(), abs() and fminf() for math operations +#include // Required for: malloc(), free() +#include // Required for: cos(), sin(), abs(), fminf() //---------------------------------------------------------------------------------- // Defines and Macros diff --git a/src/raygui.c b/src/raygui.c index 95cea0b6..40d7b265 100644 --- a/src/raygui.c +++ b/src/raygui.c @@ -5,6 +5,13 @@ * Initial design by Kevin Gato and Daniel Nicolás * Reviewed by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria (@raysan5) * +* The following functions from raylib are required for drawing and input reading: + GetColor(), GetHexValue() --> Used on SetStyleProperty() + MeasureText(), GetDefaultFont() + DrawRectangleRec(), DrawRectangle(), DrawText(), DrawLine() + GetMousePosition(), (), IsMouseButtonDown(), IsMouseButtonReleased() + 'FormatText(), IsKeyDown(), 'IsMouseButtonUp() +* * 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. * @@ -22,12 +29,20 @@ * **********************************************************************************************/ -#include "raygui.h" +#define RAYGUI_STANDALONE // NOTE: To use the raygui module as standalone lib, just uncomment this line + // NOTE: Some external funtions are required for drawing and input management + +#if defined(RAYGUI_STANDALONE) + #include "raygui.h" +#else + #include "raylib.h" +#endif -#include -#include -#include // Required for malloc(), free() -#include // Required for strcmp() +#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf() + // NOTE: Those functions are only used in SaveGuiStyle() and LoadGuiStyle() + +#include // Required for: malloc(), free() +#include // Required for: strcmp() //---------------------------------------------------------------------------------- // Defines and Macros @@ -157,6 +172,21 @@ static int style[NUM_PROPERTIES] = { //---------------------------------------------------------------------------------- static Color ColorMultiply(Color baseColor, float value); +#if defined RAYGUI_STANDALONE +static Color GetColor(int hexValue); // Returns a Color struct from hexadecimal value +static int GetHexValue(Color color); // Returns hexadecimal value for a Color +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle + +// NOTE: raygui depend on some raylib input and drawing functions +// TODO: Set your own input functions (used in ProcessCamera()) +static Vector2 GetMousePosition() { return (Vector2){ 0.0f, 0.0f }; } +static int IsMouseButtonDown(int button) { return 0; } +static int IsMouseButtonPressed(int button) { return 0; } +static int IsMouseButtonReleased(int button) { return 0; } +static int IsMouseButtonUp(int button) { return 0; } +static int IsKeyDown(int key) { return 0; } +#endif + //---------------------------------------------------------------------------------- // Module Functions Definition //---------------------------------------------------------------------------------- @@ -164,7 +194,7 @@ static Color ColorMultiply(Color baseColor, float value); // Label element, show text void GuiLabel(Rectangle bounds, const char *text) { - GuiLabelEx(bounds,text, GetColor(style[LABEL_TEXT_COLOR]), BLANK, BLANK); + GuiLabelEx(bounds, text, GetColor(style[LABEL_TEXT_COLOR]), BLANK, BLANK); } // Label element extended, configurable colors @@ -1051,4 +1081,48 @@ static Color ColorMultiply(Color baseColor, float value) multColor.b += (255 - multColor.b)*value; return multColor; -} \ No newline at end of file +} + +#if defined (RAYGUI_STANDALONE) +// Returns a Color struct from hexadecimal value +static Color GetColor(int hexValue) +{ + Color color; + + color.r = (unsigned char)(hexValue >> 24) & 0xFF; + color.g = (unsigned char)(hexValue >> 16) & 0xFF; + color.b = (unsigned char)(hexValue >> 8) & 0xFF; + color.a = (unsigned char)hexValue & 0xFF; + + return color; +} + +// Returns hexadecimal value for a Color +static int GetHexValue(Color color) +{ + return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); +} + +// Check if point is inside rectangle +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec) +{ + bool collision = false; + + if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && (point.y >= rec.y) && (point.y <= (rec.y + rec.height))) collision = true; + + return collision; +} + +// Formatting of text with variables to 'embed' +static const char *FormatText(const char *text, ...) +{ + static char buffer[MAX_FORMATTEXT_LENGTH]; + + va_list args; + va_start(args, text); + vsprintf(buffer, text, args); + va_end(args); + + return buffer; +} +#endif \ No newline at end of file diff --git a/src/raygui.h b/src/raygui.h index 6906eca7..3951e087 100644 --- a/src/raygui.h +++ b/src/raygui.h @@ -23,16 +23,55 @@ #ifndef RAYGUI_H #define RAYGUI_H -#include "raylib.h" +//#include "raylib.h" //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define NUM_PROPERTIES 98 +#define NUM_PROPERTIES 98 + +#define BLANK (Color){ 0, 0, 0, 0 } // Blank (Transparent) + +#define KEY_LEFT 263 +#define KEY_RIGHT 262 + +#define MOUSE_LEFT_BUTTON 0 + //---------------------------------------------------------------------------------- // Types and Structures Definition +// NOTE: Some types are required for RAYGUI_STANDALONE usage //---------------------------------------------------------------------------------- +#ifndef __cplusplus +// Boolean type + #ifndef true + typedef enum { false, true } bool; + #endif +#endif + +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; + +// Color type, RGBA (32bit) +typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; +} Color; + +// Rectangle type +typedef struct Rectangle { + int x; + int y; + int width; + int height; +} Rectangle; + +// Gui properties enumeration typedef enum GuiProperty { GLOBAL_BASE_COLOR = 0, GLOBAL_BORDER_COLOR, -- 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.c') 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.c') 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