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 dd4c41269..e807ffa6a 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 87316a981..000000000 --- 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_8XZ#=yWJp1k%11A}#|r;B4qMO-ou2gi@| z23&5=2_`Jqi3UE!113_Z7AqP^F<~bh)(c3yZRlCH^`d|TZzC3hvF#$?i3}y|k{}Ib ziUwB9MzdD9<8sCeF{Z_`6D}Sx!|8_YU^l33!(u(i2Hr-H6QpoEp$_7NRijQ|HkvgW z3ZUdT8Va-y1;$;i45u?9_}2-nSh0e^dd1?!%KgZu`XkOfG9``H^YqSG4AHwm#3Ow2X4GFZh*yv+51lR@@F0Y?C|vQ!dY_ex48~0v$&l; z&9z9WXv5U$JMK7V)*1vneUMSkD^G3Kp@mA>$ zx!lz->bYY8itWrUvVn>Kxdgk08B)um4j7S_10{WRlFjqNS*g}EF( z=NfvJZNOFjGVW?U@Pg-rfp<f0v$r&> z^?JbB{429|u2lGBryldvaHaD8E$8koeyUS@%0KJ5bkJ?)EuU2Po@%~WRhMC!;hyf` zR=t~0NKCq6wPMe;8WaA$HijaO1DC{qozPo2@m{0cNm(7`lUL$y`>wv?`Syd9*1u(C z6^~Box4cmdzn_t5y2B}9!D;WJ+ss?|T%)G5H!s~If42V!*e4lYl2|Ld#)|?AqCtVd zbLoiNt0=dk>vloay&8I-Gq##d4l2&v>LxaQ=L=;M=DlfCLw%fytu`& z8aw_5BPA$g z+aX~OiX=8nVW^Ya;z7BtXW1)6@sYyB_@_QV?#ia>EgwU z%~oqb$!hW^XdGcEC!DN6RRrEvEjYw!nzBco0P1p#hQeqlK#CYhyKOX6j^>Eb95I?B zpe+k%P)2)7_s~~|3;d3HR`}LXjpy|WInNi{PhR4A1nq9&yP-nfWvuM zPlNQ7Ik;Vh%{^hvYaI9ho?X5eVvBEb{cS_fKrY4sM-okEPDV3>^N5%e91@v9XK|vf z6jQSzxO(aBz^q<)8wCZxGrT8jkQ*b&t@S|=o78Z^7r6z|l|`4F%Y4y3w>SnifXW0-|F&a_R^M``*Vh`s+TFGB7YO Nc)I$ztaD0e0szh+pP&E$ diff --git a/examples/physics_forces.c b/examples/physics_forces.c deleted file mode 100644 index e45cb44cc..000000000 --- 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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 From e8630c78d069a1cba50b1a78108663ebc19e5b9b Mon Sep 17 00:00:00 2001 From: victorfisac 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 000000000..bed7c94d3 --- /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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 literal 0 HcmV?d00001 diff --git a/examples/physics_friction.c b/examples/physics_friction.c new file mode 100644 index 000000000..28d3c4b8c --- /dev/null +++ b/examples/physics_friction.c @@ -0,0 +1,136 @@ +/******************************************************************************************* +* +* Physac - Physics friction +* +* 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 friction"); + 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) + 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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 = 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 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU_8XZ#=yWJp1k%11B0EAr;B4qMO<=9#KI5r z6(2Dk){x+ByeNR3V4T%JCMQAgM~2YDRXcWK8Rhi zr|rN7?#7Ff6E3>gN--@~G{8bQY!{Gt+t8!74W~nQLENwrryD>{xQxSnJ#7a*K%5YQ z&j|^VysvRO;8yE_|NsAQlsb3fghfV^>=U<~cL!DdIuE;BD=glbYr^8@oPa@O$S}R= zt&W*HX`+CjOS!{_uXQVKDmTq+UEl7ZZCSI|*2y-k>YLchxeA#VAIs|A#b$(nV#C_C zoLRFMZ9n{B>(s2d+HiQZKRgw@LCcsN$t>ea{{7Wa5^Do|*`5wj;3-zr?WdFZ)#gtFXT!~T0W)Su`l zT@?;$e))$(8f5p?VqBD|S4^5)x^kF1ehgODlX2L!)v2x+NrKkTcT&ccot=oy( zC2i87RtiT-Bv!~SRIqkzY+T#i;GbuarknDAQ9Gw;^_~-Hf|C>2qw}k`M95-fmqrf8 znp~xrB4*V-IlCjfnx{0TPqI>Y@}?ZSzZG(29mJm3~du`DTF!lJh8_QbD;l=_7} zJ3RY;ytvQAgD*Z^V2OOV(joEfEqDBKb|uKTq{sG)?Z=i|p0qQl=0+{DR=DzJ=flrh zX4MHvSEe-YYz$aDnL5`_Mgh!`8&c$NZBMmObEK#1t7qR7710$z|EzCxn&o2Rm(hJ2UQ;$994O({bj~SMd0cQ6XuEvXeCo-a> zF#;Z%V;4ko^ynRw^3B2K3S}b}kJx3cdw6=TT;5W!a(T*13BB(Vq-Jh0V4pATmhRxj zJqKzGMxtXRSn^!ZWpSIH(2KqBa+GJf$jFoxEFTnE18?lAKqcpbx)$rvO(hO5w(z_^rQ_t8K+lY zyz9jMU+H)AjF#rJYZVNi__6!&?y7uImiT&wfUww+w(A)Sjj<=F1p*5)IK>PdJ60Dr z=<#KrcqDW@U3^nqlGM8`Jyl+Ry}ND~+-$wW**McZPrY|Sbw=vDjBhg7qH3O^!{^`) z8%j9}dXGJpUaGG4W25Pwm$JD_euzIY^Rn{U@g-?CqvQ7ODB1SLcOQDid@$4I1w{wX z-CbhMJFgf`4k_-)6~DBHxhdDJ`9>sDk&v+FT!EDG157=aIFyg>SNNY8+^^TSesQCW z+lxB)=aXNV*L-Zz-s5JivUn;uRiF_J`veqrF*mJG?DX!7di2Al;VfU0)WIjOn0K~b z`=@&S?~OZb#@!QUZ(OM?;5$OriIb8fZUBA%@LzHVl+pL<_K8xd^9bLriIb80Pa4a5u-KbXiYgRrshdhlh*-AQ z$u#^4P_>#h4LylEHzaWMoNMS&n{-h?;)TGCA8m~b-+sN&#~$FGWul5P+sq)SXkf)` zG|LCW9FQMD(G40)mYs0%2=2+OTa1#v%$PF)&{6Qi`YfPf=3t0dkv#?-fCo+e;2+~1 zb;4*wj3!4|gJd)H=;Kcj<`5uFoL>=6%gA_*e$V13r)p4(Q*OYOhO|@OGjc$$A*aymprR)`+eWN zxdz-{$4+EOGj+}3S!}GZxD#_r6Lf5&Ay4rD)4abO6$VmIFoslM`&1SyI@}hBSO}gZ zI0n@S8-GJ^Uxk@0cbJ>>y{sgvt%b+G(kiSI_r;9 zLJ3=nNhrpyDA+~{(AFMng~gpX_V>9tgZDtOAtDxy)fNw)Tw3-DxgbaODdF8kwBFa4 zz;WiXx}L-&J*O)ZSIwL7^2jW9=hs5ymG13Z>_nr+)Xiplif-;x1^D3*LP)4HFYv57oDIurGGB z!!>wpAjPzWw^5XfaA`T3If>YG3@QY%ZykiBM7|Rl6b{5QnCgG*dF0$6!N9=4;OXk; Jvd$@?2>_CiT5$jX literal 0 HcmV?d00001 diff --git a/examples/physics_restitution.c b/examples/physics_restitution.c new file mode 100644 index 000000000..3543db69c --- /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 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 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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 NGwCotN2yYR@_jeQxM81{xm^G}J-8SL|x_+@{ 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