From aa9353feb4bab7fbbc058afdce634c0ae24e6110 Mon Sep 17 00:00:00 2001 From: victorfisac Date: Mon, 21 Nov 2016 20:30:46 +0100 Subject: [PATCH 1/3] Updated Physac library --- src/physac.h | 2561 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 1903 insertions(+), 658 deletions(-) diff --git a/src/physac.h b/src/physac.h index dd4c4126..e807ffa6 100644 --- a/src/physac.h +++ b/src/physac.h @@ -1,8 +1,11 @@ /********************************************************************************************** * -* physac 1.0 - 2D Physics library for raylib (https://github.com/raysan5/raylib) +* Physac - 2D Physics library for videogames * -* // TODO: Description... +* Description: Physac is a small 2D physics engine written in pure C. The engine uses a fixed time-step thread loop +* to simluate physics. A physics step contains the following phases: get collision information, apply dynamics, +* collision solving and position correction. It uses a very simple struct for physic bodies with a position vector +* to be used in any 3D rendering API. * * CONFIGURATION: * @@ -24,30 +27,21 @@ * internally in the library and input management and drawing functions must be provided by * the user (check library implementation for further details). * +* #define PHYSAC_DEBUG +* Traces log messages when creating and destroying physics bodies and detects errors in physics +* calculations and reference exceptions; it is useful for debug purposes +* * #define PHYSAC_MALLOC() * #define PHYSAC_FREE() * You can define your own malloc/free implementation replacing stdlib.h malloc()/free() functions. * Otherwise it will include stdlib.h and use the C standard library malloc()/free() function. -* -* LIMITATIONS: * -* - There is a limit of 256 physic objects. -* - Physics behaviour can be unexpected using bounciness or friction values out of 0.0f - 1.0f range. -* - The module is limited to 2D axis oriented physics. -* - Physics colliders must be rectangle or circle shapes (there is not a custom polygon collider type). -* -* VERSIONS: -* -* 1.0 (14-Jun-2016) New module defines and fixed some delta time calculation bugs. -* 0.9 (09-Jun-2016) Module names review and converted to header-only. -* 0.8 (23-Mar-2016) Complete module redesign, steps-based for better physics resolution. -* 0.3 (13-Feb-2016) Reviewed to add PhysicObjects pool. -* 0.2 (03-Jan-2016) Improved physics calculations. -* 0.1 (30-Dec-2015) Initial release. +* VERY THANKS TO: +* - Ramón Santamaria (@raysan5) * * LICENSE: zlib/libpng * -* Copyright (c) 2016 Victor Fisac (main developer) and Ramon Santamaria +* Copyright (c) 2016 Victor Fisac * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -66,18 +60,18 @@ * **********************************************************************************************/ -#ifndef PHYSAC_H +#if !defined(PHYSAC_H) #define PHYSAC_H -#if !defined(RAYGUI_STANDALONE) - #include "raylib.h" -#endif - #define PHYSAC_STATIC -#ifdef PHYSAC_STATIC +// #define PHYSAC_NO_THREADS +// #define PHYSAC_STANDALONE +// #define PHYSAC_DEBUG + +#if defined(PHYSAC_STATIC) #define PHYSACDEF static // Functions just visible to module including this file #else - #ifdef __cplusplus + #if defined(__cplusplus) #define PHYSACDEF extern "C" // Functions visible from other files (no name mangling of functions in C++) #else #define PHYSACDEF extern // Functions visible from other files @@ -87,87 +81,141 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -// ... +#define PHYSAC_MAX_BODIES 64 +#define PHYSAC_MAX_MANIFOLDS 4096 +#define PHYSAC_MAX_VERTICES 24 +#define PHYSAC_CIRCLE_VERTICES 24 + +#define PHYSAC_DESIRED_DELTATIME 1.0/60.0 +#define PHYSAC_MAX_TIMESTEP 0.02 +#define PHYSAC_COLLISION_ITERATIONS 100 +#define PHYSAC_PENETRATION_ALLOWANCE 0.05f +#define PHYSAC_PENETRATION_CORRECTION 0.4f + +#define PHYSAC_PI 3.14159265358979323846 +#define PHYSAC_DEG2RAD (PHYSAC_PI/180.0f) + +#define PHYSAC_MALLOC(size) malloc(size) +#define PHYSAC_FREE(ptr) free(ptr) //---------------------------------------------------------------------------------- // Types and Structures Definition // NOTE: Below types are required for PHYSAC_STANDALONE usage //---------------------------------------------------------------------------------- #if defined(PHYSAC_STANDALONE) - #ifndef __cplusplus - // Boolean type - #ifndef true - typedef enum { false, true } bool; - #endif - #endif - // Vector2 type typedef struct Vector2 { float x; float y; } Vector2; - // Rectangle type - typedef struct Rectangle { - int x; - int y; - int width; - int height; - } Rectangle; + // Boolean type + #if !defined(_STDBOOL_H) + typedef enum { false, true } bool; + #define _STDBOOL_H + #endif #endif -typedef enum { COLLIDER_CIRCLE, COLLIDER_RECTANGLE } ColliderType; +typedef enum PhysicsShapeType { PHYSICS_CIRCLE, PHYSICS_POLYGON } PhysicsShapeType; -typedef struct Transform { - Vector2 position; - float rotation; // Radians (not used) - Vector2 scale; // Just for rectangle physic objects, for circle physic objects use collider radius and keep scale as { 0, 0 } -} Transform; +// Previously defined to be used in PhysicsShape struct as circular dependencies +typedef struct PhysicsBodyData *PhysicsBody; -typedef struct Rigidbody { - bool enabled; // Acts as kinematic state (collisions are calculated anyway) - float mass; - Vector2 acceleration; - Vector2 velocity; - bool applyGravity; - bool isGrounded; - float friction; // Normalized value - float bounciness; -} Rigidbody; +// Mat2 type (used for polygon shape rotation matrix) +typedef struct Mat2 +{ + float m00; + float m01; + float m10; + float m11; +} Mat2; -typedef struct Collider { - bool enabled; - ColliderType type; - Rectangle bounds; // Used for COLLIDER_RECTANGLE - int radius; // Used for COLLIDER_CIRCLE -} Collider; +typedef struct PolygonData { + unsigned int vertexCount; // Current used vertex and normals count + Vector2 vertices[PHYSAC_MAX_VERTICES]; // Polygon vertex positions vectors + Vector2 normals[PHYSAC_MAX_VERTICES]; // Polygon vertex normals vectors + Mat2 transform; // Vertices transform matrix 2x2 +} PolygonData; -typedef struct PhysicBodyData { - unsigned int id; - Transform transform; - Rigidbody rigidbody; - Collider collider; - bool enabled; -} PhysicBodyData, *PhysicBody; +typedef struct PhysicsShape { + PhysicsShapeType type; // Physics shape type (circle or polygon) + PhysicsBody body; // Shape physics body reference + float radius; // Circle shape radius (used for circle shapes) + PolygonData vertexData; // Polygon shape vertices position and normals data (just used for polygon shapes) +} PhysicsShape; + +typedef struct PhysicsBodyData { + unsigned int id; // Reference unique identifier + bool enabled; // Enabled dynamics state (collisions are calculated anyway) + Vector2 position; // Physics body shape pivot + Vector2 velocity; // Current linear velocity applied to position + Vector2 force; // Current linear force (reset to 0 every step) + float angularVelocity; // Current angular velocity applied to orient + float torque; // Current angular force (reset to 0 every step) + float orient; // Rotation in radians + float inertia; // Moment of inertia + float inverseInertia; // Inverse value of inertia + float mass; // Physics body mass + float inverseMass; // Inverse value of mass + float staticFriction; // Friction when the body has not movement (0 to 1) + float dynamicFriction; // Friction when the body has movement (0 to 1) + float restitution; // Restitution coefficient of the body (0 to 1) + bool useGravity; // Apply gravity force to dynamics + bool isGrounded; // Physics grounded on other body state + bool freezeOrient; // Physics rotation constraint + PhysicsShape shape; // Physics body shape information (type, radius, vertices, normals) +} PhysicsBodyData; + +typedef struct PhysicsManifoldData { + unsigned int id; // Reference unique identifier + PhysicsBody bodyA; // Manifold first physics body reference + PhysicsBody bodyB; // Manifold second physics body reference + float penetration; // Depth of penetration from collision + Vector2 normal; // Normal direction vector from 'a' to 'b' + Vector2 contacts[2]; // Points of contact during collision + unsigned int contactsCount; // Current collision number of contacts + float restitution; // Mixed restitution during collision + float dynamicFriction; // Mixed dynamic friction during collision + float staticFriction; // Mixed static friction during collision +} PhysicsManifoldData, *PhysicsManifold; + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- -PHYSACDEF void InitPhysics(Vector2 gravity); // Initializes pointers array (just pointers, fixed size) -PHYSACDEF void* PhysicsThread(void *arg); // Physics calculations thread function -PHYSACDEF void ClosePhysics(); // Unitialize all physic objects and empty the objects pool +PHYSACDEF void InitPhysics(void); // Initializes physics values, pointers and creates physics loop thread +PHYSACDEF bool IsPhysicsEnabled(void); // Returns true if physics thread is currently enabled +PHYSACDEF void SetPhysicsGravity(float x, float y); // Sets physics global gravity force +PHYSACDEF PhysicsBody CreatePhysicsBodyCircle(Vector2 pos, float radius, float density); // Creates a new circle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyRectangle(Vector2 pos, float width, float height, float density); // Creates a new rectangle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyPolygon(Vector2 pos, float radius, int sides, float density); // Creates a new polygon physics body with generic parameters +PHYSACDEF void PhysicsAddForce(PhysicsBody body, Vector2 force); // Adds a force to a physics body +PHYSACDEF void PhysicsAddTorque(PhysicsBody body, float amount); // Adds an angular force to a physics body +PHYSACDEF void PhysicsShatter(PhysicsBody body, Vector2 position, float force); // Shatters a polygon shape physics body to little physics bodies with explosion force +PHYSACDEF int GetPhysicsBodiesCount(void); // Returns the current amount of created physics bodies +PHYSACDEF PhysicsBody GetPhysicsBody(int index); // Returns a physics body of the bodies pool at a specific index +PHYSACDEF int GetPhysicsShapeType(int index); // Returns the physics body shape type (PHYSICS_CIRCLE or PHYSICS_POLYGON) +PHYSACDEF int GetPhysicsShapeVerticesCount(int index); // Returns the amount of vertices of a physics body shape +PHYSACDEF Vector2 GetPhysicsShapeVertex(PhysicsBody body, int vertex); // Returns transformed position of a body shape (body position + vertex transformed position) +PHYSACDEF void SetPhysicsBodyRotation(PhysicsBody body, float radians); // Sets physics body shape transform based on radians parameter +PHYSACDEF void DestroyPhysicsBody(PhysicsBody body); // Unitializes and destroy a physics body +PHYSACDEF void ResetPhysics(void); // Destroys created physics bodies and manifolds and resets global values +PHYSACDEF void ClosePhysics(void); // Unitializes physics pointers and closes physics loop thread -PHYSACDEF PhysicBody CreatePhysicBody(Vector2 position, float rotation, Vector2 scale); // Create a new physic body dinamically, initialize it and add to pool -PHYSACDEF void DestroyPhysicBody(PhysicBody pbody); // Destroy a specific physic body and take it out of the list - -PHYSACDEF void ApplyForce(PhysicBody pbody, Vector2 force); // Apply directional force to a physic body -PHYSACDEF void ApplyForceAtPosition(Vector2 position, float force, float radius); // Apply radial force to all physic objects in range - -PHYSACDEF Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale) +#if defined(__cplusplus) +} +#endif #endif // PHYSAC_H - /*********************************************************************************** * * PHYSAC IMPLEMENTATION @@ -176,657 +224,1854 @@ PHYSACDEF Rectangle TransformToRectangle(Transform transform); #if defined(PHYSAC_IMPLEMENTATION) -// Check if custom malloc/free functions defined, if not, using standard ones -#if !defined(PHYSAC_MALLOC) - #include // Required for: malloc(), free() - - #define PHYSAC_MALLOC(size) malloc(size) - #define PHYSAC_FREE(ptr) free(ptr) +#if !defined(PHYSAC_NO_THREADS) + #include // Required for: pthread_t, pthread_create() #endif -#include // Required for: cos(), sin(), abs(), fminf() -#include // Required for typedef unsigned long long int uint64_t, used by hi-res timer - -#ifndef PHYSAC_NO_THREADS - #include // Required for: pthread_create() +#if defined(PHYSAC_DEBUG) + #include // Required for: printf() #endif -#if defined(PLATFORM_DESKTOP) +#include // Required for: malloc(), free(), srand(), rand() +#include // Required for: cosf(), sinf(), fabs(), sqrtf() + +#if defined(_WIN32) // Functions required to query time on Windows int __stdcall QueryPerformanceCounter(unsigned long long int *lpPerformanceCount); int __stdcall QueryPerformanceFrequency(unsigned long long int *lpFrequency); -#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) - #include // Required for: timespec - #include // Required for: clock_gettime() +#elif defined(__linux) + #include // Required for: timespec + #include // Required for: clock_gettime() #endif //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define MAX_PHYSIC_BODIES 256 // Maximum available physic bodies slots in bodies pool -#define PHYSICS_TIMESTEP 0.016666 // Physics fixed time step (1/fps) -#define PHYSICS_ACCURACY 0.0001f // Velocity subtract operations round filter (friction) -#define PHYSICS_ERRORPERCENT 0.001f // Collision resolve position fix +#define min(a,b) (((a)<(b))?(a):(b)) +#define max(a,b) (((a)>(b))?(a):(b)) +#define PHYSAC_FLT_MAX 3.402823466e+38f +#define PHYSAC_EPSILON 0.000001f //---------------------------------------------------------------------------------- // Types and Structures Definition -// NOTE: Below types are required for PHYSAC_STANDALONE usage //---------------------------------------------------------------------------------- // ... //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static bool physicsThreadEnabled = false; // Physics calculations thread exit control -static uint64_t baseTime; // Base time measure for hi-res timer -static double currentTime, previousTime; // Used to track timmings -static PhysicBody physicBodies[MAX_PHYSIC_BODIES]; // Physic bodies pool -static int physicBodiesCount; // Counts current enabled physic bodies -static Vector2 gravityForce; // Gravity force +#if !defined(PHYSAC_NO_THREADS) + static pthread_t physicsThreadId; // Physics thread id +#endif +static unsigned int usedMemory = 0; // Total allocated dynamic memory +static bool physicsThreadEnabled = false; // Physics thread enabled state +static double currentTime = 0; // Current time in milliseconds +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + static double baseTime = 0; // Android and RPI platforms base time +#endif +static double startTime = 0; // Start time in milliseconds +static double deltaTime = 0; // Delta time used for physics steps +static double accumulator = 0; // Physics time step delta time accumulator +static unsigned int stepsCount = 0; // Total physics steps processed +static Vector2 gravityForce = { 0, 9.81f/1000 }; // Physics world gravity force +static PhysicsBody bodies[PHYSAC_MAX_BODIES]; // Physics bodies pointers array +static unsigned int physicsBodiesCount = 0; // Physics world current bodies counter +static PhysicsManifold contacts[PHYSAC_MAX_MANIFOLDS]; // Physics bodies pointers array +static unsigned int physicsManifoldsCount = 0; // Physics world current manifolds counter //---------------------------------------------------------------------------------- -// Module specific Functions Declaration +// Module Internal Functions Declaration //---------------------------------------------------------------------------------- -static void UpdatePhysics(double deltaTime); // Update physic objects, calculating physic behaviours and collisions detection -static void InitTimer(void); // Initialize hi-resolution timer -static double GetCurrentTime(void); // Time measure returned are microseconds -static float Vector2DotProduct(Vector2 v1, Vector2 v2); // Returns the dot product of two Vector2 -static float Vector2Length(Vector2 v); // Returns the length of a Vector2 +static PolygonData CreateRandomPolygon(float radius, int sides); // Creates a random polygon shape with max vertex distance from polygon pivot +static PolygonData CreateRectanglePolygon(Vector2 pos, Vector2 size); // Creates a rectangle polygon shape based on a min and max positions +static void *PhysicsLoop(void *arg); // Physics loop thread function +static void PhysicsStep(void); // Physics steps calculations (dynamics, collisions and position corrections) +static PhysicsManifold CreatePhysicsManifold(PhysicsBody a, PhysicsBody b); // Creates a new physics manifold to solve collision +static void DestroyPhysicsManifold(PhysicsManifold manifold); // Unitializes and destroys a physics manifold +static void SolvePhysicsManifold(PhysicsManifold manifold); // Solves a created physics manifold between two physics bodies +static void SolveCircleToCircle(PhysicsManifold manifold); // Solves collision between two circle shape physics bodies +static void SolveCircleToPolygon(PhysicsManifold manifold); // Solves collision between a circle to a polygon shape physics bodies +static void SolvePolygonToCircle(PhysicsManifold manifold); // Solves collision between a polygon to a circle shape physics bodies +static void SolvePolygonToPolygon(PhysicsManifold manifold); // Solves collision between two polygons shape physics bodies +static void IntegratePhysicsForces(PhysicsBody body); // Integrates physics forces into velocity +static void InitializePhysicsManifolds(PhysicsManifold manifold); // Initializes physics manifolds to solve collisions +static void IntegratePhysicsImpulses(PhysicsManifold manifold); // Integrates physics collisions impulses to solve collisions +static void IntegratePhysicsVelocity(PhysicsBody body); // Integrates physics velocity into position and forces +static void CorrectPhysicsPositions(PhysicsManifold manifold); // Corrects physics bodies positions based on manifolds collision information +static float FindAxisLeastPenetration(int *faceIndex, PhysicsShape shapeA, PhysicsShape shapeB); // Finds polygon shapes axis least penetration +static void FindIncidentFace(Vector2 *v0, Vector2 *v1, PhysicsShape ref, PhysicsShape inc, int index); // Finds two polygon shapes incident face +static int Clip(Vector2 normal, float clip, Vector2 *faceA, Vector2 *faceB); // Calculates clipping based on a normal and two faces +static bool BiasGreaterThan(float valueA, float valueB); // Check if values are between bias range +static Vector2 TriangleBarycenter(Vector2 v1, Vector2 v2, Vector2 v3); // Returns the barycenter of a triangle given by 3 points +static void InitTimer(void); // Initializes hi-resolution timer +static double GetCurrentTime(void); // Get current time in milliseconds +static int GetRandomNumber(int min, int max); // Returns a random number between min and max (both included) + +static void MathClamp(double *value, double min, double max); // Clamp a value in a range +static Vector2 MathCross(float value, Vector2 vector); // Returns the cross product of a vector and a value +static float MathCrossVector2(Vector2 v1, Vector2 v2); // Returns the cross product of two vectors +static float MathLenSqr(Vector2 vector); // Returns the len square root of a vector +static float MathDot(Vector2 v1, Vector2 v2); // Returns the dot product of two vectors +static inline float DistSqr(Vector2 v1, Vector2 v2); // Returns the square root of distance between two vectors +static void MathNormalize(Vector2 *vector); // Returns the normalized values of a vector +static Vector2 Vector2Add(Vector2 v1, Vector2 v2); // Returns the sum of two given vectors +static Vector2 Vector2Subtract(Vector2 v1, Vector2 v2); // Returns the subtract of two given vectors + +static Mat2 Mat2Radians(float radians); // Creates a matrix 2x2 from a given radians value +static void Mat2Set(Mat2 *matrix, float radians); // Set values from radians to a created matrix 2x2 +static Mat2 Mat2Transpose(Mat2 matrix); // Returns the transpose of a given matrix 2x2 +static Vector2 Mat2MultiplyVector2(Mat2 matrix, Vector2 vector); // Multiplies a vector by a matrix 2x2 //---------------------------------------------------------------------------------- // Module Functions Definition //---------------------------------------------------------------------------------- - -// Initializes pointers array (just pointers, fixed size) -PHYSACDEF void InitPhysics(Vector2 gravity) +// Initializes physics values, pointers and creates physics loop thread +PHYSACDEF void InitPhysics(void) { - // Initialize physics variables - physicBodiesCount = 0; - gravityForce = gravity; - - #ifndef PHYSAC_NO_THREADS // NOTE: if defined, user will need to create a thread for PhysicsThread function manually - // Create physics thread - pthread_t tid; - pthread_create(&tid, NULL, &PhysicsThread, NULL); + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] physics module initialized successfully\n"); + #endif + + #if !defined(PHYSAC_NO_THREADS) + // NOTE: if defined, user will need to create a thread for PhysicsThread function manually + // Create physics thread using POSIXS thread libraries + pthread_create(&physicsThreadId, NULL, &PhysicsLoop, NULL); #endif } -// Unitialize all physic objects and empty the objects pool -PHYSACDEF void ClosePhysics() +// Returns true if physics thread is currently enabled +PHYSACDEF bool IsPhysicsEnabled(void) { - // Exit physics thread loop + return physicsThreadEnabled; +} + +// Sets physics global gravity force +PHYSACDEF void SetPhysicsGravity(float x, float y) +{ + gravityForce.x = x; + gravityForce.y = y; +} + +// Creates a new circle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyCircle(Vector2 pos, float radius, float density) +{ + PhysicsBody newBody = CreatePhysicsBodyPolygon(pos, radius, PHYSAC_CIRCLE_VERTICES, density); + return newBody; + + /*PhysicsBody newBody = (PhysicsBody)PHYSAC_MALLOC(sizeof(PhysicsBodyData)); + usedMemory += sizeof(PhysicsBodyData); + + int newId = -1; + for (int i = 0; i < PHYSAC_MAX_BODIES; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (int k = 0; k < physicsBodiesCount; k++) + { + if (bodies[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == i) + { + newId = i; + break; + } + } + + if (newId != -1) + { + // Initialize new body with generic values + newBody->id = newId; + newBody->enabled = true; + newBody->position = pos; + newBody->velocity = (Vector2){ 0 }; + newBody->force = (Vector2){ 0 }; + newBody->angularVelocity = 0; + newBody->torque = 0; + newBody->orient = 0; + newBody->mass = PHYSAC_PI*radius*radius*density; + newBody->inverseMass = ((newBody->mass != 0.0f) ? 1.0f/newBody->mass : 0.0f); + newBody->inertia = newBody->mass*radius*radius; + newBody->inverseInertia = ((newBody->inertia != 0.0f) ? 1.0f/newBody->inertia : 0.0f); + newBody->staticFriction = 0; + newBody->dynamicFriction = 0; + newBody->restitution = 0; + newBody->useGravity = true; + newBody->freezeOrient = false; + newBody->shape.type = PHYSICS_CIRCLE; + newBody->shape.body = newBody; + newBody->shape.radius = radius; + + // Add new body to bodies pointers array and update bodies count + bodies[physicsBodiesCount] = newBody; + physicsBodiesCount++; + + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] created circle physics body id %i\n", newBody->id); + #endif + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] new physics body creation failed because there is any available id to use\n"); + #endif + + return newBody;*/ +} + +// Creates a new rectangle physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyRectangle(Vector2 pos, float width, float height, float density) +{ + PhysicsBody newBody = (PhysicsBody)PHYSAC_MALLOC(sizeof(PhysicsBodyData)); + usedMemory += sizeof(PhysicsBodyData); + + int newId = -1; + for (int i = 0; i < PHYSAC_MAX_BODIES; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (int k = 0; k < physicsBodiesCount; k++) + { + if (bodies[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == i) + { + newId = i; + break; + } + } + + if (newId != -1) + { + // Initialize new body with generic values + newBody->id = newId; + newBody->enabled = true; + newBody->position = pos; + newBody->velocity = (Vector2){ 0 }; + newBody->force = (Vector2){ 0 }; + newBody->angularVelocity = 0; + newBody->torque = 0; + newBody->orient = 0; + newBody->shape.type = PHYSICS_POLYGON; + newBody->shape.body = newBody; + newBody->shape.vertexData = CreateRectanglePolygon(pos, (Vector2){ width, height }); + + // Calculate centroid and moment of inertia + Vector2 center = { 0 }; + float area = 0; + float inertia = 0; + const float k = 1.0f/3.0f; + + for (int i = 0; i < newBody->shape.vertexData.vertexCount; i++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 p1 = newBody->shape.vertexData.vertices[i]; + int nextIndex = (((i + 1) < newBody->shape.vertexData.vertexCount) ? (i + 1) : 0); + Vector2 p2 = newBody->shape.vertexData.vertices[nextIndex]; + + float D = MathCrossVector2(p1, p2); + float triangleArea = D/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*k*(p1.x + p2.x); + center.y += triangleArea*k*(p1.y + p2.y); + + float intx2 = p1.x*p1.x + p2.x*p1.x + p2.x*p2.x; + float inty2 = p1.y*p1.y + p2.y*p1.y + p2.y*p2.y; + inertia += (0.25f*k*D)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + // Translate vertices to centroid (make the centroid (0, 0) for the polygon in model space) + // Note: this is not really necessary + for (int i = 0; i < newBody->shape.vertexData.vertexCount; i++) + { + newBody->shape.vertexData.vertices[i].x -= center.x; + newBody->shape.vertexData.vertices[i].y -= center.y; + } + + newBody->mass = density*area; + newBody->inverseMass = ((newBody->mass != 0.0f) ? 1.0f/newBody->mass : 0.0f); + newBody->inertia = density*inertia; + newBody->inverseInertia = ((newBody->inertia != 0.0f) ? 1.0f/newBody->inertia : 0.0f); + newBody->staticFriction = 0.4f; + newBody->dynamicFriction = 0.2f; + newBody->restitution = 0; + newBody->useGravity = true; + newBody->isGrounded = false; + newBody->freezeOrient = false; + + // Add new body to bodies pointers array and update bodies count + bodies[physicsBodiesCount] = newBody; + physicsBodiesCount++; + + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] created polygon physics body id %i\n", newBody->id); + #endif + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] new physics body creation failed because there is any available id to use\n"); + #endif + + return newBody; +} + +// Creates a new polygon physics body with generic parameters +PHYSACDEF PhysicsBody CreatePhysicsBodyPolygon(Vector2 pos, float radius, int sides, float density) +{ + PhysicsBody newBody = (PhysicsBody)PHYSAC_MALLOC(sizeof(PhysicsBodyData)); + usedMemory += sizeof(PhysicsBodyData); + + int newId = -1; + for (int i = 0; i < PHYSAC_MAX_BODIES; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (int k = 0; k < physicsBodiesCount; k++) + { + if (bodies[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == i) + { + newId = i; + break; + } + } + + if (newId != -1) + { + // Initialize new body with generic values + newBody->id = newId; + newBody->enabled = true; + newBody->position = pos; + newBody->velocity = (Vector2){ 0 }; + newBody->force = (Vector2){ 0 }; + newBody->angularVelocity = 0; + newBody->torque = 0; + newBody->orient = 0; + newBody->shape.type = PHYSICS_POLYGON; + newBody->shape.body = newBody; + newBody->shape.vertexData = CreateRandomPolygon(radius, sides); + + // Calculate centroid and moment of inertia + Vector2 center = { 0 }; + float area = 0; + float inertia = 0; + const float alpha = 1.0f/3.0f; + + for (int i = 0; i < newBody->shape.vertexData.vertexCount; i++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 position1 = newBody->shape.vertexData.vertices[i]; + int nextIndex = (((i + 1) < newBody->shape.vertexData.vertexCount) ? (i + 1) : 0); + Vector2 position2 = newBody->shape.vertexData.vertices[nextIndex]; + + float cross = MathCrossVector2(position1, position2); + float triangleArea = cross/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*alpha*(position1.x + position2.x); + center.y += triangleArea*alpha*(position1.y + position2.y); + + float intx2 = position1.x*position1.x + position2.x*position1.x + position2.x*position2.x; + float inty2 = position1.y*position1.y + position2.y*position1.y + position2.y*position2.y; + inertia += (0.25f*alpha*cross)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + // Translate vertices to centroid (make the centroid (0, 0) for the polygon in model space) + // Note: this is not really necessary + for (int i = 0; i < newBody->shape.vertexData.vertexCount; i++) + { + newBody->shape.vertexData.vertices[i].x -= center.x; + newBody->shape.vertexData.vertices[i].y -= center.y; + } + + newBody->mass = density*area; + newBody->inverseMass = ((newBody->mass != 0.0f) ? 1.0f/newBody->mass : 0.0f); + newBody->inertia = density*inertia; + newBody->inverseInertia = ((newBody->inertia != 0.0f) ? 1.0f/newBody->inertia : 0.0f); + newBody->staticFriction = 0.4f; + newBody->dynamicFriction = 0.2f; + newBody->restitution = 0; + newBody->useGravity = true; + newBody->isGrounded = false; + newBody->freezeOrient = false; + + // Add new body to bodies pointers array and update bodies count + bodies[physicsBodiesCount] = newBody; + physicsBodiesCount++; + + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] created polygon physics body id %i\n", newBody->id); + #endif + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] new physics body creation failed because there is any available id to use\n"); + #endif + + return newBody; +} + +// Adds a force to a physics body +PHYSACDEF void PhysicsAddForce(PhysicsBody body, Vector2 force) +{ + if (body != NULL) body->force = Vector2Add(body->force, force); +} + +// Adds an angular force to a physics body +PHYSACDEF void PhysicsAddTorque(PhysicsBody body, float amount) +{ + if (body != NULL) body->torque += amount; +} + +// Shatters a polygon shape physics body to little physics bodies with explosion force +PHYSACDEF void PhysicsShatter(PhysicsBody body, Vector2 position, float force) +{ + if (body != NULL) + { + if (body->shape.type == PHYSICS_POLYGON) + { + PolygonData vertexData = body->shape.vertexData; + bool collision = false; + + for (int i = 0; i < vertexData.vertexCount; i++) + { + Vector2 positionA = body->position; + Vector2 positionB = Mat2MultiplyVector2(vertexData.transform, Vector2Add(body->position, vertexData.vertices[i])); + int nextIndex = (((i + 1) < vertexData.vertexCount) ? (i + 1) : 0); + Vector2 positionC = Mat2MultiplyVector2(vertexData.transform, Vector2Add(body->position, vertexData.vertices[nextIndex])); + + // Check collision between each triangle + float alpha = ((positionB.y - positionC.y)*(position.x - positionC.x) + (positionC.x - positionB.x)*(position.y - positionC.y))/ + ((positionB.y - positionC.y)*(positionA.x - positionC.x) + (positionC.x - positionB.x)*(positionA.y - positionC.y)); + + float beta = ((positionC.y - positionA.y)*(position.x - positionC.x) + (positionA.x - positionC.x)*(position.y - positionC.y))/ + ((positionB.y - positionC.y)*(positionA.x - positionC.x) + (positionC.x - positionB.x)*(positionA.y - positionC.y)); + + float gamma = 1.0f - alpha - beta; + + if ((alpha > 0) && (beta > 0) & (gamma > 0)) + { + collision = true; + break; + } + } + + if (collision) + { + int count = vertexData.vertexCount; + Vector2 bodyPos = body->position; + Vector2 vertices[count]; + Mat2 trans = vertexData.transform; + for (int i = 0; i < count; i++) vertices[i] = vertexData.vertices[i]; + + // Destroy shattered physics body + DestroyPhysicsBody(body); + + for (int i = 0; i < count; i++) + { + int nextIndex = (((i + 1) < count) ? (i + 1) : 0); + Vector2 center = TriangleBarycenter(vertices[i], vertices[nextIndex], (Vector2){ 0, 0 }); + center = Vector2Add(bodyPos, center); + Vector2 offset = Vector2Subtract(center, bodyPos); + + PhysicsBody newBody = CreatePhysicsBodyPolygon(center, 10, 3, 10); // Create polygon physics body with relevant values + + PolygonData newData = { 0 }; + newData.vertexCount = 3; + newData.transform = trans; + + newData.vertices[0] = Vector2Subtract(vertices[i], offset); + newData.vertices[1] = Vector2Subtract(vertices[nextIndex], offset); + newData.vertices[2] = Vector2Subtract(position, center); + + // Separate vertices to avoid unnecessary physics collisions + newData.vertices[0].x *= 0.95f; + newData.vertices[0].y *= 0.95f; + newData.vertices[1].x *= 0.95f; + newData.vertices[1].y *= 0.95f; + newData.vertices[2].x *= 0.95f; + newData.vertices[2].y *= 0.95f; + + // Calculate polygon faces normals + for (int j = 0; j < newData.vertexCount; j++) + { + int nextVertex = (((j + 1) < newData.vertexCount) ? (j + 1) : 0); + Vector2 face = Vector2Subtract(newData.vertices[nextVertex], newData.vertices[j]); + + newData.normals[j] = (Vector2){ face.y, -face.x }; + MathNormalize(&newData.normals[j]); + } + + // Apply computed vertex data to new physics body shape + newBody->shape.vertexData = newData; + + // Calculate centroid and moment of inertia + center = (Vector2){ 0 }; + float area = 0; + float inertia = 0; + const float k = 1.0f/3.0f; + + for (int j = 0; j < newBody->shape.vertexData.vertexCount; j++) + { + // Triangle vertices, third vertex implied as (0, 0) + Vector2 p1 = newBody->shape.vertexData.vertices[j]; + int nextVertex = (((j + 1) < newBody->shape.vertexData.vertexCount) ? (j + 1) : 0); + Vector2 p2 = newBody->shape.vertexData.vertices[nextVertex]; + + float D = MathCrossVector2(p1, p2); + float triangleArea = D/2; + + area += triangleArea; + + // Use area to weight the centroid average, not just vertex position + center.x += triangleArea*k*(p1.x + p2.x); + center.y += triangleArea*k*(p1.y + p2.y); + + float intx2 = p1.x*p1.x + p2.x*p1.x + p2.x*p2.x; + float inty2 = p1.y*p1.y + p2.y*p1.y + p2.y*p2.y; + inertia += (0.25f*k*D)*(intx2 + inty2); + } + + center.x *= 1.0f/area; + center.y *= 1.0f/area; + + newBody->mass = area; + newBody->inverseMass = ((newBody->mass != 0.0f) ? 1.0f/newBody->mass : 0.0f); + newBody->inertia = inertia; + newBody->inverseInertia = ((newBody->inertia != 0.0f) ? 1.0f/newBody->inertia : 0.0f); + + // Calculate explosion force direction + Vector2 pointA = newBody->position; + Vector2 pointB = Vector2Subtract(newData.vertices[1], newData.vertices[0]); + pointB.x /= 2; + pointB.y /= 2; + Vector2 forceDirection = Vector2Subtract(Vector2Add(pointA, Vector2Add(newData.vertices[0], pointB)), newBody->position); + MathNormalize(&forceDirection); + forceDirection.x *= force; + forceDirection.y *= force; + + // Apply force to new physics body + PhysicsAddForce(newBody, forceDirection); + } + } + } + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] error when trying to shatter a null reference physics body"); + #endif +} + +// Returns the current amount of created physics bodies +PHYSACDEF int GetPhysicsBodiesCount(void) +{ + return physicsBodiesCount; +} + +// Returns a physics body of the bodies pool at a specific index +PHYSACDEF PhysicsBody GetPhysicsBody(int index) +{ + if (index < physicsBodiesCount) + { + PhysicsBody body = bodies[index]; + if (body != NULL) return body; + else + { + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] error when trying to get a null reference physics body"); + #endif + + return NULL; + } + } + #if defined(PHYSAC_DEBUG) + else + { + printf("[PHYSAC] physics body index is out of bounds"); + return NULL; + } + #endif +} + +// Returns the physics body shape type (PHYSICS_CIRCLE or PHYSICS_POLYGON) +PHYSACDEF int GetPhysicsShapeType(int index) +{ + if (index < physicsBodiesCount) + { + PhysicsBody body = bodies[index]; + if (body != NULL) return body->shape.type; + #if defined(PHYSAC_DEBUG) + else + { + printf("[PHYSAC] error when trying to get a null reference physics body"); + return -1; + } + #endif + } + #if defined(PHYSAC_DEBUG) + else + { + printf("[PHYSAC] physics body index is out of bounds"); + return -1; + } + #endif +} + +// Returns the amount of vertices of a physics body shape +PHYSACDEF int GetPhysicsShapeVerticesCount(int index) +{ + if (index < physicsBodiesCount) + { + PhysicsBody body = bodies[index]; + if (body != NULL) + { + switch (body->shape.type) + { + case PHYSICS_CIRCLE: return PHYSAC_CIRCLE_VERTICES; break; + case PHYSICS_POLYGON: return body->shape.vertexData.vertexCount; break; + default: break; + } + } + #if defined(PHYSAC_DEBUG) + else + { + printf("[PHYSAC] error when trying to get a null reference physics body"); + return 0; + } + #endif + } + #if defined(PHYSAC_DEBUG) + else + { + printf("[PHYSAC] physics body index is out of bounds"); + return 0; + } + #endif +} + +// Returns transformed position of a body shape (body position + vertex transformed position) +PHYSACDEF Vector2 GetPhysicsShapeVertex(PhysicsBody body, int vertex) +{ + Vector2 position = { 0 }; + + if (body != NULL) + { + switch (body->shape.type) + { + case PHYSICS_CIRCLE: + { + position.x = body->position.x + cosf(360/PHYSAC_CIRCLE_VERTICES*vertex*PHYSAC_DEG2RAD)*body->shape.radius; + position.y = body->position.y + sinf(360/PHYSAC_CIRCLE_VERTICES*vertex*PHYSAC_DEG2RAD)*body->shape.radius; + } break; + case PHYSICS_POLYGON: + { + PolygonData vertexData = body->shape.vertexData; + position = Vector2Add(body->position, Mat2MultiplyVector2(vertexData.transform, vertexData.vertices[vertex])); + } break; + default: break; + } + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] error when trying to get a null reference physics body"); + #endif + + return position; +} + +// Sets physics body shape transform based on radians parameter +PHYSACDEF void SetPhysicsBodyRotation(PhysicsBody body, float radians) +{ + if (body != NULL) + { + body->orient = radians; + + if (body->shape.type == PHYSICS_POLYGON) body->shape.vertexData.transform = Mat2Radians(radians); + } +} + +// Unitializes and destroys a physics body +PHYSACDEF void DestroyPhysicsBody(PhysicsBody body) +{ + if (body != NULL) + { + int id = body->id; + int index = -1; + + for (int i = 0; i < physicsBodiesCount; i++) + { + if (bodies[i]->id == id) + { + index = i; + break; + } + } + + #if defined(PHYSAC_DEBUG) + if (index == -1) printf("[PHYSAC] cannot find body id %i in pointers array\n", id); + #endif + + // Free body allocated memory + PHYSAC_FREE(bodies[index]); + usedMemory -= sizeof(PhysicsBodyData); + bodies[index] = NULL; + + // Reorder physics bodies pointers array and its catched index + for (int i = index; i < physicsBodiesCount; i++) + { + if ((i + 1) < physicsBodiesCount) bodies[i] = bodies[i + 1]; + } + + // Update physics bodies count + physicsBodiesCount--; + + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] destroyed physics body id %i\n", id); + #endif + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] error trying to destroy a null referenced body\n"); + #endif +} + +// Destroys created physics bodies and manifolds and resets global values +PHYSACDEF void ResetPhysics(void) +{ + // Unitialize physics bodies dynamic memory allocations + for (int i = physicsBodiesCount - 1; i >= 0; i--) + { + PhysicsBody body = bodies[i]; + + if (body != NULL) + { + PHYSAC_FREE(body); + body = NULL; + usedMemory -= sizeof(PhysicsBodyData); + } + } + + physicsBodiesCount = 0; + + // Unitialize physics manifolds dynamic memory allocations + for (int i = physicsManifoldsCount - 1; i >= 0; i--) + { + PhysicsManifold manifold = contacts[i]; + + if (manifold != NULL) + { + PHYSAC_FREE(manifold); + manifold = NULL; + usedMemory -= sizeof(PhysicsManifoldData); + } + } + + physicsManifoldsCount = 0; + + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] physics module reset successfully\n"); + #endif +} + +// Unitializes physics pointers and exits physics loop thread +PHYSACDEF void ClosePhysics(void) +{ + // Exit physics loop thread physicsThreadEnabled = false; - - // Free all dynamic memory allocations - for (int i = 0; i < physicBodiesCount; i++) PHYSAC_FREE(physicBodies[i]); - - // Reset enabled physic objects count - physicBodiesCount = 0; + + #if !defined(PHYSAC_NO_THREADS) + pthread_join(physicsThreadId, NULL); + #endif } -// Create a new physic body dinamically, initialize it and add to pool -PHYSACDEF PhysicBody CreatePhysicBody(Vector2 position, float rotation, Vector2 scale) +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +// Creates a random polygon shape with max vertex distance from polygon pivot +static PolygonData CreateRandomPolygon(float radius, int sides) { - // 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; -} + PolygonData data = { 0 }; + data.vertexCount = sides; -// 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++) + float orient = GetRandomNumber(0, 360); + data.transform = Mat2Radians(orient*PHYSAC_DEG2RAD); + + // Calculate polygon vertices positions + for (int i = 0; i < data.vertexCount; 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]); + data.vertices[i].x = cosf(360/sides*i*PHYSAC_DEG2RAD)*radius; + data.vertices[i].y = sinf(360/sides*i*PHYSAC_DEG2RAD)*radius; } - - // Decrease enabled physic bodies count - physicBodiesCount--; -} -// Apply directional force to a physic body -PHYSACDEF void ApplyForce(PhysicBody pbody, Vector2 force) -{ - if (pbody->rigidbody.enabled) + // Calculate polygon faces normals + for (int i = 0; i < data.vertexCount; i++) { - pbody->rigidbody.velocity.x += force.x/pbody->rigidbody.mass; - pbody->rigidbody.velocity.y += force.y/pbody->rigidbody.mass; + int nextIndex = (((i + 1) < sides) ? (i + 1) : 0); + Vector2 face = Vector2Subtract(data.vertices[nextIndex], data.vertices[i]); + + data.normals[i] = (Vector2){ face.y, -face.x }; + MathNormalize(&data.normals[i]); } + + return data; } -// Apply radial force to all physic objects in range -PHYSACDEF void ApplyForceAtPosition(Vector2 position, float force, float radius) +// Creates a rectangle polygon shape based on a min and max positions +static PolygonData CreateRectanglePolygon(Vector2 pos, Vector2 size) { - for (int i = 0; i < physicBodiesCount; i++) + PolygonData data = { 0 }; + + data.vertexCount = 4; + data.transform = Mat2Radians(0); + + // Calculate polygon vertices positions + data.vertices[0] = (Vector2){ pos.x + size.x/2, pos.y - size.y/2 }; + data.vertices[1] = (Vector2){ pos.x + size.x/2, pos.y + size.y/2 }; + data.vertices[2] = (Vector2){ pos.x - size.x/2, pos.y + size.y/2 }; + data.vertices[3] = (Vector2){ pos.x - size.x/2, pos.y - size.y/2 }; + + // Calculate polygon faces normals + for (int i = 0; i < data.vertexCount; i++) { - 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 }; + int nextIndex = (((i + 1) < data.vertexCount) ? (i + 1) : 0); + Vector2 face = Vector2Subtract(data.vertices[nextIndex], data.vertices[i]); - 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); - } - } + data.normals[i] = (Vector2){ face.y, -face.x }; + MathNormalize(&data.normals[i]); } + + return data; } -// Convert Transform data type to Rectangle (position and scale) -PHYSACDEF Rectangle TransformToRectangle(Transform transform) +// Physics loop thread function +static void *PhysicsLoop(void *arg) { - return (Rectangle){transform.position.x, transform.position.y, transform.scale.x, transform.scale.y}; -} + #if defined(PHYSAC_DEBUG) + printf("[PHYSAC] physics thread created with successfully\n"); + #endif -// Physics calculations thread function -PHYSACDEF void* PhysicsThread(void *arg) -{ - // Initialize thread loop state + // Initialize physics loop thread values physicsThreadEnabled = true; - - // Initialize hi-resolution timer - InitTimer(); - - // Physics update loop - while (physicsThreadEnabled) - { - currentTime = GetCurrentTime(); - double deltaTime = (double)(currentTime - previousTime); - previousTime = currentTime; + accumulator = 0; - // Delta time value needs to be inverse multiplied by physics time step value (1/target fps) - UpdatePhysics(deltaTime/PHYSICS_TIMESTEP); + // Initialize high resolution timer + InitTimer(); + + // Physics update loop + while (physicsThreadEnabled) + { + // Calculate current time + currentTime = GetCurrentTime(); + + // Calculate current delta time + deltaTime = currentTime - startTime; + + // Store the time elapsed since the last frame began + accumulator += deltaTime; + + // Clamp accumulator to max time step to avoid bad performance + MathClamp(&accumulator, 0, PHYSAC_MAX_TIMESTEP); + + // Fixed time stepping loop + while (accumulator >= PHYSAC_DESIRED_DELTATIME) + { + PhysicsStep(); + accumulator -= deltaTime; + } + + // Record the starting of this frame + startTime = currentTime; } - + + // Unitialize physics manifolds dynamic memory allocations + for (int i = physicsManifoldsCount - 1; i >= 0; i--) DestroyPhysicsManifold(contacts[i]); + + // Unitialize physics bodies dynamic memory allocations + for (int i = physicsBodiesCount - 1; i >= 0; i--) DestroyPhysicsBody(bodies[i]); + + #if defined(PHYSAC_DEBUG) + if (physicsBodiesCount > 0 || usedMemory != 0) printf("[PHYSAC] physics module closed with %i still allocated bodies [MEMORY: %i bytes]\n", physicsBodiesCount, usedMemory); + else if (physicsManifoldsCount > 0 || usedMemory != 0) printf("[PHYSAC] physics module closed with %i still allocated manifolds [MEMORY: %i bytes]\n", physicsManifoldsCount, usedMemory); + else printf("[PHYSAC] physics module closed successfully\n"); + #endif + return NULL; } -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- -// Initialize hi-resolution timer -static void InitTimer(void) +// Physics steps calculations (dynamics, collisions and position corrections) +static void PhysicsStep(void) { -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) - struct timespec now; + stepsCount++; - if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success + // Clear previous generated collisions information + for (int i = physicsManifoldsCount - 1; i >= 0; i--) { - baseTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) DestroyPhysicsManifold(manifold); } -#endif - previousTime = GetCurrentTime(); // Get time as double -} - -// Time measure returned are microseconds -static double GetCurrentTime(void) -{ - double time; - -#if defined(PLATFORM_DESKTOP) - unsigned long long int clockFrequency, currentTime; - - QueryPerformanceFrequency(&clockFrequency); - QueryPerformanceCounter(¤tTime); - - time = (double)((double)currentTime/(double)clockFrequency); -#endif - -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - uint64_t temp = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec; - - time = (double)(temp - baseTime)*1e-9; -#endif - - return time; -} - -// Returns the dot product of two Vector2 -static float Vector2DotProduct(Vector2 v1, Vector2 v2) -{ - float result; - - result = v1.x*v2.x + v1.y*v2.y; - - return result; -} - -static float Vector2Length(Vector2 v) -{ - float result; - - result = sqrt(v.x*v.x + v.y*v.y); - - return result; -} - -// Update physic objects, calculating physic behaviours and collisions detection -static void UpdatePhysics(double deltaTime) -{ - for (int i = 0; i < physicBodiesCount; i++) + // Generate new collision information + for (int i = 0; i < physicsBodiesCount; i++) { - if (physicBodies[i]->enabled) + PhysicsBody bodyA = bodies[i]; + + if (bodyA != NULL) { - // Update physic behaviour - if (physicBodies[i]->rigidbody.enabled) + for (int j = i + 1; j < physicsBodiesCount; j++) { - // Apply friction to acceleration in X axis - if (physicBodies[i]->rigidbody.acceleration.x > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.x -= physicBodies[i]->rigidbody.friction*deltaTime; - else if (physicBodies[i]->rigidbody.acceleration.x < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.x += physicBodies[i]->rigidbody.friction*deltaTime; - else physicBodies[i]->rigidbody.acceleration.x = 0.0f; - - // Apply friction to acceleration in Y axis - if (physicBodies[i]->rigidbody.acceleration.y > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.y -= physicBodies[i]->rigidbody.friction*deltaTime; - else if (physicBodies[i]->rigidbody.acceleration.y < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.acceleration.y += physicBodies[i]->rigidbody.friction*deltaTime; - else physicBodies[i]->rigidbody.acceleration.y = 0.0f; - - // Apply friction to velocity in X axis - if (physicBodies[i]->rigidbody.velocity.x > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.x -= physicBodies[i]->rigidbody.friction*deltaTime; - else if (physicBodies[i]->rigidbody.velocity.x < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.x += physicBodies[i]->rigidbody.friction*deltaTime; - else physicBodies[i]->rigidbody.velocity.x = 0.0f; - - // Apply friction to velocity in Y axis - if (physicBodies[i]->rigidbody.velocity.y > PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.y -= physicBodies[i]->rigidbody.friction*deltaTime; - else if (physicBodies[i]->rigidbody.velocity.y < PHYSICS_ACCURACY) physicBodies[i]->rigidbody.velocity.y += physicBodies[i]->rigidbody.friction*deltaTime; - else physicBodies[i]->rigidbody.velocity.y = 0.0f; - - // Apply gravity to velocity - if (physicBodies[i]->rigidbody.applyGravity) + PhysicsBody bodyB = bodies[j]; + + if (bodyB != NULL) { - physicBodies[i]->rigidbody.velocity.x += gravityForce.x*deltaTime; - physicBodies[i]->rigidbody.velocity.y += gravityForce.y*deltaTime; - } - - // Apply acceleration to velocity - physicBodies[i]->rigidbody.velocity.x += physicBodies[i]->rigidbody.acceleration.x*deltaTime; - physicBodies[i]->rigidbody.velocity.y += physicBodies[i]->rigidbody.acceleration.y*deltaTime; - - // Apply velocity to position - physicBodies[i]->transform.position.x += physicBodies[i]->rigidbody.velocity.x*deltaTime; - physicBodies[i]->transform.position.y -= physicBodies[i]->rigidbody.velocity.y*deltaTime; - } - - // Update collision detection - if (physicBodies[i]->collider.enabled) - { - // Update collider bounds - physicBodies[i]->collider.bounds = TransformToRectangle(physicBodies[i]->transform); - - // Check collision with other colliders - for (int k = 0; k < physicBodiesCount; k++) - { - if (physicBodies[k]->collider.enabled && i != k) + if ((bodyA->inverseMass == 0) && (bodyB->inverseMass == 0)) continue; + + PhysicsManifold manifold = NULL; + if (bodyA->shape.type == PHYSICS_POLYGON && bodyB->shape.type == PHYSICS_CIRCLE) manifold = CreatePhysicsManifold(bodyB, bodyA); + else manifold = CreatePhysicsManifold(bodyA, bodyB); + SolvePhysicsManifold(manifold); + + if (manifold->contactsCount > 0) { - // Resolve physic collision - // NOTE: collision resolve is generic for all directions and conditions (no axis separated cases behaviours) - // and it is separated in rigidbody attributes resolve (velocity changes by impulse) and position correction (position overlap) - - // 1. Calculate collision normal - // ------------------------------------------------------------------------------------------------------------------------------------- - - // Define collision contact normal, direction and penetration depth - Vector2 contactNormal = { 0.0f, 0.0f }; - Vector2 direction = { 0.0f, 0.0f }; - float penetrationDepth = 0.0f; - - switch (physicBodies[i]->collider.type) - { - case COLLIDER_RECTANGLE: - { - switch (physicBodies[k]->collider.type) - { - case COLLIDER_RECTANGLE: - { - // Check if colliders are overlapped - if (CheckCollisionRecs(physicBodies[i]->collider.bounds, physicBodies[k]->collider.bounds)) - { - // Calculate direction vector from i to k - direction.x = (physicBodies[k]->transform.position.x + physicBodies[k]->transform.scale.x/2) - (physicBodies[i]->transform.position.x + physicBodies[i]->transform.scale.x/2); - direction.y = (physicBodies[k]->transform.position.y + physicBodies[k]->transform.scale.y/2) - (physicBodies[i]->transform.position.y + physicBodies[i]->transform.scale.y/2); - - // Define overlapping and penetration attributes - Vector2 overlap; - - // Calculate overlap on X axis - overlap.x = (physicBodies[i]->transform.scale.x + physicBodies[k]->transform.scale.x)/2 - abs(direction.x); - - // SAT test on X axis - if (overlap.x > 0.0f) - { - // Calculate overlap on Y axis - overlap.y = (physicBodies[i]->transform.scale.y + physicBodies[k]->transform.scale.y)/2 - abs(direction.y); - - // SAT test on Y axis - if (overlap.y > 0.0f) - { - // Find out which axis is axis of least penetration - if (overlap.y > overlap.x) - { - // Point towards k knowing that direction points from i to k - if (direction.x < 0.0f) contactNormal = (Vector2){ -1.0f, 0.0f }; - else contactNormal = (Vector2){ 1.0f, 0.0f }; - - // Update penetration depth for position correction - penetrationDepth = overlap.x; - } - else - { - // Point towards k knowing that direction points from i to k - if (direction.y < 0.0f) contactNormal = (Vector2){ 0.0f, 1.0f }; - else contactNormal = (Vector2){ 0.0f, -1.0f }; - - // Update penetration depth for position correction - penetrationDepth = overlap.y; - } - } - } - } - } break; - case COLLIDER_CIRCLE: - { - if (CheckCollisionCircleRec(physicBodies[k]->transform.position, physicBodies[k]->collider.radius, physicBodies[i]->collider.bounds)) - { - // Calculate direction vector between circles - direction.x = physicBodies[k]->transform.position.x - physicBodies[i]->transform.position.x + physicBodies[i]->transform.scale.x/2; - direction.y = physicBodies[k]->transform.position.y - physicBodies[i]->transform.position.y + physicBodies[i]->transform.scale.y/2; - - // Calculate closest point on rectangle to circle - Vector2 closestPoint = { 0.0f, 0.0f }; - if (direction.x > 0.0f) closestPoint.x = physicBodies[i]->collider.bounds.x + physicBodies[i]->collider.bounds.width; - else closestPoint.x = physicBodies[i]->collider.bounds.x; - - if (direction.y > 0.0f) closestPoint.y = physicBodies[i]->collider.bounds.y + physicBodies[i]->collider.bounds.height; - else closestPoint.y = physicBodies[i]->collider.bounds.y; - - // Check if the closest point is inside the circle - if (CheckCollisionPointCircle(closestPoint, physicBodies[k]->transform.position, physicBodies[k]->collider.radius)) - { - // Recalculate direction based on closest point position - direction.x = physicBodies[k]->transform.position.x - closestPoint.x; - direction.y = physicBodies[k]->transform.position.y - closestPoint.y; - float distance = Vector2Length(direction); - - // Calculate final contact normal - contactNormal.x = direction.x/distance; - contactNormal.y = -direction.y/distance; - - // Calculate penetration depth - penetrationDepth = physicBodies[k]->collider.radius - distance; - } - else - { - if (abs(direction.y) < abs(direction.x)) - { - // Calculate final contact normal - if (direction.y > 0.0f) - { - contactNormal = (Vector2){ 0.0f, -1.0f }; - penetrationDepth = fabs(physicBodies[i]->collider.bounds.y - physicBodies[k]->transform.position.y - physicBodies[k]->collider.radius); - } - else - { - contactNormal = (Vector2){ 0.0f, 1.0f }; - penetrationDepth = fabs(physicBodies[i]->collider.bounds.y - physicBodies[k]->transform.position.y + physicBodies[k]->collider.radius); - } - } - else - { - // Calculate final contact normal - if (direction.x > 0.0f) - { - contactNormal = (Vector2){ 1.0f, 0.0f }; - penetrationDepth = fabs(physicBodies[k]->transform.position.x + physicBodies[k]->collider.radius - physicBodies[i]->collider.bounds.x); - } - else - { - contactNormal = (Vector2){ -1.0f, 0.0f }; - penetrationDepth = fabs(physicBodies[i]->collider.bounds.x + physicBodies[i]->collider.bounds.width - physicBodies[k]->transform.position.x - physicBodies[k]->collider.radius); - } - } - } - } - } break; - } - } break; - case COLLIDER_CIRCLE: - { - switch (physicBodies[k]->collider.type) - { - case COLLIDER_RECTANGLE: - { - if (CheckCollisionCircleRec(physicBodies[i]->transform.position, physicBodies[i]->collider.radius, physicBodies[k]->collider.bounds)) - { - // Calculate direction vector between circles - direction.x = physicBodies[k]->transform.position.x + physicBodies[i]->transform.scale.x/2 - physicBodies[i]->transform.position.x; - direction.y = physicBodies[k]->transform.position.y + physicBodies[i]->transform.scale.y/2 - physicBodies[i]->transform.position.y; - - // Calculate closest point on rectangle to circle - Vector2 closestPoint = { 0.0f, 0.0f }; - if (direction.x > 0.0f) closestPoint.x = physicBodies[k]->collider.bounds.x + physicBodies[k]->collider.bounds.width; - else closestPoint.x = physicBodies[k]->collider.bounds.x; - - if (direction.y > 0.0f) closestPoint.y = physicBodies[k]->collider.bounds.y + physicBodies[k]->collider.bounds.height; - else closestPoint.y = physicBodies[k]->collider.bounds.y; - - // Check if the closest point is inside the circle - if (CheckCollisionPointCircle(closestPoint, physicBodies[i]->transform.position, physicBodies[i]->collider.radius)) - { - // Recalculate direction based on closest point position - direction.x = physicBodies[i]->transform.position.x - closestPoint.x; - direction.y = physicBodies[i]->transform.position.y - closestPoint.y; - float distance = Vector2Length(direction); - - // Calculate final contact normal - contactNormal.x = direction.x/distance; - contactNormal.y = -direction.y/distance; - - // Calculate penetration depth - penetrationDepth = physicBodies[k]->collider.radius - distance; - } - else - { - if (abs(direction.y) < abs(direction.x)) - { - // Calculate final contact normal - if (direction.y > 0.0f) - { - contactNormal = (Vector2){ 0.0f, -1.0f }; - penetrationDepth = fabs(physicBodies[k]->collider.bounds.y - physicBodies[i]->transform.position.y - physicBodies[i]->collider.radius); - } - else - { - contactNormal = (Vector2){ 0.0f, 1.0f }; - penetrationDepth = fabs(physicBodies[k]->collider.bounds.y - physicBodies[i]->transform.position.y + physicBodies[i]->collider.radius); - } - } - else - { - // Calculate final contact normal and penetration depth - if (direction.x > 0.0f) - { - contactNormal = (Vector2){ 1.0f, 0.0f }; - penetrationDepth = fabs(physicBodies[i]->transform.position.x + physicBodies[i]->collider.radius - physicBodies[k]->collider.bounds.x); - } - else - { - contactNormal = (Vector2){ -1.0f, 0.0f }; - penetrationDepth = fabs(physicBodies[k]->collider.bounds.x + physicBodies[k]->collider.bounds.width - physicBodies[i]->transform.position.x - physicBodies[i]->collider.radius); - } - } - } - } - } break; - case COLLIDER_CIRCLE: - { - // Check if colliders are overlapped - if (CheckCollisionCircles(physicBodies[i]->transform.position, physicBodies[i]->collider.radius, physicBodies[k]->transform.position, physicBodies[k]->collider.radius)) - { - // Calculate direction vector between circles - direction.x = physicBodies[k]->transform.position.x - physicBodies[i]->transform.position.x; - direction.y = physicBodies[k]->transform.position.y - physicBodies[i]->transform.position.y; - - // Calculate distance between circles - float distance = Vector2Length(direction); - - // Check if circles are not completely overlapped - if (distance != 0.0f) - { - // Calculate contact normal direction (Y axis needs to be flipped) - contactNormal.x = direction.x/distance; - contactNormal.y = -direction.y/distance; - } - else contactNormal = (Vector2){ 1.0f, 0.0f }; // Choose random (but consistent) values - } - } break; - default: break; - } - } break; - default: break; - } - - // Update rigidbody grounded state - if (physicBodies[i]->rigidbody.enabled) physicBodies[i]->rigidbody.isGrounded = (contactNormal.y < 0.0f); - - // 2. Calculate collision impulse - // ------------------------------------------------------------------------------------------------------------------------------------- - - // Calculate relative velocity - Vector2 relVelocity = { 0.0f, 0.0f }; - relVelocity.x = physicBodies[k]->rigidbody.velocity.x - physicBodies[i]->rigidbody.velocity.x; - relVelocity.y = physicBodies[k]->rigidbody.velocity.y - physicBodies[i]->rigidbody.velocity.y; - - // Calculate relative velocity in terms of the normal direction - float velAlongNormal = Vector2DotProduct(relVelocity, contactNormal); - - // Dot not resolve if velocities are separating - if (velAlongNormal <= 0.0f) - { - // Calculate minimum bounciness value from both objects - float e = fminf(physicBodies[i]->rigidbody.bounciness, physicBodies[k]->rigidbody.bounciness); - - // Calculate impulse scalar value - float j = -(1.0f + e)*velAlongNormal; - j /= 1.0f/physicBodies[i]->rigidbody.mass + 1.0f/physicBodies[k]->rigidbody.mass; - - // Calculate final impulse vector - Vector2 impulse = { j*contactNormal.x, j*contactNormal.y }; - - // Calculate collision mass ration - float massSum = physicBodies[i]->rigidbody.mass + physicBodies[k]->rigidbody.mass; - float ratio = 0.0f; - - // Apply impulse to current rigidbodies velocities if they are enabled - if (physicBodies[i]->rigidbody.enabled) - { - // Calculate inverted mass ration - ratio = physicBodies[i]->rigidbody.mass/massSum; - - // Apply impulse direction to velocity - physicBodies[i]->rigidbody.velocity.x -= impulse.x*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); - physicBodies[i]->rigidbody.velocity.y -= impulse.y*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); - } - - if (physicBodies[k]->rigidbody.enabled) - { - // Calculate inverted mass ration - ratio = physicBodies[k]->rigidbody.mass/massSum; - - // Apply impulse direction to velocity - physicBodies[k]->rigidbody.velocity.x += impulse.x*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); - physicBodies[k]->rigidbody.velocity.y += impulse.y*ratio*(1.0f+physicBodies[i]->rigidbody.bounciness); - } - - // 3. Correct colliders overlaping (transform position) - // --------------------------------------------------------------------------------------------------------------------------------- - - // Calculate transform position penetration correction - Vector2 posCorrection; - posCorrection.x = penetrationDepth/((1.0f/physicBodies[i]->rigidbody.mass) + (1.0f/physicBodies[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.x; - posCorrection.y = penetrationDepth/((1.0f/physicBodies[i]->rigidbody.mass) + (1.0f/physicBodies[k]->rigidbody.mass))*PHYSICS_ERRORPERCENT*contactNormal.y; - - // Fix transform positions - if (physicBodies[i]->rigidbody.enabled) - { - // Fix physic objects transform position - physicBodies[i]->transform.position.x -= 1.0f/physicBodies[i]->rigidbody.mass*posCorrection.x; - physicBodies[i]->transform.position.y += 1.0f/physicBodies[i]->rigidbody.mass*posCorrection.y; - - // Update collider bounds - physicBodies[i]->collider.bounds = TransformToRectangle(physicBodies[i]->transform); - - if (physicBodies[k]->rigidbody.enabled) - { - // Fix physic objects transform position - physicBodies[k]->transform.position.x += 1.0f/physicBodies[k]->rigidbody.mass*posCorrection.x; - physicBodies[k]->transform.position.y -= 1.0f/physicBodies[k]->rigidbody.mass*posCorrection.y; - - // Update collider bounds - physicBodies[k]->collider.bounds = TransformToRectangle(physicBodies[k]->transform); - } - } - } + // Create a new manifold with same information as previously solved manifold and add it to the manifolds pool last slot + PhysicsManifold newManifold = CreatePhysicsManifold(bodyA, bodyB); + newManifold->penetration = manifold->penetration; + newManifold->normal = manifold->normal; + newManifold->contacts[0] = manifold->contacts[0]; + newManifold->contacts[1] = manifold->contacts[1]; + newManifold->contactsCount = manifold->contactsCount; + newManifold->restitution = manifold->restitution; + newManifold->dynamicFriction = manifold->dynamicFriction; + newManifold->staticFriction = manifold->staticFriction; } } } } } + + // Integrate forces to physics bodies + for (int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) IntegratePhysicsForces(body); + } + + // Initialize physics manifolds to solve collisions + for (int i = 0; i < physicsManifoldsCount; i++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) InitializePhysicsManifolds(manifold); + } + + // Integrate physics collisions impulses to solve collisions + for (int i = 0; i < PHYSAC_COLLISION_ITERATIONS; i++) + { + for (int j = 0; j < physicsManifoldsCount; j++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) IntegratePhysicsImpulses(manifold); + } + } + + // Integrate velocity to physics bodies + for (int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) IntegratePhysicsVelocity(body); + } + + // Correct physics bodies positions based on manifolds collision information + for (int i = 0; i < physicsManifoldsCount; i++) + { + PhysicsManifold manifold = contacts[i]; + if (manifold != NULL) CorrectPhysicsPositions(manifold); + } + + // Clear physics bodies forces + for (int i = 0; i < physicsBodiesCount; i++) + { + PhysicsBody body = bodies[i]; + if (body != NULL) + { + body->force = (Vector2){ 0 }; + body->torque = 0; + } + } } -#endif // PHYSAC_IMPLEMENTATION \ No newline at end of file +// Creates a new physics manifold to solve collision +static PhysicsManifold CreatePhysicsManifold(PhysicsBody a, PhysicsBody b) +{ + PhysicsManifold newManifold = (PhysicsManifold)PHYSAC_MALLOC(sizeof(PhysicsManifoldData)); + usedMemory += sizeof(PhysicsManifoldData); + + int newId = -1; + for (int i = 0; i < PHYSAC_MAX_MANIFOLDS; i++) + { + int currentId = i; + + // Check if current id already exist in other physics body + for (int k = 0; k < physicsManifoldsCount; k++) + { + if (contacts[k]->id == currentId) + { + currentId++; + break; + } + } + + // If it is not used, use it as new physics body id + if (currentId == i) + { + newId = i; + break; + } + } + + if (newId != -1) + { + // Initialize new manifold with generic values + newManifold->id = newId; + newManifold->bodyA = a; + newManifold->bodyB = b; + newManifold->penetration = 0; + newManifold->normal = (Vector2){ 0 }; + newManifold->contacts[0] = (Vector2){ 0 }; + newManifold->contacts[1] = (Vector2){ 0 }; + newManifold->contactsCount = 0; + newManifold->restitution = 0; + newManifold->dynamicFriction = 0; + newManifold->staticFriction = 0; + + // Add new body to bodies pointers array and update bodies count + contacts[physicsManifoldsCount] = newManifold; + physicsManifoldsCount++; + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] new physics manifold creation failed because there is any available id to use\n"); + #endif + + return newManifold; +} + +// Unitializes and destroys a physics manifold +static void DestroyPhysicsManifold(PhysicsManifold manifold) +{ + if (manifold != NULL) + { + int id = manifold->id; + int index = -1; + + for (int i = 0; i < physicsManifoldsCount; i++) + { + if (contacts[i]->id == id) + { + index = i; + break; + } + } + + #if defined(PHYSAC_DEBUG) + if (index == -1) printf("[PHYSAC] cannot find manifold id %i in pointers array\n", id); + #endif + + // Free manifold allocated memory + PHYSAC_FREE(contacts[index]); + usedMemory -= sizeof(PhysicsManifoldData); + contacts[index] = NULL; + + // Reorder physics manifolds pointers array and its catched index + for (int i = index; i < physicsManifoldsCount; i++) + { + if ((i + 1) < physicsManifoldsCount) contacts[i] = contacts[i + 1]; + } + + // Update physics manifolds count + physicsManifoldsCount--; + } + #if defined(PHYSAC_DEBUG) + else printf("[PHYSAC] error trying to destroy a null referenced manifold\n"); + #endif +} + +// Solves a created physics manifold between two physics bodies +static void SolvePhysicsManifold(PhysicsManifold manifold) +{ + switch (manifold->bodyA->shape.type) + { + case PHYSICS_CIRCLE: + { + switch (manifold->bodyB->shape.type) + { + case PHYSICS_CIRCLE: SolveCircleToCircle(manifold); break; + case PHYSICS_POLYGON: SolveCircleToPolygon(manifold); break; + default: break; + } + } break; + case PHYSICS_POLYGON: + { + switch (manifold->bodyB->shape.type) + { + case PHYSICS_CIRCLE: SolvePolygonToCircle(manifold); break; + case PHYSICS_POLYGON: SolvePolygonToPolygon(manifold); break; + default: break; + } + } break; + default: break; + } + + // Update physics body grounded state if normal direction is downside + manifold->bodyB->isGrounded = (manifold->normal.y < 0); +} + +// Solves collision between two circle shape physics bodies +static void SolveCircleToCircle(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + // Calculate translational vector, which is normal + Vector2 normal = Vector2Subtract(bodyB->position, bodyA->position); + + float distSqr = MathLenSqr(normal); + float radius = bodyA->shape.radius + bodyB->shape.radius; + + // Check if circles are not in contact + if (distSqr >= radius*radius) + { + manifold->contactsCount = 0; + return; + } + + float distance = sqrtf(distSqr); + manifold->contactsCount = 1; + + if (distance == 0) + { + manifold->penetration = bodyA->shape.radius; + manifold->normal = (Vector2){ 1, 0 }; + manifold->contacts[0] = bodyA->position; + } + else + { + manifold->penetration = radius - distance; + manifold->normal = (Vector2){ normal.x/distance, normal.y/distance }; // Faster than using MathNormalize() due to sqrt is already performed + manifold->contacts[0] = (Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + } + + // Update physics body grounded state if normal direction is down + if (manifold->normal.y < 0) bodyA->isGrounded = true; +} + +// Solves collision between a circle to a polygon shape physics bodies +static void SolveCircleToPolygon(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + manifold->contactsCount = 0; + + // Transform circle center to polygon transform space + Vector2 center = bodyA->position; + center = Mat2MultiplyVector2(Mat2Transpose(bodyB->shape.vertexData.transform), Vector2Subtract(center, bodyB->position)); + + // Find edge with minimum penetration + // It is the same concept as using support points in SolvePolygonToPolygon + float separation = -PHYSAC_FLT_MAX; + int faceNormal = 0; + PolygonData vertexData = bodyB->shape.vertexData; + + for (int i = 0; i < vertexData.vertexCount; i++) + { + float currentSeparation = MathDot(vertexData.normals[i], Vector2Subtract(center, vertexData.vertices[i])); + + if (currentSeparation > bodyA->shape.radius) return; + + if (currentSeparation > separation) + { + separation = currentSeparation; + faceNormal = i; + } + } + + // Grab face's vertices + Vector2 v1 = vertexData.vertices[faceNormal]; + int nextIndex = (((faceNormal + 1) < vertexData.vertexCount) ? (faceNormal + 1) : 0); + Vector2 v2 = vertexData.vertices[nextIndex]; + + // Check to see if center is within polygon + if (separation < PHYSAC_EPSILON) + { + manifold->contactsCount = 1; + Vector2 normal = Mat2MultiplyVector2(vertexData.transform, vertexData.normals[faceNormal]); + manifold->normal = (Vector2){ -normal.x, -normal.y }; + manifold->contacts[0] = (Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + manifold->penetration = bodyA->shape.radius; + return; + } + + // Determine which voronoi region of the edge center of circle lies within + float dot1 = MathDot(Vector2Subtract(center, v1), Vector2Subtract(v2, v1)); + float dot2 = MathDot(Vector2Subtract(center, v2), Vector2Subtract(v1, v2)); + manifold->penetration = bodyA->shape.radius - separation; + + if (dot1 <= 0) // Closest to v1 + { + if (DistSqr(center, v1) > bodyA->shape.radius*bodyA->shape.radius) return; + + manifold->contactsCount = 1; + Vector2 normal = Vector2Subtract(v1, center); + normal = Mat2MultiplyVector2(vertexData.transform, normal); + MathNormalize(&normal); + manifold->normal = normal; + v1 = Mat2MultiplyVector2(vertexData.transform, v1); + v1 = Vector2Add(v1, bodyB->position); + manifold->contacts[0] = v1; + } + else if (dot2 <= 0) // Closest to v2 + { + if (DistSqr(center, v2) > bodyA->shape.radius*bodyA->shape.radius) return; + + manifold->contactsCount = 1; + Vector2 normal = Vector2Subtract(v2, center); + v2 = Mat2MultiplyVector2(vertexData.transform, v2); + v2 = Vector2Add(v2, bodyB->position); + manifold->contacts[0] = v2; + normal = Mat2MultiplyVector2(vertexData.transform, normal); + MathNormalize(&normal); + manifold->normal = normal; + } + else // Closest to face + { + Vector2 normal = vertexData.normals[faceNormal]; + + if (MathDot(Vector2Subtract(center, v1), normal) > bodyA->shape.radius) return; + + normal = Mat2MultiplyVector2(vertexData.transform, normal); + manifold->normal = (Vector2){ -normal.x, -normal.y }; + manifold->contacts[0] = (Vector2){ manifold->normal.x*bodyA->shape.radius + bodyA->position.x, manifold->normal.y*bodyA->shape.radius + bodyA->position.y }; + manifold->contactsCount = 1; + } +} + +// Solves collision between a polygon to a circle shape physics bodies +static void SolvePolygonToCircle(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + manifold->bodyA = bodyB; + manifold->bodyB = bodyA; + SolveCircleToPolygon(manifold); + + manifold->normal.x *= -1; + manifold->normal.y *= -1; +} + +// Solves collision between two polygons shape physics bodies +static void SolvePolygonToPolygon(PhysicsManifold manifold) +{ + PhysicsShape bodyA = manifold->bodyA->shape; + PhysicsShape bodyB = manifold->bodyB->shape; + manifold->contactsCount = 0; + + // Check for separating axis with A shape's face planes + int faceA = 0; + float penetrationA = FindAxisLeastPenetration(&faceA, bodyA, bodyB); + if (penetrationA >= 0) return; + + // Check for separating axis with B shape's face planes + int faceB = 0; + float penetrationB = FindAxisLeastPenetration(&faceB, bodyB, bodyA); + if (penetrationB >= 0) return; + + int referenceIndex = 0; + bool flip = false; // Always point from A shape to B shape + + PhysicsShape refPoly; // Reference + PhysicsShape incPoly; // Incident + + // Determine which shape contains reference face + if (BiasGreaterThan(penetrationA, penetrationB)) + { + refPoly = bodyA; + incPoly = bodyB; + referenceIndex = faceA; + } + else + { + refPoly = bodyB; + incPoly = bodyA; + referenceIndex = faceB; + flip = true; + } + + // World space incident face + Vector2 incidentFace[2]; + FindIncidentFace(&incidentFace[0], &incidentFace[1], refPoly, incPoly, referenceIndex); + + // Setup reference face vertices + PolygonData refData = refPoly.vertexData; + Vector2 v1 = refData.vertices[referenceIndex]; + referenceIndex = (((referenceIndex + 1) < refData.vertexCount) ? (referenceIndex + 1) : 0); + Vector2 v2 = refData.vertices[referenceIndex]; + + // Transform vertices to world space + v1 = Mat2MultiplyVector2(refData.transform, v1); + v1 = Vector2Add(v1, refPoly.body->position); + v2 = Mat2MultiplyVector2(refData.transform, v2); + v2 = Vector2Add(v2, refPoly.body->position); + + // Calculate reference face side normal in world space + Vector2 sidePlaneNormal = Vector2Subtract(v2, v1); + MathNormalize(&sidePlaneNormal); + + // Orthogonalize + Vector2 refFaceNormal = { sidePlaneNormal.y, -sidePlaneNormal.x }; + float refC = MathDot(refFaceNormal, v1); + float negSide = MathDot(sidePlaneNormal, v1)*-1; + float posSide = MathDot(sidePlaneNormal, v2); + + // Clip incident face to reference face side planes (due to floating point error, possible to not have required points + if (Clip((Vector2){ -sidePlaneNormal.x, -sidePlaneNormal.y }, negSide, &incidentFace[0], &incidentFace[1]) < 2) return; + if (Clip(sidePlaneNormal, posSide, &incidentFace[0], &incidentFace[1]) < 2) return; + + // Flip normal if required + manifold->normal = (flip ? (Vector2){ -refFaceNormal.x, -refFaceNormal.y } : refFaceNormal); + + // Keep points behind reference face + int currentPoint = 0; // Clipped points behind reference face + float separation = MathDot(refFaceNormal, incidentFace[0]) - refC; + if (separation <= 0) + { + manifold->contacts[currentPoint] = incidentFace[0]; + manifold->penetration = -separation; + currentPoint++; + } + else manifold->penetration = 0; + + separation = MathDot(refFaceNormal, incidentFace[1]) - refC; + + if (separation <= 0) + { + manifold->contacts[currentPoint] = incidentFace[1]; + manifold->penetration += -separation; + currentPoint++; + + // Calculate total penetration average + manifold->penetration /= currentPoint; + } + + manifold->contactsCount = currentPoint; +} + +// Integrates physics forces into velocity +static void IntegratePhysicsForces(PhysicsBody body) +{ + if (body->inverseMass == 0 || !body->enabled) return; + + body->velocity.x += (body->force.x*body->inverseMass)*(deltaTime/2); + body->velocity.y += (body->force.y*body->inverseMass)*(deltaTime/2); + + if (body->useGravity) + { + body->velocity.x += gravityForce.x*(deltaTime/2); + body->velocity.y += gravityForce.y*(deltaTime/2); + } + + if (!body->freezeOrient) body->angularVelocity += body->torque*body->inverseInertia*(deltaTime/2); +} + +// Initializes physics manifolds to solve collisions +static void InitializePhysicsManifolds(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + // Calculate average restitution, static and dynamic friction + manifold->restitution = sqrtf(bodyA->restitution*bodyB->restitution); + manifold->staticFriction = sqrtf(bodyA->staticFriction*bodyB->staticFriction); + manifold->dynamicFriction = sqrtf(bodyA->dynamicFriction*bodyB->dynamicFriction); + + for (int i = 0; i < 2; i++) + { + // Caculate radius from center of mass to contact + Vector2 radiusA = Vector2Subtract(manifold->contacts[i], bodyA->position); + Vector2 radiusB = Vector2Subtract(manifold->contacts[i], bodyB->position); + + Vector2 crossA = MathCross(bodyA->angularVelocity, radiusA); + Vector2 crossB = MathCross(bodyB->angularVelocity, radiusB); + + Vector2 radiusV = { 0 }; + radiusV.x = bodyB->velocity.x + crossB.x - bodyA->velocity.x - crossA.x; + radiusV.y = bodyB->velocity.y + crossB.y - bodyA->velocity.y - crossA.y; + + // Determine if we should perform a resting collision or not; + // The idea is if the only thing moving this object is gravity, then the collision should be performed without any restitution + if (MathLenSqr(radiusV) < (MathLenSqr((Vector2){ gravityForce.x*deltaTime, gravityForce.y*deltaTime }) + PHYSAC_EPSILON)) manifold->restitution = 0; + } +} + +// Integrates physics collisions impulses to solve collisions +static void IntegratePhysicsImpulses(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + // Early out and positional correct if both objects have infinite mass + if (fabs(bodyA->inverseMass + bodyB->inverseMass) <= PHYSAC_EPSILON) + { + bodyA->velocity = (Vector2){ 0 }; + bodyB->velocity = (Vector2){ 0 }; + return; + } + + for (int i = 0; i < manifold->contactsCount; i++) + { + // Calculate radius from center of mass to contact + Vector2 radiusA = Vector2Subtract(manifold->contacts[i], bodyA->position); + Vector2 radiusB = Vector2Subtract(manifold->contacts[i], bodyB->position); + + // Calculate relative velocity + Vector2 radiusV = { 0 }; + radiusV.x = bodyB->velocity.x + MathCross(bodyB->angularVelocity, radiusB).x - bodyA->velocity.x - MathCross(bodyA->angularVelocity, radiusA).x; + radiusV.y = bodyB->velocity.y + MathCross(bodyB->angularVelocity, radiusB).y - bodyA->velocity.y - MathCross(bodyA->angularVelocity, radiusA).y; + + // Relative velocity along the normal + float contactVelocity = MathDot(radiusV, manifold->normal); + + // Do not resolve if velocities are separating + if (contactVelocity > 0) return; + + float raCrossN = MathCrossVector2(radiusA, manifold->normal); + float rbCrossN = MathCrossVector2(radiusB, manifold->normal); + + float inverseMassSum = bodyA->inverseMass + bodyB->inverseMass + (raCrossN*raCrossN)*bodyA->inverseInertia + (rbCrossN*rbCrossN)*bodyB->inverseInertia; + + // Calculate impulse scalar value + float impulse = -(1.0f + manifold->restitution)*contactVelocity; + impulse /= inverseMassSum; + impulse /= (float)manifold->contactsCount; + + // Apply impulse to each physics body + Vector2 impulseV = { manifold->normal.x*impulse, manifold->normal.y*impulse }; + + if (bodyA->enabled) + { + bodyA->velocity.x += bodyA->inverseMass*(-impulseV.x); + bodyA->velocity.y += bodyA->inverseMass*(-impulseV.y); + if (!bodyA->freezeOrient) bodyA->angularVelocity += bodyA->inverseInertia*MathCrossVector2(radiusA, (Vector2){ -impulseV.x, -impulseV.y }); + } + + if (bodyB->enabled) + { + bodyB->velocity.x += bodyB->inverseMass*(impulseV.x); + bodyB->velocity.y += bodyB->inverseMass*(impulseV.y); + if (!bodyB->freezeOrient) bodyB->angularVelocity += bodyB->inverseInertia*MathCrossVector2(radiusB, impulseV); + } + + // Apply friction impulse to each physics body + radiusV.x = bodyB->velocity.x + MathCross(bodyB->angularVelocity, radiusB).x - bodyA->velocity.x - MathCross(bodyA->angularVelocity, radiusA).x; + radiusV.y = bodyB->velocity.y + MathCross(bodyB->angularVelocity, radiusB).y - bodyA->velocity.y - MathCross(bodyA->angularVelocity, radiusA).y; + + Vector2 tangent = { radiusV.x - (manifold->normal.x*MathDot(radiusV, manifold->normal)), radiusV.y - (manifold->normal.y*MathDot(radiusV, manifold->normal)) }; + MathNormalize(&tangent); + + // Calculate impulse tangent magnitude + float impulseTangent = -MathDot(radiusV, tangent); + impulseTangent /= inverseMassSum; + impulseTangent /= (float)manifold->contactsCount; + + float absImpulseTangent = fabs(impulseTangent); + + // Don't apply tiny friction impulses + if (absImpulseTangent <= PHYSAC_EPSILON) return; + + // Apply coulumb's law + Vector2 tangentImpulse = { 0 }; + if (absImpulseTangent < impulse*manifold->staticFriction) tangentImpulse = (Vector2){ tangent.x*impulseTangent, tangent.y*impulseTangent }; + else tangentImpulse = (Vector2){ tangent.x*-impulse*manifold->dynamicFriction, tangent.y*-impulse*manifold->dynamicFriction }; + + // Apply friction impulse + if (bodyA->enabled) + { + bodyA->velocity.x += bodyA->inverseMass*(-tangentImpulse.x); + bodyA->velocity.y += bodyA->inverseMass*(-tangentImpulse.y); + + if (!bodyA->freezeOrient) bodyA->angularVelocity += bodyA->inverseInertia*MathCrossVector2(radiusA, (Vector2){ -tangentImpulse.x, -tangentImpulse.y }); + } + + if (bodyB->enabled) + { + bodyB->velocity.x += bodyB->inverseMass*(tangentImpulse.x); + bodyB->velocity.y += bodyB->inverseMass*(tangentImpulse.y); + + if (!bodyB->freezeOrient) bodyB->angularVelocity += bodyB->inverseInertia*MathCrossVector2(radiusB, tangentImpulse); + } + } +} + +// Integrates physics velocity into position and forces +static void IntegratePhysicsVelocity(PhysicsBody body) +{ + if (!body->enabled) return; + + body->position.x += body->velocity.x*deltaTime; + body->position.y += body->velocity.y*deltaTime; + + if (!body->freezeOrient) body->orient += body->angularVelocity*deltaTime; + Mat2Set(&body->shape.vertexData.transform, body->orient); + + IntegratePhysicsForces(body); +} + +// Corrects physics bodies positions based on manifolds collision information +static void CorrectPhysicsPositions(PhysicsManifold manifold) +{ + PhysicsBody bodyA = manifold->bodyA; + PhysicsBody bodyB = manifold->bodyB; + + Vector2 correction = { 0 }; + correction.x = (max(manifold->penetration - PHYSAC_PENETRATION_ALLOWANCE, 0)/(bodyA->inverseMass + bodyB->inverseMass))*manifold->normal.x*PHYSAC_PENETRATION_CORRECTION; + correction.y = (max(manifold->penetration - PHYSAC_PENETRATION_ALLOWANCE, 0)/(bodyA->inverseMass + bodyB->inverseMass))*manifold->normal.y*PHYSAC_PENETRATION_CORRECTION; + + if (bodyA->enabled) + { + bodyA->position.x -= correction.x*bodyA->inverseMass; + bodyA->position.y -= correction.y*bodyA->inverseMass; + } + + if (bodyB->enabled) + { + bodyB->position.x += correction.x*bodyB->inverseMass; + bodyB->position.y += correction.y*bodyB->inverseMass; + } +} + +// Returns the extreme point along a direction within a polygon +static Vector2 GetSupport(PhysicsShape shape, Vector2 dir) +{ + float bestProjection = -PHYSAC_FLT_MAX; + Vector2 bestVertex = { 0 }; + PolygonData data = shape.vertexData; + + for (int i = 0; i < data.vertexCount; i++) + { + Vector2 vertex = data.vertices[i]; + float projection = MathDot(vertex, dir); + + if (projection > bestProjection) + { + bestVertex = vertex; + bestProjection = projection; + } + } + + return bestVertex; +} + +// Finds polygon shapes axis least penetration +static float FindAxisLeastPenetration(int *faceIndex, PhysicsShape shapeA, PhysicsShape shapeB) +{ + float bestDistance = -PHYSAC_FLT_MAX; + int bestIndex = 0; + + PolygonData dataA = shapeA.vertexData; + PolygonData dataB = shapeB.vertexData; + + for (int i = 0; i < dataA.vertexCount; i++) + { + // Retrieve a face normal from A shape + Vector2 normal = dataA.normals[i]; + Vector2 transNormal = Mat2MultiplyVector2(dataA.transform, normal); + + // Transform face normal into B shape's model space + Mat2 buT = Mat2Transpose(dataB.transform); + normal = Mat2MultiplyVector2(buT, transNormal); + + // Retrieve support point from B shape along -n + Vector2 support = GetSupport(shapeB, (Vector2){ -normal.x, -normal.y }); + + // Retrieve vertex on face from A shape, transform into B shape's model space + Vector2 vertex = dataA.vertices[i]; + vertex = Mat2MultiplyVector2(dataA.transform, vertex); + vertex = Vector2Add(vertex, shapeA.body->position); + vertex = Vector2Subtract(vertex, shapeB.body->position); + vertex = Mat2MultiplyVector2(buT, vertex); + + // Compute penetration distance in B shape's model space + float distance = MathDot(normal, Vector2Subtract(support, vertex)); + + // Store greatest distance + if (distance > bestDistance) + { + bestDistance = distance; + bestIndex = i; + } + } + + *faceIndex = bestIndex; + return bestDistance; +} + +// Finds two polygon shapes incident face +static void FindIncidentFace(Vector2 *v0, Vector2 *v1, PhysicsShape ref, PhysicsShape inc, int index) +{ + PolygonData refData = ref.vertexData; + PolygonData incData = inc.vertexData; + + Vector2 referenceNormal = refData.normals[index]; + + // Calculate normal in incident's frame of reference + referenceNormal = Mat2MultiplyVector2(refData.transform, referenceNormal); // To world space + referenceNormal = Mat2MultiplyVector2(Mat2Transpose(incData.transform), referenceNormal); // To incident's model space + + // Find most anti-normal face on polygon + int incidentFace = 0; + float minDot = PHYSAC_FLT_MAX; + + for (int i = 0; i < incData.vertexCount; i++) + { + float dot = MathDot(referenceNormal, incData.normals[i]); + + if (dot < minDot) + { + minDot = dot; + incidentFace = i; + } + } + + // Assign face vertices for incident face + *v0 = Mat2MultiplyVector2(incData.transform, incData.vertices[incidentFace]); + *v0 = Vector2Add(*v0, inc.body->position); + incidentFace = (((incidentFace + 1) < incData.vertexCount) ? (incidentFace + 1) : 0); + *v1 = Mat2MultiplyVector2(incData.transform, incData.vertices[incidentFace]); + *v1 = Vector2Add(*v1, inc.body->position); +} + +// Calculates clipping based on a normal and two faces +static int Clip(Vector2 normal, float clip, Vector2 *faceA, Vector2 *faceB) +{ + int sp = 0; + Vector2 out[2] = { *faceA, *faceB }; + + // Retrieve distances from each endpoint to the line + float distanceA = MathDot(normal, *faceA) - clip; + float distanceB = MathDot(normal, *faceB) - clip; + + // If negative (behind plane) + if (distanceA <= 0) out[sp++] = *faceA; + if (distanceB <= 0) out[sp++] = *faceB; + + // If the points are on different sides of the plane + if ((distanceA*distanceB) < 0) + { + // Push intersection point + float alpha = distanceA/(distanceA - distanceB); + out[sp] = *faceA; + Vector2 delta = Vector2Subtract(*faceB, *faceA); + delta.x *= alpha; + delta.y *= alpha; + out[sp] = Vector2Add(out[sp], delta); + sp++; + } + + // Assign the new converted values + *faceA = out[0]; + *faceB = out[1]; + + return sp; +} + +// Check if values are between bias range +static bool BiasGreaterThan(float valueA, float valueB) +{ + return (valueA >= (valueB*0.95f + valueA*0.01f)); +} + +// Returns the barycenter of a triangle given by 3 points +static Vector2 TriangleBarycenter(Vector2 v1, Vector2 v2, Vector2 v3) +{ + Vector2 result = { 0 }; + + result.x = (v1.x + v2.x + v3.x)/3; + result.y = (v1.y + v2.y + v3.y)/3; + + return result; +} + +// Initializes hi-resolution timer +static void InitTimer(void) +{ + srand(time(NULL)); // Initialize random seed + + #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) baseTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; + #endif + + startTime = GetCurrentTime(); +} + +// Get current time in milliseconds +static double GetCurrentTime(void) +{ + double time = 0; + + #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + unsigned long long int clockFrequency, currentTime; + + QueryPerformanceFrequency(&clockFrequency); + QueryPerformanceCounter(¤tTime); + + time = (double)((double)currentTime/clockFrequency)*1000; + #endif + + #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t temp = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec; + + time = (double)((double)(temp - baseTime)*1e-6); + #endif + + return time; +} + +// Returns a random number between min and max (both included) +static int GetRandomNumber(int min, int max) +{ + if (min > max) + { + int tmp = max; + max = min; + min = tmp; + } + + return (rand()%(abs(max - min) + 1) + min); +} + +// Clamp a value in a range +static inline void MathClamp(double *value, double min, double max) +{ + if (*value < min) *value = min; + else if (*value > max) *value = max; +} + +// Returns the cross product of a vector and a value +static inline Vector2 MathCross(float value, Vector2 vector) +{ + return (Vector2){ -value*vector.y, value*vector.x }; +} + +// Returns the cross product of two vectors +static inline float MathCrossVector2(Vector2 v1, Vector2 v2) +{ + return (v1.x*v2.y - v1.y*v2.x); +} + +// Returns the len square root of a vector +static inline float MathLenSqr(Vector2 vector) +{ + return (vector.x*vector.x + vector.y*vector.y); +} + +// Returns the dot product of two vectors +static inline float MathDot(Vector2 v1, Vector2 v2) +{ + return (v1.x*v2.x + v1.y*v2.y); +} + +// Returns the square root of distance between two vectors +static inline float DistSqr(Vector2 v1, Vector2 v2) +{ + Vector2 dir = Vector2Subtract(v1, v2); + return MathDot(dir, dir); +} + +// Returns the normalized values of a vector +static void MathNormalize(Vector2 *vector) +{ + float length, ilength; + + Vector2 aux = *vector; + length = sqrtf(aux.x*aux.x + aux.y*aux.y); + + if (length == 0) length = 1.0f; + + ilength = 1.0f/length; + + vector->x *= ilength; + vector->y *= ilength; +} + +// Returns the sum of two given vectors +static inline Vector2 Vector2Add(Vector2 v1, Vector2 v2) +{ + return (Vector2){ v1.x + v2.x, v1.y + v2.y }; +} + +// Returns the subtract of two given vectors +static inline Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) +{ + return (Vector2){ v1.x - v2.x, v1.y - v2.y }; +} + +// Creates a matrix 2x2 from a given radians value +static inline Mat2 Mat2Radians(float radians) +{ + float c = cosf(radians); + float s = sinf(radians); + + return (Mat2){ c, -s, s, c }; +} + +// Set values from radians to a created matrix 2x2 +static void Mat2Set(Mat2 *matrix, float radians) +{ + float cos = cosf(radians); + float sin = sinf(radians); + + matrix->m00 = cos; + matrix->m01 = -sin; + matrix->m10 = sin; + matrix->m11 = cos; +} + +// Returns the transpose of a given matrix 2x2 +static inline Mat2 Mat2Transpose(Mat2 matrix) +{ + return (Mat2){ matrix.m00, matrix.m10, matrix.m01, matrix.m11 }; +} + +// Multiplies a vector by a matrix 2x2 +static inline Vector2 Mat2MultiplyVector2(Mat2 matrix, Vector2 vector) +{ + return (Vector2){ matrix.m00*vector.x + matrix.m01*vector.y, matrix.m10*vector.x + matrix.m11*vector.y }; +} + +#endif // PHYSAC_IMPLEMENTATION From d0fca7e02b56a88d26a40d96e8e4842c8c29a2fd Mon Sep 17 00:00:00 2001 From: victorfisac Date: Mon, 21 Nov 2016 20:31:07 +0100 Subject: [PATCH 2/3] Removed old Physac examples --- examples/physics_basic_rigidbody.c | 135 ------------------- examples/physics_basic_rigidbody.png | Bin 15294 -> 0 bytes examples/physics_forces.c | 193 --------------------------- examples/physics_forces.png | Bin 17935 -> 0 bytes 4 files changed, 328 deletions(-) delete mode 100644 examples/physics_basic_rigidbody.c delete mode 100644 examples/physics_basic_rigidbody.png delete mode 100644 examples/physics_forces.c delete mode 100644 examples/physics_forces.png diff --git a/examples/physics_basic_rigidbody.c b/examples/physics_basic_rigidbody.c deleted file mode 100644 index 87316a98..00000000 --- a/examples/physics_basic_rigidbody.c +++ /dev/null @@ -1,135 +0,0 @@ -/******************************************************************************************* -* -* raylib [physac] example - Basic rigidbody -* -* This example has been created using raylib 1.5 (www.raylib.com) -* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) -* -* NOTE: -* Physac requires multi-threading, when InitPhysics() a second thread is created to manage -* physics calculations. To accomplish that, physac uses pthread Win32 library that can be -* found inside raylib/src/external/pthread directory. -* -* Add pthread library when compiling physac example: -* gcc -o $(NAME_PART).exe $(FILE_NAME) $(RAYLIB_DIR)\raylib_icon -L../src/external/pthread/lib \ -* -I../src -I../src/external/pthread/include -lraylib -lglfw3 -lopengl32 -lgdi32 -lpthreadGC2 -std=c99 -Wall -* -* Note that pthreadGC2.dll must be also copied to project directory! -* -* Copyright (c) 2016 Victor Fisac and Ramon Santamaria (@raysan5) -* -********************************************************************************************/ - -#include "raylib.h" - -#define PHYSAC_IMPLEMENTATION -#include "physac.h" - -#define MOVE_VELOCITY 5 -#define JUMP_VELOCITY 30 - -int main() -{ - // Initialization - //-------------------------------------------------------------------------------------- - int screenWidth = 800; - int screenHeight = 450; - - InitWindow(screenWidth, screenHeight, "raylib [physac] example - basic rigidbody"); - InitPhysics((Vector2){ 0.0f, -9.81f/2 }); // Initialize physics module - - // Debug variables - bool isDebug = false; - - // Create rectangle physic object - PhysicBody rectangle = CreatePhysicBody((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 - PhysicBody square = CreatePhysicBody((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 - PhysicBody floor = CreatePhysicBody((Vector2){ screenWidth/2, screenHeight*0.95f }, 0.0f, (Vector2){ screenWidth*0.9f, 100 }); - PhysicBody leftWall = CreatePhysicBody((Vector2){ 0.0f, screenHeight/2 }, 0.0f, (Vector2){ screenWidth*0.1f, screenHeight }); - PhysicBody rightWall = CreatePhysicBody((Vector2){ screenWidth, screenHeight/2 }, 0.0f, (Vector2){ screenWidth*0.1f, screenHeight }); - PhysicBody roof = CreatePhysicBody((Vector2){ screenWidth/2, screenHeight*0.05f }, 0.0f, (Vector2){ screenWidth*0.9f, 100 }); - - // Create pplatform physic object - PhysicBody platform = CreatePhysicBody((Vector2){ screenWidth/2, screenHeight*0.7f }, 0.0f, (Vector2){ screenWidth*0.25f, 20 }); - - SetTargetFPS(60); - //-------------------------------------------------------------------------------------- - - // Main game loop - while (!WindowShouldClose()) // Detect window close button or ESC key - { - // Update - //---------------------------------------------------------------------------------- - // Check rectangle movement inputs - if (IsKeyPressed('W')) rectangle->rigidbody.velocity.y = JUMP_VELOCITY; - if (IsKeyDown('A')) rectangle->rigidbody.velocity.x = -MOVE_VELOCITY; - else if (IsKeyDown('D')) rectangle->rigidbody.velocity.x = MOVE_VELOCITY; - - // 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; - - // Check debug switch input - if (IsKeyPressed('P')) isDebug = !isDebug; - //---------------------------------------------------------------------------------- - - // Draw - //---------------------------------------------------------------------------------- - BeginDrawing(); - - ClearBackground(RAYWHITE); - - // 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); - DrawRectangleLines(leftWall->collider.bounds.x, leftWall->collider.bounds.y, leftWall->collider.bounds.width, leftWall->collider.bounds.height, GREEN); - DrawRectangleLines(rightWall->collider.bounds.x, rightWall->collider.bounds.y, rightWall->collider.bounds.width, rightWall->collider.bounds.height, GREEN); - DrawRectangleLines(roof->collider.bounds.x, roof->collider.bounds.y, roof->collider.bounds.width, roof->collider.bounds.height, GREEN); - DrawRectangleLines(platform->collider.bounds.x, platform->collider.bounds.y, platform->collider.bounds.width, platform->collider.bounds.height, GREEN); - DrawRectangleLines(rectangle->collider.bounds.x, rectangle->collider.bounds.y, rectangle->collider.bounds.width, rectangle->collider.bounds.height, GREEN); - DrawRectangleLines(square->collider.bounds.x, square->collider.bounds.y, square->collider.bounds.width, square->collider.bounds.height, GREEN); - } - - // Draw help message - DrawText("Use WASD to move rectangle and ARROWS to move square", screenWidth/2 - MeasureText("Use WASD to move rectangle and ARROWS to move square", 20)/2, screenHeight*0.075f, 20, LIGHTGRAY); - - DrawFPS(10, 10); - - EndDrawing(); - //---------------------------------------------------------------------------------- - } - - // De-Initialization - //-------------------------------------------------------------------------------------- - ClosePhysics(); // Unitialize physics (including all loaded objects) - CloseWindow(); // Close window and OpenGL context - //-------------------------------------------------------------------------------------- - - return 0; -} \ No newline at end of file diff --git a/examples/physics_basic_rigidbody.png b/examples/physics_basic_rigidbody.png deleted file mode 100644 index 52f265acf9a73ee1fcc41cdcc545fa841ce59354..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15294 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU_8XZ1{4ubUVDOp!MfGc#WAEJE}4gelKR^Gp|3~5$djgf8r~-lCK7K8{f6I@~{0W*u`G* zoo`!(#?%tTYxi`0JdzEYoCXU)zBUjthD$8KM6k%v!m0 zd+iHF_qU%aO>)5!J#7pdA>~CF_Cga_nu3ZfV}-*i8wGcZH(l9!KkN8sho_Gh&XRM#&2?Cr#qIQIu0={k z8>UX*amP8c)*#^NgN#b2`vMXmw-;hBj)CQL2{*|5iPLU)%-yJ9(&YI5fnwPc=DV?1 zQ}W;J{GEU69p9|FMPW6( zruByp&A;}Pf9KO{2`iUngr4i0HhqGGT-H<7rB4kO*0`_zG~-T&2Ibq8g)&!jZc4+IiNQ(k9&YXqY-$VnOLp{^{PyfE&1$_Ka5n$S zteqhFILrMm}a=AJGfQvCKM8r zZdk3@bFIdNzpss2k2dr%tM1ul@=h6|kS5a<7 z*X@F;do}bvXKXc_98{dS)lF>r&KJri%zM+OhWz%FUAQ##htTX?PhbSRdMSJU$u#jL zyCz=|bcWk~(pWg&khN7< z+?fk)IJ9MO@O-lsSj?G?+>+P;an>%J6)z~P-hnbAUWB>7We#q_x3Sm`brE}t z2``4rV6Nra4~dPdI6Vo<@xTU7k~A(SfLmzT8fOqOU_t%K{2#c!EN<9TN7SNMgejh9$swND=f3k-Sk^ zpdu&(7|t7e@i-xZtvW(Kc2|jsCC=CokYu`e@nW;p8c?#D{0X~nfK>#+WJR!5OZ${P z>I6`iV>A>-LjhL2kEVsuv@n_$kedOcIbt+NjOK{Z9DzPSJX$V{mJ6fh!f3fLS}tH4 zlNc=>M@z@i(s8tO94#G3aOv2PYO)5nqQIc#fV=SWiyaC^EI-b_k>*{EIxr0yg5Lz0 zPplw+{Q>ON1X58sMzR2k`9j#SmM3lk30*K?AuMXEZZFRsoF8 z34)g;P%tMrBr=1};zVF>Y*qwUFTEX@)eCQqgzx6&U!eEZqxy8qZ$nbP)m3;6h=b{L@pP&E$ diff --git a/examples/physics_forces.c b/examples/physics_forces.c deleted file mode 100644 index e45cb44c..00000000 --- a/examples/physics_forces.c +++ /dev/null @@ -1,193 +0,0 @@ -/******************************************************************************************* -* -* raylib [physac] example - Forces -* -* This example has been created using raylib 1.5 (www.raylib.com) -* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) -* -* NOTE: -* Physac requires multi-threading, when InitPhysics() a second thread is created to manage -* physics calculations. To accomplish that, physac uses pthread Win32 library that can be -* found inside raylib/src/external/pthread directory. -* -* Add pthread library when compiling physac example: -* gcc -o $(NAME_PART).exe $(FILE_NAME) $(RAYLIB_DIR)\raylib_icon -L../src/external/pthread/lib \ -* -I../src -I../src/external/pthread/include -lraylib -lglfw3 -lopengl32 -lgdi32 -lpthreadGC2 -std=c99 -Wall -* -* Note that pthreadGC2.dll must be also copied to project directory! -* -* Copyright (c) 2016 Victor Fisac and Ramon Santamaria (@raysan5) -* -********************************************************************************************/ - -#include "raylib.h" - -#define PHYSAC_IMPLEMENTATION -#include "physac.h" - -#define FORCE_AMOUNT 5.0f -#define FORCE_RADIUS 150 -#define LINE_LENGTH 75 -#define TRIANGLE_LENGTH 12 - -int main() -{ - // Initialization - //-------------------------------------------------------------------------------------- - int screenWidth = 800; - int screenHeight = 450; - - InitWindow(screenWidth, screenHeight, "raylib [physac] example - forces"); - InitPhysics((Vector2){ 0.0f, -9.81f/2 }); // Initialize physics module - - // Global variables - Vector2 mousePosition; - bool isDebug = false; - - // Create rectangle physic objects - PhysicBody rectangles[3]; - for (int i = 0; i < 3; i++) - { - rectangles[i] = CreatePhysicBody((Vector2){ screenWidth/4*(i+1), (((i % 2) == 0) ? (screenHeight/3) : (screenHeight/1.5f)) }, 0.0f, (Vector2){ 50, 50 }); - rectangles[i]->rigidbody.enabled = true; // Enable physic object rigidbody behaviour - rectangles[i]->rigidbody.friction = 0.1f; - } - - // 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. - PhysicBody circles[3]; - for (int i = 0; i < 3; i++) - { - circles[i] = CreatePhysicBody((Vector2){ screenWidth/4*(i+1), (((i % 2) == 0) ? (screenHeight/1.5f) : (screenHeight/4)) }, 0.0f, (Vector2){ 0, 0 }); - circles[i]->rigidbody.enabled = true; // Enable physic object rigidbody behaviour - circles[i]->rigidbody.friction = 0.1f; - circles[i]->collider.type = COLLIDER_CIRCLE; - circles[i]->collider.radius = 25; - } - - // Create walls physic objects - PhysicBody leftWall = CreatePhysicBody((Vector2){ -25, screenHeight/2 }, 0.0f, (Vector2){ 50, screenHeight }); - PhysicBody rightWall = CreatePhysicBody((Vector2){ screenWidth + 25, screenHeight/2 }, 0.0f, (Vector2){ 50, screenHeight }); - PhysicBody topWall = CreatePhysicBody((Vector2){ screenWidth/2, -25 }, 0.0f, (Vector2){ screenWidth, 50 }); - PhysicBody bottomWall = CreatePhysicBody((Vector2){ screenWidth/2, screenHeight + 25 }, 0.0f, (Vector2){ screenWidth, 50 }); - - SetTargetFPS(60); - //-------------------------------------------------------------------------------------- - - // Main game loop - while (!WindowShouldClose()) // Detect window close button or ESC key - { - // Update - //---------------------------------------------------------------------------------- - - // Update mouse position value - mousePosition = GetMousePosition(); - - // Check force input - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) ApplyForceAtPosition(mousePosition, FORCE_AMOUNT, FORCE_RADIUS); - - // Check reset input - if (IsKeyPressed('R')) - { - // Reset rectangle physic objects positions - for (int i = 0; i < 3; i++) - { - rectangles[i]->transform.position = (Vector2){ screenWidth/4*(i+1) - rectangles[i]->transform.scale.x/2, (((i % 2) == 0) ? (screenHeight/3) : (screenHeight/1.5f)) - rectangles[i]->transform.scale.y/2 }; - rectangles[i]->rigidbody.velocity =(Vector2){ 0.0f, 0.0f }; - } - - // Reset circles physic objects positions - for (int i = 0; i < 3; i++) - { - circles[i]->transform.position = (Vector2){ screenWidth/4*(i+1), (((i % 2) == 0) ? (screenHeight/1.5f) : (screenHeight/4)) }; - circles[i]->rigidbody.velocity =(Vector2){ 0.0f, 0.0f }; - } - } - - // Check debug switch input - if (IsKeyPressed('P')) isDebug = !isDebug; - //---------------------------------------------------------------------------------- - - // Draw - //---------------------------------------------------------------------------------- - BeginDrawing(); - - ClearBackground(RAYWHITE); - - // Draw rectangles - for (int i = 0; i < 3; i++) - { - // Convert transform values to rectangle data type variable - DrawRectangleRec(TransformToRectangle(rectangles[i]->transform), RED); - if (isDebug) DrawRectangleLines(rectangles[i]->collider.bounds.x, rectangles[i]->collider.bounds.y, rectangles[i]->collider.bounds.width, rectangles[i]->collider.bounds.height, GREEN); - - // Draw force radius - DrawCircleLines(mousePosition.x, mousePosition.y, FORCE_RADIUS, BLACK); - - // 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); - - // 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); - } - } - - // Draw circles - for (int i = 0; i < 3; i++) - { - DrawCircleV(circles[i]->transform.position, circles[i]->collider.radius, BLUE); - if (isDebug) DrawCircleLines(circles[i]->transform.position.x, circles[i]->transform.position.y, circles[i]->collider.radius, GREEN); - - // Draw force radius - DrawCircleLines(mousePosition.x, mousePosition.y, FORCE_RADIUS, BLACK); - - // 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); - - // 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); - } - } - - // Draw help messages - DrawText("Use LEFT MOUSE BUTTON to apply a force", screenWidth/2 - MeasureText("Use LEFT MOUSE BUTTON to apply a force", 20)/2, screenHeight*0.075f, 20, LIGHTGRAY); - DrawText("Use R to reset objects position", screenWidth/2 - MeasureText("Use R to reset objects position", 20)/2, screenHeight*0.875f, 20, GRAY); - - DrawFPS(10, 10); - - EndDrawing(); - //---------------------------------------------------------------------------------- - } - - // De-Initialization - //-------------------------------------------------------------------------------------- - ClosePhysics(); // Unitialize physics module - CloseWindow(); // Close window and OpenGL context - //-------------------------------------------------------------------------------------- - - return 0; -} \ No newline at end of file diff --git a/examples/physics_forces.png b/examples/physics_forces.png deleted file mode 100644 index 832bdbd9ecf79940c404362ba94916ac59f31022..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17935 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 Q(F Date: Mon, 21 Nov 2016 20:31:30 +0100 Subject: [PATCH 3/3] Added new Physac examples --- examples/physics_demo.c | 122 +++++++++++++++++++++++++++ examples/physics_demo.png | Bin 0 -> 23466 bytes examples/physics_friction.c | 136 +++++++++++++++++++++++++++++++ examples/physics_friction.png | Bin 0 -> 18150 bytes examples/physics_movement.c | 122 +++++++++++++++++++++++++++ examples/physics_movement.png | Bin 0 -> 15947 bytes examples/physics_restitution.c | 115 ++++++++++++++++++++++++++ examples/physics_restitution.png | Bin 0 -> 17833 bytes examples/physics_shatter.c | 107 ++++++++++++++++++++++++ examples/physics_shatter.png | Bin 0 -> 23197 bytes 10 files changed, 602 insertions(+) create mode 100644 examples/physics_demo.c create mode 100644 examples/physics_demo.png create mode 100644 examples/physics_friction.c create mode 100644 examples/physics_friction.png create mode 100644 examples/physics_movement.c create mode 100644 examples/physics_movement.png create mode 100644 examples/physics_restitution.c create mode 100644 examples/physics_restitution.png create mode 100644 examples/physics_shatter.c create mode 100644 examples/physics_shatter.png diff --git a/examples/physics_demo.c b/examples/physics_demo.c new file mode 100644 index 00000000..bed7c94d --- /dev/null +++ b/examples/physics_demo.c @@ -0,0 +1,122 @@ +/******************************************************************************************* +* +* Physac - Physics demo +* +* NOTE: Physac requires multi-threading, when InitPhysics() a second thread is created to manage physics calculations. +* The file pthreadGC2.dll is required to run the program; you can find it in 'src\external' +* +* Copyright (c) 2016 Victor Fisac +* +********************************************************************************************/ + +#include "raylib.h" + +#define PHYSAC_IMPLEMENTATION +#include "..\src\physac.h" + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 800; + int screenHeight = 450; + + SetConfigFlags(FLAG_MSAA_4X_HINT); + InitWindow(screenWidth, screenHeight, "Physac [raylib] - Physics demo"); + SetTargetFPS(60); + + // Physac logo drawing position + int logoX = screenWidth - MeasureText("Physac", 30) - 10; + int logoY = 15; + + // Initialize physics and default physics bodies + InitPhysics(); + + // Create floor rectangle physics body + PhysicsBody floor = CreatePhysicsBodyRectangle((Vector2){ screenWidth/2, screenHeight }, 500, 100, 10); + floor->enabled = false; // Disable body state to convert it to static (no dynamics, but collisions) + + // Create obstacle circle physics body + PhysicsBody circle = CreatePhysicsBodyCircle((Vector2){ screenWidth/2, screenHeight/2 }, 45, 10); + circle->enabled = false; // Disable body state to convert it to static (no dynamics, but collisions) + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (IsKeyPressed('R')) // Reset physics input + { + ResetPhysics(); + + floor = CreatePhysicsBodyRectangle((Vector2){ screenWidth/2, screenHeight }, 500, 100, 10); + floor->enabled = false; + + circle = CreatePhysicsBodyCircle((Vector2){ screenWidth/2, screenHeight/2 }, 45, 10); + circle->enabled = false; + } + + // Physics body creation inputs + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) CreatePhysicsBodyPolygon(GetMousePosition(), GetRandomValue(20, 80), GetRandomValue(3, 8), 10); + else if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) CreatePhysicsBodyCircle(GetMousePosition(), GetRandomValue(10, 45), 10); + + // Destroy falling physics bodies + int bodiesCount = GetPhysicsBodiesCount(); + for (int i = bodiesCount - 1; i >= 0; i--) + { + PhysicsBody body = GetPhysicsBody(i); + if (body != NULL && (body->position.y > screenHeight*2)) DestroyPhysicsBody(body); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); + + DrawFPS(screenWidth - 90, screenHeight - 30); + + // Draw created physics bodies + bodiesCount = GetPhysicsBodiesCount(); + for (int i = 0; i < bodiesCount; i++) + { + PhysicsBody body = GetPhysicsBody(i); + + if (body != NULL) + { + int vertexCount = GetPhysicsShapeVerticesCount(i); + for (int j = 0; j < vertexCount; j++) + { + // Get physics bodies shape vertices to draw lines + // Note: GetPhysicsShapeVertex() already calculates rotation transformations + Vector2 vertexA = GetPhysicsShapeVertex(body, j); + + int jj = (((j + 1) < vertexCount) ? (j + 1) : 0); // Get next vertex or first to close the shape + Vector2 vertexB = GetPhysicsShapeVertex(body, jj); + + DrawLineV(vertexA, vertexB, GREEN); // Draw a line between two vertex positions + } + } + } + + DrawText("Left mouse button to create a polygon", 10, 10, 10, WHITE); + DrawText("Right mouse button to create a circle", 10, 25, 10, WHITE); + DrawText("Press 'R' to reset example", 10, 40, 10, WHITE); + + DrawText("Physac", logoX, logoY, 30, WHITE); + DrawText("Powered by", logoX + 50, logoY - 7, 10, WHITE); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + ClosePhysics(); // Unitialize physics + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/physics_demo.png b/examples/physics_demo.png new file mode 100644 index 0000000000000000000000000000000000000000..12dc7e724c52ec20a5310c24c856bb2aa5b58d6b GIT binary patch literal 23466 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 = false; // Disable body state to convert it to static (no dynamics, but collisions) + PhysicsBody wall = CreatePhysicsBodyRectangle((Vector2){ screenWidth/2, screenHeight*0.8f }, 10, 80, 10); + wall->enabled = false; // Disable body state to convert it to static (no dynamics, but collisions) + + // Create left ramp physics body + PhysicsBody rectLeft = CreatePhysicsBodyRectangle((Vector2){ 25, screenHeight - 5 }, 250, 250, 10); + rectLeft->enabled = false; // Disable body state to convert it to static (no dynamics, but collisions) + SetPhysicsBodyRotation(rectLeft, 30*DEG2RAD); + + // Create right ramp physics body + PhysicsBody rectRight = CreatePhysicsBodyRectangle((Vector2){ screenWidth - 25, screenHeight - 5 }, 250, 250, 10); + rectRight->enabled = false; // Disable body state to convert it to static (no dynamics, but collisions) + SetPhysicsBodyRotation(rectRight, 330*DEG2RAD); + + // Create dynamic physics bodies + PhysicsBody bodyA = CreatePhysicsBodyRectangle((Vector2){ 35, screenHeight*0.6f }, 40, 40, 10); + bodyA->staticFriction = 0.1f; + bodyA->dynamicFriction = 0.1f; + SetPhysicsBodyRotation(bodyA, 30*DEG2RAD); + + PhysicsBody bodyB = CreatePhysicsBodyRectangle((Vector2){ screenWidth - 35, screenHeight*0.6f }, 40, 40, 10); + bodyB->staticFriction = 1; + bodyB->dynamicFriction = 1; + SetPhysicsBodyRotation(bodyB, 330*DEG2RAD); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (IsKeyPressed('R')) // Reset physics input + { + // Reset dynamic physics bodies position, velocity and rotation + bodyA->position = (Vector2){ 35, screenHeight*0.6f }; + bodyA->velocity = (Vector2){ 0, 0 }; + bodyA->angularVelocity = 0; + SetPhysicsBodyRotation(bodyA, 30*DEG2RAD); + + bodyB->position = (Vector2){ screenWidth - 35, screenHeight*0.6f }; + bodyB->velocity = (Vector2){ 0, 0 }; + bodyB->angularVelocity = 0; + SetPhysicsBodyRotation(bodyB, 330*DEG2RAD); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); + + DrawFPS(screenWidth - 90, screenHeight - 30); + + // Draw created physics bodies + int bodiesCount = GetPhysicsBodiesCount(); + for (int i = 0; i < bodiesCount; i++) + { + PhysicsBody body = GetPhysicsBody(i); + + if (body != NULL) + { + int vertexCount = GetPhysicsShapeVerticesCount(i); + for (int j = 0; j < vertexCount; j++) + { + // Get physics bodies shape vertices to draw lines + // Note: GetPhysicsShapeVertex() already calculates rotation transformations + Vector2 vertexA = GetPhysicsShapeVertex(body, j); + + int jj = (((j + 1) < vertexCount) ? (j + 1) : 0); // Get next vertex or first to close the shape + Vector2 vertexB = GetPhysicsShapeVertex(body, jj); + + DrawLineV(vertexA, vertexB, GREEN); // Draw a line between two vertex positions + } + } + } + + DrawRectangle(0, screenHeight - 49, screenWidth, 49, BLACK); + + DrawText("Friction amount", (screenWidth - MeasureText("Friction amount", 30))/2, 75, 30, WHITE); + DrawText("0.1", bodyA->position.x - MeasureText("0.1", 20)/2, bodyA->position.y - 7, 20, WHITE); + DrawText("1", bodyB->position.x - MeasureText("1", 20)/2, bodyB->position.y - 7, 20, WHITE); + + DrawText("Press 'R' to reset example", 10, 10, 10, WHITE); + + DrawText("Physac", logoX, logoY, 30, WHITE); + DrawText("Powered by", logoX + 50, logoY - 7, 10, WHITE); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + ClosePhysics(); // Unitialize physics + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/physics_friction.png b/examples/physics_friction.png new file mode 100644 index 0000000000000000000000000000000000000000..e791ec2b901486fb5b5d49de1d93f7ba3c92bd3e GIT binary patch literal 18150 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 M**+ep-5Cl019djzkpKVy literal 0 HcmV?d00001 diff --git a/examples/physics_movement.c b/examples/physics_movement.c new file mode 100644 index 00000000..ca18f3df --- /dev/null +++ b/examples/physics_movement.c @@ -0,0 +1,122 @@ +/******************************************************************************************* +* +* Physac - Physics movement +* +* NOTE: Physac requires multi-threading, when InitPhysics() a second thread is created to manage physics calculations. +* The file pthreadGC2.dll is required to run the program; you can find it in 'src\external' +* +* Copyright (c) 2016 Victor Fisac +* +********************************************************************************************/ + +#include "raylib.h" + +#define PHYSAC_IMPLEMENTATION +#include "..\src\physac.h" + +#define VELOCITY 0.5f + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 800; + int screenHeight = 450; + + SetConfigFlags(FLAG_MSAA_4X_HINT); + InitWindow(screenWidth, screenHeight, "Physac [raylib] - Physics movement"); + SetTargetFPS(60); + + // Physac logo drawing position + int logoX = screenWidth - MeasureText("Physac", 30) - 10; + int logoY = 15; + + // Initialize physics and default physics bodies + InitPhysics(); + + // Create floor and walls rectangle physics body + PhysicsBody floor = CreatePhysicsBodyRectangle((Vector2){ screenWidth/2, screenHeight }, screenWidth, 100, 10); + PhysicsBody platformLeft = CreatePhysicsBodyRectangle((Vector2){ screenWidth*0.25f, screenHeight*0.6f }, screenWidth*0.25f, 10, 10); + PhysicsBody platformRight = CreatePhysicsBodyRectangle((Vector2){ screenWidth*0.75f, screenHeight*0.6f }, screenWidth*0.25f, 10, 10); + PhysicsBody wallLeft = CreatePhysicsBodyRectangle((Vector2){ -5, screenHeight/2 }, 10, screenHeight, 10); + PhysicsBody wallRight = CreatePhysicsBodyRectangle((Vector2){ screenWidth + 5, screenHeight/2 }, 10, screenHeight, 10); + + // Disable dynamics to floor and walls physics bodies + floor->enabled = false; + platformLeft->enabled = false; + platformRight->enabled = false; + wallLeft->enabled = false; + wallRight->enabled = false; + + // Create movement physics body + PhysicsBody body = CreatePhysicsBodyRectangle((Vector2){ screenWidth/2, screenHeight/2 }, 50, 50, 1); + body->freezeOrient = true; // Constrain body rotation to avoid little collision torque amounts + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (IsKeyPressed('R')) // Reset physics input + { + // Reset movement physics body position, velocity and rotation + body->position = (Vector2){ screenWidth/2, screenHeight/2 }; + body->velocity = (Vector2){ 0, 0 }; + SetPhysicsBodyRotation(body, 0); + } + + // Horizontal movement input + if (IsKeyDown(KEY_RIGHT)) body->velocity.x = VELOCITY; + else if (IsKeyDown(KEY_LEFT)) body->velocity.x = -VELOCITY; + + // Vertical movement input checking if player physics body is grounded + if (IsKeyDown(KEY_UP) && body->isGrounded) body->velocity.y = -VELOCITY*4; + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); + + DrawFPS(screenWidth - 90, screenHeight - 30); + + // Draw created physics bodies + int bodiesCount = GetPhysicsBodiesCount(); + for (int i = 0; i < bodiesCount; i++) + { + PhysicsBody body = GetPhysicsBody(i); + + int vertexCount = GetPhysicsShapeVerticesCount(i); + for (int j = 0; j < vertexCount; j++) + { + // Get physics bodies shape vertices to draw lines + // Note: GetPhysicsShapeVertex() already calculates rotation transformations + Vector2 vertexA = GetPhysicsShapeVertex(body, j); + + int jj = (((j + 1) < vertexCount) ? (j + 1) : 0); // Get next vertex or first to close the shape + Vector2 vertexB = GetPhysicsShapeVertex(body, jj); + + DrawLineV(vertexA, vertexB, GREEN); // Draw a line between two vertex positions + } + } + + DrawText("Use 'ARROWS' to move player", 10, 10, 10, WHITE); + DrawText("Press 'R' to reset example", 10, 30, 10, WHITE); + + DrawText("Physac", logoX, logoY, 30, WHITE); + DrawText("Powered by", logoX + 50, logoY - 7, 10, WHITE); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + ClosePhysics(); // Unitialize physics + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/physics_movement.png b/examples/physics_movement.png new file mode 100644 index 0000000000000000000000000000000000000000..a88a7d79facfbfb5ca1a91b74cf02ebfd3f11847 GIT binary patch literal 15947 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 aJYcu*6}QKoNq|J+z_O)Y9v9r`iT?wVkXms7 literal 0 HcmV?d00001 diff --git a/examples/physics_restitution.c b/examples/physics_restitution.c new file mode 100644 index 00000000..3543db69 --- /dev/null +++ b/examples/physics_restitution.c @@ -0,0 +1,115 @@ +/******************************************************************************************* +* +* Physac - Physics restitution +* +* NOTE: Physac requires multi-threading, when InitPhysics() a second thread is created to manage physics calculations. +* The file pthreadGC2.dll is required to run the program; you can find it in 'src\external' +* +* Copyright (c) 2016 Victor Fisac +* +********************************************************************************************/ + +#include "raylib.h" + +#define PHYSAC_IMPLEMENTATION +#include "..\src\physac.h" + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 800; + int screenHeight = 450; + + SetConfigFlags(FLAG_MSAA_4X_HINT); + InitWindow(screenWidth, screenHeight, "Physac [raylib] - Physics restitution"); + SetTargetFPS(60); + + // Physac logo drawing position + int logoX = screenWidth - MeasureText("Physac", 30) - 10; + int logoY = 15; + + // Initialize physics and default physics bodies + InitPhysics(); + + // Create floor rectangle physics body + PhysicsBody floor = CreatePhysicsBodyRectangle((Vector2){ screenWidth/2, screenHeight }, screenWidth, 100, 10); + floor->enabled = false; // Disable body state to convert it to static (no dynamics, but collisions) + floor->restitution = 1; + + // Create circles physics body + PhysicsBody circleA = CreatePhysicsBodyCircle((Vector2){ screenWidth*0.25f, screenHeight/2 }, 30, 10); + circleA->restitution = 0; + PhysicsBody circleB = CreatePhysicsBodyCircle((Vector2){ screenWidth*0.5f, screenHeight/2 }, 30, 10); + circleB->restitution = 0.5f; + PhysicsBody circleC = CreatePhysicsBodyCircle((Vector2){ screenWidth*0.75f, screenHeight/2 }, 30, 10); + circleC->restitution = 1; + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (IsKeyPressed('R')) // Reset physics input + { + // Reset circles physics bodies position and velocity + circleA->position = (Vector2){ screenWidth*0.25f, screenHeight/2 }; + circleA->velocity = (Vector2){ 0, 0 }; + circleB->position = (Vector2){ screenWidth*0.5f, screenHeight/2 }; + circleB->velocity = (Vector2){ 0, 0 }; + circleC->position = (Vector2){ screenWidth*0.75f, screenHeight/2 }; + circleC->velocity = (Vector2){ 0, 0 }; + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); + + DrawFPS(screenWidth - 90, screenHeight - 30); + + // Draw created physics bodies + int bodiesCount = GetPhysicsBodiesCount(); + for (int i = 0; i < bodiesCount; i++) + { + PhysicsBody body = GetPhysicsBody(i); + + int vertexCount = GetPhysicsShapeVerticesCount(i); + for (int j = 0; j < vertexCount; j++) + { + // Get physics bodies shape vertices to draw lines + // Note: GetPhysicsShapeVertex() already calculates rotation transformations + Vector2 vertexA = GetPhysicsShapeVertex(body, j); + + int jj = (((j + 1) < vertexCount) ? (j + 1) : 0); // Get next vertex or first to close the shape + Vector2 vertexB = GetPhysicsShapeVertex(body, jj); + + DrawLineV(vertexA, vertexB, GREEN); // Draw a line between two vertex positions + } + } + + DrawText("Restitution amount", (screenWidth - MeasureText("Restitution amount", 30))/2, 75, 30, WHITE); + DrawText("0", circleA->position.x - MeasureText("0", 20)/2, circleA->position.y - 7, 20, WHITE); + DrawText("0.5", circleB->position.x - MeasureText("0.5", 20)/2, circleB->position.y - 7, 20, WHITE); + DrawText("1", circleC->position.x - MeasureText("1", 20)/2, circleC->position.y - 7, 20, WHITE); + + DrawText("Press 'R' to reset example", 10, 10, 10, WHITE); + + DrawText("Physac", logoX, logoY, 30, WHITE); + DrawText("Powered by", logoX + 50, logoY - 7, 10, WHITE); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + ClosePhysics(); // Unitialize physics + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/physics_restitution.png b/examples/physics_restitution.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec4b3f3270063659472bd5e21ffb39f29af6303 GIT binary patch literal 17833 zcmeHPc~lcu+n+3k0D%NW30p`+#RV}ySVYi3G;xEbYFni)5fCE^A_NyI8VCfJun9$_ zqCjwKtwlwv;t~M`if96eh)YE&f*UFbQn&9;h9Nct?P=flobNlI{*f~iZ|*$zS%1$m zZ07s%j0`LcAP6#=Hg)nW2*S}J2vbZXfN#tKe=dihDdE#5d(Tlv=$^}cd%Whkazd`o-fVOU?hbI!}cu|TA2XcS#@1u*eK&k~`rSvS3J+6eyYSk0>JyP{ z$m2e$!k$N@giOk-OmA2?;>7KwO0&`vD>oYr$l4P(MVN_c;_ul1EU9|XeMZNZ?_o}hWiz~O*oud$i_fE2&$_L>?5Do8Jh!%Z@Iskn#g>(Y zM4Ar!%Gp5VR|gYrZ+NkhHH*uEyzt7UUvN3XK3daajV+veBsLU^W(BueVrR-R6Z?>= zVM~WWENr#@C1A~rgWN%v`fy(+g~s}bm$2scW3~l}&_Rj3*8iMKmeTL-EhbB$gO!D;+hfzBvxYdlG__v3RrL0Pz*%om zoJBUqd~CRG3qI&8F*`c68)SN4(%{7(1i(*?;6bzw-Ei$br&{XR=?PU`Nl^83-qg$e=JZn zE+F))f6_%n*%ffehaWR#A>ecWa+0lPb8B>=66CvQfXTqX=k$*~KIKi+LbB)|e{DeU z*N^=g$o~TT1zX*+eo?C674Vn9<^HHELaGm*^DPnP^Z#>W0O5L8kCY)(?EClc_fKat zot%stpedcU2cMSi-9FIy;9!2?v+Kgcz*zwzP{JA3CjVyo zcc$BSp3EF(64^i=d%5(F;DB434{e{b(Wv>P?T8D;u3a_7UZwVXY@0h=XBk2vd(W!! z)~KCSlT@u}4{db1ci`4Osc3MnA<{=9gn}6Re$MF($_>G+?OX3|84{A+FnMdu9zx0H zS-3yO1yqxcS3{2TGHb)>*WPB>hvqGH=Z`sbVDNeQ#V0*(U5vOm>wf+C--=m(6^osB zqk&(@*hfRJ?NV*+%MCEIwg-M05j}XUA1m+T#gL30)2_}g z<~D3Qabfk)I(9aZ_b4xU$hwfiwTNc!MhH0iuaxprfCPXx8uFYg|NNl;{hau-PyQR= z`ZGXbd@|kn3{d_?+)%(M@-tfhc>47jt*c9wA1`Tqjtmyqc^bLy(D&;8e12cKcm zXP5+%v(I%Lb$jn~9p}>p`Oi7S=bQl~{(q;aSs;_@>S{v?4E}HfM|O(kwVWA?7;jEo zmwS7{;SMswaOrP1vRJwb3!6w7{!(16ir*9@NDWRDM*le|xBJ~1yk$P2l=HP-ddga+ zMH49h59O$F_Om`k5HX)fU*o|JaXaVTLSM2P7i>KB*CYs6l13(xTSx$i_sAJe3?;u$ypCdCQ7&;c2UV zNGq~I)92{9f($TC^T_RwaF!zqlFQjefvhE<32Njc=%A&lf?WD^`*N=EGFdpBuz4|) z^{4kS8~U%FjsgJ`#pMQq_N??vb?0Ej1GKi)Q&CK_#ug;qm8@zTa43)!?O!K!ufSdkYtKU;j!pxO#82R`Z6y?d@AC%-!W>NKX2_4Vf zE=U@2U;F81*n%bBIGIT^fA{{*hnv!BU3G-xsUDw5f2O^$A*QMFy>wffcQ#oW+^u3w zu)uQMk+p5wYYk%4vr=mgOCN67qOy(VMLwpG)oK604=TQD4vc3o=etIdU;5PwcU!8E zf-z3JxGAHEpS-3T+f9pgy&`elD@ks(_B{{8REnrqkhXgH5yekys1E0DzmmpYSQNy1 z35wXMNcSqwTC0@%jsazjf)~$EgG${IcIH3ZY0WoQ1K$4QIL~u(>~cbaI&{h z6@|3F>Osp9m(%7kEIslOWXl`cq=GGz8cMv9y*J=P3vGNW!MX7WcV}wR3S7y7M8)1; zNTi&t`nZTKE0wWYB!LDwgrm>)<6&Fi}P;nUFPF%PUD& z5|!PX{P_=X=B;KY=h_b6z-z;qS5?!S^CWlQOh43EW!wh_CRBQ$}Q|D0HwdjFmpx*N-Oa=)Rs~FxhJY z6;E;IXFXNfD`A!dny zDlTHTobI+VUFV7=k(|u9gI<8H^k!rpgRiB8KvjUTz6y0;IeAg8LJ{6nA{-5N`RcB$XgRTFjHot-OSGDBqjB9MJQB^&VpISpix=Ba-?gsfw2Y|K^E08&V z{REHKj#-h3fHcs-On<6HVmT3`3>GP67sB0544V>FXKm!;*T7?EI-1s$L`XOUFPi_P zXRUk<@7PTWfCM>3?^7<_(b^f@?x2XrmRS5~$1{rRN(nx00_J!?!+oZ9!~KQN-TVcY&UF1(InpX|r#p{XO_UlZd zdP=AQQE6DOA!j^0+o+zqVA<+To-M@L6vMjb;#O=Fm%Sn=is>T%)j4I~;R`fu&T!wH zn*4oUKA8NYr;G5!%}A)CA3Er z0JF8g&OzqBW+5$Kqu4Z4tYlfx;5rj?uUR4%**GDYSe{hqKXK}#C8_dJD}d$9L)5Yf zRh|qARc6;JOYTf=KA%g$7KjWQY^PN1oDe-uV&xkOxHuI#iYa=OVetyK!>OUB=j1$D za`T1Uf!Lh&_=qu&MQ%Hayw@~R8ipB9e?Wg(@SR8))lwo10i-bi)B$=Jgj#ip7hd|? zEW5e)!#Xk!sXWJ<`aqcry~76FPLOO^WPF)i|5Rmu_Dm!zDdm}(AHlsdDOVWGT8o#G zC)8TiE#Qa%TUYGE?OWOL>XwSdvcOUi;hUnupU;6#ge_<;R=#K)gahs)Fq+r?N6*Pg zHz@*(qz{aKu?4o}QElQDH}C8e`(*?|M_Q9A9wiy5gjs z_#S@+T%o$+{asH4lYUDiZLhi|f9=g}Z$=+niMYgG4B?&tt@ynx+IfdJvF>wdQYT}Q5sd2EbGJ#87DtR|0@w;J~qC}UdH-BqK%jMMeUOM0-GxvEURMC z{{a~TJi=4ayC?8>&FziuuQ6MH*guKa2JqHFbnQ$;wK7n8+~c8^>I;ol>GH|~Zaa!b zNIxKFYDSIX;fJ_rl@pAM_VlvguU!{X9_8fwm{&Z#@|2vUPPzWd9p04frrN zS}?*}#{j>$J6-h(wJ7k=YKFqeVk1g4fGM7Mc2b0rrf+cO`TO@9^F+>0P61!>4!%>d zJ{TvB0Eq_mj;a#_+J>6+7kD{V{+knq$~ zdO`WO*EwZ5qonTuaUSG|`5Gc2GA5uaBxl^gyh<8bnQfN?g5Uk+{VNloHivuae7VpQ zJ-AvtsUY5g;jmt>0(631R^344HyO@yOO*e#zw#}OKIymlRNr4TRBPvRq?Z&S?v89NW24sgLlCnHZGswR6QuU7x%WA#-}1U*WX%=m59*NRchTj$K!_Xlb~6T7BAB?_ zjMa{buszW3c)Bf+^#!UC1t1IXZISB(;EbRdA7Ga@IkusCfjFCHSLF($10}4ip(q~_ zlmBpfZ%4dBaC3)o1bL*8*R@_2d;}%Nv1dRGb@o#YFmpLJg}0W`$Bn_?FnUBv0%)=Q zZjUr&2M`x=7k$z&se(PW0vqKCr{wI)3*7DVlbU;nhtiOfzCusBjRT#s#Tw}OheCRw zG~)KdjAPDBPmpXw9z)lP&X)mn)KeNTF<0Y__({E41T>yu$Qpw&FboNslL&s?oV?nD zJG%iTObYd6o@ibQfT#(!Kpwr=F8D$aoZQe^ftZ`KvHk&j%RzSUanmvDD2fbO_yQ!3 z&O?PQBnrrwrtdfcYlGD3%7LSUtaFkHM_;{d15i#hms=8p3W6h;45ssdo;0RNK~;>t zhlIDN7~qo64&h$^DP`aHDvCHB`m=SOe4V;@g-)HOB6zCFDS`vlZNi4i5)!a0Du^<& zMa2|a_khh3?ndu>^v;%%Z9?M@;uyjm%r+$I3b+f2^p#r%bkhnQhYT?&l{FP7n+$06 zUTF8lPc;&5O-nCx(8u8#`5H3+HId)pTLc#h=l89qxg%vfcTgEozJ7{nnlPBV zeQ8qjVN`W`t2kh!4xUQRqk?h=jCDgRaZ!~W!f4coXnx>LQ;T(&hbIT*FQtocG0)A* zt#gbX$t#a?BQ?TA=@la+P^8}w6`Avm_Hic(UGbuAbuD9+@>6|>;;CxnCU^yuePJi- z^swbR2FoE(F9Y#VfAQLQ1%Jm9s_dAvymg~SoIO`9RtTkT9Y8?c%<~gjZ=6n#3u0YI z{SV69yD$cQ3yxR~FwIxc0>Y;CJMRj*DCIxX-Acj&Y<%NT?eA4(W*maBPLZEbs6QUl z=Y-Y;v-W6IujdN-z6V@M!-!&w=k)93Yqnv$2@LtE4=|9LTs)$JH^@~KszXwj>MdP9 zK3wZa@nB#VM8si}I(#75hpq4^Gb@HsZDg-;qhqE$0E|w?c!d$9wl-28>-gDV!H&)h zbt&(LHVdSRO-L4~AJdYc05F}f)39VrGkXIzYBJk;8>k3tj*g;iAO*M@+Lk+5T}WHf zi5uUv=baT?%-gEPFx#Ps#A1XAP|G`6ZuG;!=TZqz2BZbs_$Fzs1-Fk8oheWBBa*J) zAoEG$%!RR_WIN8zswz!uc?l@A!r*9qOT2jKgJ;QOmGZ`?w_B461d<5>+XSa?kZQE$$tNnC9%pqi71@NcL z1W?hF?_Zp>uUu<4*tpIyR78|5oa~+@21F_wUt9nMzd?hC zfcN7HjgB?0RoxR#M7?Y8{urRe4i_wZ4Yc^8x$Pa>c*B4^1{;WYgzWhSS&a&5>kz)Y zEA}Ht?<4b`O?!vaE`aKc?OI;-^FsRhxHq0zu4quvXf72|?0IN(LX8g9RL$!l&k~`Q zR|^2Pv>kq!n_@+)`d)Je^;HRC_Z4Vfr}6zgS8%-%DeqY@*k#|b!*91^itK{mR>RD` zFBUZ+nE#=9uNV9P`NH4zjwLIJv1t)*gzPp?bKiGbM9CTKHZ?xlcM8a4CWBd>Y(?{p z(OP8-irYX-H*XNeD=xW_NTSo0xWel=OPcR!t(EHAj6`)n8f=tJq3ZLVHxOlgHHtz- zQ3%Ht->{H#OTWR^+ty6t_v=tXjlJUQkxZtz^Ymm-s;@R5!6Q~6N_!Gmm^gEB_f}FH zwdj2eYYK{1G*W_b%Xi=ZfL*{<>$7XiESm*n--kJ|dzFeOH1Ne%Q#Nl)KWtF--sE$F@g7FMi>_ieovUX&3o z>I`I=X_K6Tpw+HiGi=VhRQuM(L6LG^TXJ)8ugM^V3=>nV{0Aj3{k4*WQ69pyETjO$ zIf3B)4(b+&x~~cSthDrkfpi^e6N8Bgi^m0oH~fx^!Uut@E^TM3Kqz>fYl+RdK~~xm zjmFvK&$|oYp4{>(FY>cuQAb@zd z=|+`V*1%mV)djgTsE>weY+8L456lO=ZfDOt;{(+xC%uS`U0h(lDsHR^?#VZp1^Mn- z&DsQU1b9TRyUAB6RD0g7;>-l2m#?RD+bgt)5fb^Mkm$0v(HvWFBPG^<+kmXim>Wj^ z5iSkg)T$*Kee1b`0R14VD`lSHK(2$Z9-WzWl}Fjx#vuL?l(&cF@U0g_xMUUep$W1@ zEHj`f9(w#@5}$`}S|0Mm1QX@?W(t*>f5V)*`(2QL`ed!F`Nux;P#_q$)bR!2feEJD zs?PjW$M>h{$ zF3Fq|8|P~Xa+b3ceGa5S&*HBckX8MX#niLZP*$wA zmTDZZEnYmMk(bhF?Hi)&dFtW|6>E<&m zjsF9J5SS;`_A$4f_>2({FW1;PP`5ew)TaHoJGHemOV2rR&Kmv|Y-D`}Y)jK~aBX zt23;RzfuTSEBblw5{e*F8j8Fhc@1i6_~PmAK_PpZr_f!J0Q9R0`>55zPO%@N%Ngt^ zvwxmyw7@?*F5#(hz!S!r-~ox}dEgD$WBtAX`{}>?0Eg)hD;HXU10K-zdtAb|AYhx3g2= 0; i--) + { + PhysicsBody currentBody = GetPhysicsBody(i); + if (currentBody != NULL) PhysicsShatter(currentBody, GetMousePosition(), 10/currentBody->inverseMass); + } + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); + + // Draw created physics bodies + int bodiesCount = GetPhysicsBodiesCount(); + for (int i = 0; i < bodiesCount; i++) + { + PhysicsBody currentBody = GetPhysicsBody(i); + + int vertexCount = GetPhysicsShapeVerticesCount(i); + for (int j = 0; j < vertexCount; j++) + { + // Get physics bodies shape vertices to draw lines + // Note: GetPhysicsShapeVertex() already calculates rotation transformations + Vector2 vertexA = GetPhysicsShapeVertex(currentBody, j); + + int jj = (((j + 1) < vertexCount) ? (j + 1) : 0); // Get next vertex or first to close the shape + Vector2 vertexB = GetPhysicsShapeVertex(currentBody, jj); + + DrawLineV(vertexA, vertexB, GREEN); // Draw a line between two vertex positions + } + } + + DrawText("Left mouse button in polygon area to shatter body\nPress 'R' to reset example", 10, 10, 10, WHITE); + + DrawText("Physac", logoX, logoY, 30, WHITE); + DrawText("Powered by", logoX + 50, logoY - 7, 10, WHITE); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + ClosePhysics(); // Unitialize physics + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/physics_shatter.png b/examples/physics_shatter.png new file mode 100644 index 0000000000000000000000000000000000000000..68f9a1b7d2ea6a0043828131342c6e2f641e858e GIT binary patch literal 23197 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 literal 0 HcmV?d00001