Gama C Library
Gama C API Documentation
physics.h
Go to the documentation of this file.
1#pragma once
2
3#include "_math.h"
4#include "body.h"
5#include "body_list.h"
6#include "collision.h"
7#include "gapi.h"
8#include "position.h"
9#include "system.h"
10
11/**
12 * @brief Resolves a collision between two bodies by applying appropriate forces
13 * and corrections.
14 * @param collision Pointer to the collision to resolve.
15 */
16void gm_collision_resolve(gmCollision *collision);
17
18// ---------------------------------------------------------------------------
19// ------------------------------ Core Physics Update ------------------------
20// ---------------------------------------------------------------------------
21
22/**
23 * @brief Updates a single physics body's position and velocity based on applied
24 * accelerations and damping.
25 *
26 * This function integrates the body's motion over a given time step. It accounts
27 * for the body's own acceleration and any system-wide acceleration and damping.
28 *
29 * @param sys Pointer to the physics system the body belongs to (can be NULL if
30 * no system-wide effects are desired).
31 * @param body Pointer to the body to update.
32 * @param dt The time step (delta time) for the update.
33 */
34void gm_system_update_body_dt(gmSystem *sys, gmBody *body, double dt) {
35 if (body->is_static || body == NULL || (sys != NULL && !sys->is_active) ||
36 !body->is_active) {
37 return; // Don't update inactive or static bodies
38 }
39 // Step 1: Update velocity based on accelerations (both body and system)
40 body->velocity.x += body->acceleration.x * dt;
41 body->velocity.y += body->acceleration.y * dt;
42
43 if (sys != NULL) {
44 body->velocity.x += sys->acceleration.x * dt;
45 body->velocity.y += sys->acceleration.y * dt;
46 }
47
48 // Step 2: Apply damping to velocity (after all force updates)
49 if (sys != NULL) {
50 double damp_factor = 1.0 / (1.0 + (sys->damping * dt));
51 body->velocity.x *= damp_factor;
52 body->velocity.y *= damp_factor;
53 }
54
55 // Step 3: Update position based on the new velocity
56 body->position.x += body->velocity.x * dt;
57 body->position.y += body->velocity.y * dt;
58
59 if (sys != NULL) {
60 body->position.x +=
61 sys->velocity.x * dt; // Update position with system velocity
62 body->position.y += sys->velocity.y * dt;
63 }
64}
65
66/**
67 * @brief Updates a single physics body's position and velocity using a
68 * specified time step, without considering a global physics system.
69 * @param body Pointer to the body to update.
70 * @param dt The time step for the update.
71 */
72void gm_body_update_dt(gmBody *body, double dt) {
73 return gm_system_update_body_dt(NULL, body, dt);
74}
75
76/**
77 * @brief Updates a single physics body's position and velocity using the
78 * engine's global delta time (`gm_dt()`), without considering a global physics system.
79 * @param body Pointer to the body to update.
80 */
82 return gm_system_update_body_dt(NULL, body, gm_dt());
83}
84
85/**
86 * @brief Detects a collision between two physics bodies.
87 * @param a Pointer to the first body.
88 * @param b Pointer to the second body.
89 * @return A pointer to a `gmCollision` structure if a collision is detected,
90 * otherwise NULL. The returned `gmCollision` must be freed by the caller
91 * if it's not managed by a `gmSystem`.
92 */
94
95/**
96 * @brief Checks if two given bodies are involved in the specified collision.
97 *
98 * This function determines if the provided collision object (`c`) involves
99 * the two specified bodies (`a` and `b`), regardless of their order within the
100 * collision object.
101 *
102 * @param c Pointer to the collision object to check.
103 * @param a Pointer to the first body.
104 * @param b Pointer to the second body.
105 * @return 1 if the bodies are involved in the collision, 0 otherwise.
106 */
107static inline int gm_collision_bodies_are(gmCollision *c, gmBody *a,
108 gmBody *b) {
109 return c->bodies[0] == a && c->bodies[1] == b ||
110 c->bodies[0] == b && c->bodies[1] == a;
111}
112
113/**
114 * @brief Updates a physics system over a given total time step,
115 * performing sub-steps for stable collision detection and resolution.
116 *
117 * This is the main update function for a physics system. It integrates
118 * the motion of all bodies, detects new collisions, resolves them, and
119 * manages the lifecycle of collision objects.
120 *
121 * @param sys Pointer to the system to update.
122 * @param unit The duration of each sub-step for physics integration.
123 * @param dt The total time duration to simulate in this update.
124 */
125void gm_system_update_dt(gmSystem *sys, double unit, double dt) {
126 if (sys == NULL || !sys->is_active)
127 return;
128
129 gmCollision **newCollisions = NULL;
130 gmCollision **prevCollisions = sys->collisions;
131
132 const unsigned int subSteps = (dt / unit) + 1;
133 const double sub_dt = gm_dt() / subSteps;
134 const unsigned count = gm_system_size(sys);
135
136 for (int i = 0; i < subSteps; i++) {
137 for (int j = 0; j < count; j++) {
138 gm_system_update_body_dt(sys, sys->bodies[j], sub_dt);
139 }
140
141 for (int j = 0; j < count; j++) {
142 for (int k = j + 1; k < count; k++) {
143 if (!sys->bodies[j]->is_active || !sys->bodies[k]->is_active) {
144 continue;
145 }
146
147 gmCollision *collision =
148 gm_collision_detect(sys->bodies[j], sys->bodies[k]);
149 if (collision != NULL) {
150 collision->sys = sys;
151 gm_collision_resolve(collision);
152 newCollisions = (gmCollision **)gm_ptr_list_push(
153 (gmPtrList)newCollisions, collision);
154 }
155 }
156 }
157 }
158
159 gmCollision *prevC, *newC;
160 gm_ptr_list_for_each(prevC, prevCollisions) {
161 int found = 0;
162 gm_ptr_list_for_each(newC, newCollisions) {
163 if (gm_collision_bodies_are(prevC, newC->bodies[0], newC->bodies[1])) {
164 newC->since = prevC->since + dt;
165 found = 1;
166 break;
167 }
168 }
169 // Only free previous collisions that are not in the new list
170 if (!found)
171 free(prevC);
172 }
173
174 // Free the previous list container, not its elements which are either freed
175 // or carried over
176 if (prevCollisions)
177 free(prevCollisions);
178
179 sys->collisions = newCollisions;
180}
181
182/**
183 * @brief Gets the collision information for two specific bodies in a system.
184 *
185 * This function searches the system's active collisions to find one involving
186 * the two specified bodies.
187 *
188 * @param collision Pointer to a `gmCollision` struct where the found collision
189 * data will be copied. Can be NULL if only checking for existence.
190 * @param sys Pointer to the system to search in.
191 * @param a Pointer to the first body.
192 * @param b Pointer to the second body.
193 * @return 1 if a collision involving `a` and `b` is found, 0 otherwise.
194 */
196 gmBody *b) {
197 gmCollision *coll;
198 gm_ptr_list_for_each(coll, sys->collisions) {
199 if (gm_collision_bodies_are(coll, a, b)) {
200 if (collision != NULL)
201 *collision = *coll;
202 return 1;
203 }
204 }
205 return 0;
206}
207
208/**
209 * @brief Updates the physics system using a specified time unit and the
210 * engine's global delta time (`gm_dt()`).
211 * @param sys Pointer to the system to update.
212 * @param unit The time unit for sub-step calculations.
213 */
214static inline void gm_system_update_unit(gmSystem *sys, double unit) {
215 return gm_system_update_dt(sys, unit, gm_dt());
216}
217
218/**
219 * @brief Default time step for physics system frame updates.
220 *
221 * This value determines the granularity of physics calculations per frame.
222 * A smaller value leads to more accurate (and potentially slower) simulations.
223 */
224double gm_system_frame_time = 0.001; // ~15 updates per frame.
225
226/**
227 * @brief Updates the physics system using the default `gm_system_frame_time`.
228 * @param sys Pointer to the system to update.
229 */
230static inline void gm_system_update(gmSystem *sys) {
231 return gm_system_update_unit(sys, gm_system_frame_time);
232}
233
234// ---------------------------------------------------------------------------
235// ------------------------------ Collision Response -------------------------
236// ---------------------------------------------------------------------------
237
238/**
239 * @brief Calculates the penetration depth and optionally the normal vector
240 * for a collision between two bodies.
241 *
242 * This function handles collision between various collider types (Circle-Circle,
243 * Rect-Rect, Circle-Rect). It determines how much the bodies overlap
244 * and the direction of the separation.
245 *
246 * @param a Pointer to the first body.
247 * @param b Pointer to the second body.
248 * @param normal_x Pointer to store the x component of the collision normal (can
249 * be NULL if not needed). The normal points from body `a` to body `b`.
250 * @param normal_y Pointer to store the y component of the collision normal (can
251 * be NULL if not needed). The normal points from body `a` to body `b`.
252 * @return The penetration depth between the bodies. A positive value indicates
253 * overlap.
254 */
255double gm_collision_penetration_normals(gmBody *a, gmBody *b, double *normal_x,
256 double *normal_y) {
257 double penetration_depth = 0; // Initialize to 0
258
259 // CASE: Circle vs Circle
262 double dx = b->position.x - a->position.x;
263 double dy = b->position.y - a->position.y;
264 double distance = sqrt(dx * dx + dy * dy);
265 if (distance == 0) {
266 distance = 0.001;
267 dx = 0.001; // Avoid division by zero
268 }
269 penetration_depth = a->radius + b->radius - distance;
270 if (penetration_depth > 0 && normal_x != NULL && normal_y != NULL) {
271 *normal_x = dx / distance;
272 *normal_y = dy / distance;
273 }
274 }
275 // CASE: Rect vs Rect
276 else if (a->collider_type == GM_COLLIDER_RECT &&
278 double dx = b->position.x - a->position.x;
279 double dy = b->position.y - a->position.y;
280
281 double overlap_x = (a->width / 2 + b->width / 2) - fabs(dx);
282 if (overlap_x <= 0) return 0; // No overlap in X
283
284 double overlap_y = (a->height / 2 + b->height / 2) - fabs(dy);
285 if (overlap_y <= 0) return 0; // No overlap in Y
286
287 if (normal_x != NULL && normal_y != NULL) {
288 if (overlap_x < overlap_y) {
289 penetration_depth = overlap_x;
290 *normal_x = (dx < 0) ? -1 : 1;
291 *normal_y = 0;
292 } else {
293 penetration_depth = overlap_y;
294 *normal_x = 0;
295 *normal_y = (dy < 0) ? -1 : 1;
296 }
297 }
298 }
299 // CASE: Circle vs Rectangle
300 else {
301 // Determine which is circle and which is rect
302 gmBody *circle = (a->collider_type == GM_COLLIDER_CIRCLE) ? a : b;
303 gmBody *rect = (a->collider_type == GM_COLLIDER_RECT) ? a : b;
304
305 double half_w = rect->width * 0.5;
306 double half_h = rect->height * 0.5;
307
308 // Find the point on the rectangle edge closest to the circle center
309 double closest_x =
310 fmax(rect->position.x - half_w,
311 fmin(circle->position.x, rect->position.x + half_w));
312
313 double closest_y =
314 fmax(rect->position.y - half_h,
315 fmin(circle->position.y, rect->position.y + half_h));
316
317 double dx = circle->position.x - closest_x;
318 double dy = circle->position.y - closest_y;
319 double distance_sq = dx * dx + dy * dy;
320
321 // Check if collision occurred (distance < radius)
322 if (distance_sq < circle->radius * circle->radius) {
323 double distance = sqrt(distance_sq);
324
325 // Sub-case A: Center is OUTSIDE the rectangle
326 if (distance > 0.0001 && normal_x != NULL && normal_y != NULL) {
327 // Normal points from Rect Surface -> Circle Center
328 *normal_x = dx / distance;
329 *normal_y = dy / distance;
330 penetration_depth = circle->radius - distance;
331 }
332 // Sub-case B: Center is INSIDE the rectangle (special handling)
333 else {
334 // Calculate distance to all 4 edges to find the shortest path out
335 double left_pen = circle->position.x - (rect->position.x - half_w);
336 double right_pen = (rect->position.x + half_w) - circle->position.x;
337 double bottom_pen = circle->position.y - (rect->position.y - half_h);
338 double top_pen = (rect->position.y + half_h) - circle->position.y;
339
340 double min_x = fmin(left_pen, right_pen);
341 double min_y = fmin(bottom_pen, top_pen);
342
343 // We add radius because we need to push the circle entirely out
344 // so its edge touches the rect edge, not just its center.
345 if (normal_x != NULL && normal_y != NULL) {
346 if (min_x < min_y) {
347 penetration_depth = min_x + circle->radius;
348 *normal_x = (left_pen < right_pen) ? -1 : 1;
349 *normal_y = 0;
350 } else {
351 penetration_depth = min_y + circle->radius;
352 *normal_x = 0;
353 *normal_y = (bottom_pen < top_pen) ? -1 : 1;
354 }
355 }
356 }
357
358 // --- CRITICAL FIX ---
359 // The logic above calculates a normal pointing from the rectangle surface
360 // towards the circle center. We must ensure the final 'normal' used for
361 // resolution points from body A to body B.
362 if (a == circle) {
363 // Current normal is B(Rect) -> A(Circle). We want A -> B. Invert.
364 *normal_x = -*normal_x;
365 *normal_y = -*normal_y;
366 }
367 // If a is Rect, current normal is A(Rect) -> B(Circle). Keep as is.
368 }
369 }
370 return penetration_depth;
371}
372
373/**
374 * @brief Calculates the penetration depth for a collision between two bodies.
375 *
376 * This function is a simplified version of `gm_collision_penetration_normals`
377 * that only returns the depth and does not calculate the normal vector.
378 *
379 * @param a Pointer to the first body.
380 * @param b Pointer to the second body.
381 * @return The penetration depth between the bodies. A positive value indicates
382 * overlap.
383 */
385 return gm_collision_penetration_normals(a, b, NULL, NULL);
386}
387
388/**
389 * @brief Resolves a collision by adjusting positions and velocities of
390 * colliding bodies.
391 * @param coll Pointer to the collision to resolve.
392 */
394 if (coll == NULL)
395 return;
396 gmBody *a = coll->bodies[0];
397 gmBody *b = coll->bodies[1];
399 &coll->normals.y);
400
401 if (coll->penetration <= 0) {
402 return; // No collision to resolve
403 }
404
405 // --- 2. Resolve Velocity ---
406 double rel_vx = b->velocity.x - a->velocity.x;
407 double rel_vy = b->velocity.y - a->velocity.y;
408 double vel_along_normal = rel_vx * coll->normals.x + rel_vy * coll->normals.y;
409
410 // Do not resolve if velocities are separating
411 if (vel_along_normal > 0) {
412 return;
413 }
414
415 double e = fmin(a->restitution, b->restitution);
416 double j = -(1 + e) * vel_along_normal;
417
418 double inv_mass_a = (a->mass > 0) ? 1.0 / a->mass : 0;
419 double inv_mass_b = (b->mass > 0) ? 1.0 / b->mass : 0;
420
421 if (inv_mass_a + inv_mass_b == 0)
422 return;
423
424 j /= (inv_mass_a + inv_mass_b);
425
426 double impulse_x = j * coll->normals.x;
427 double impulse_y = j * coll->normals.y;
428
429 if (a->mass > 0) {
430 a->velocity.x -= inv_mass_a * impulse_x;
431 a->velocity.y -= inv_mass_a * impulse_y;
432 }
433 if (b->mass > 0) {
434 b->velocity.x += inv_mass_b * impulse_x;
435 b->velocity.y += inv_mass_b * impulse_y;
436 }
437
438 // --- 3. Positional Correction ---
439 const double percent = 0.2; // Percentage of penetration to correct
440 const double slop = 0.01; // Penetration allowance to prevent jitter
441 double correction_amount =
442 fmax(coll->penetration - slop, 0.0) / (inv_mass_a + inv_mass_b) * percent;
443
444 double correction_x = correction_amount * coll->normals.x;
445 double correction_y = correction_amount * coll->normals.y;
446
447 if (a->mass > 0) {
448 a->position.x -= inv_mass_a * correction_x;
449 a->position.y -= inv_mass_a * correction_y;
450 }
451 if (b->mass > 0) {
452 b->position.x += inv_mass_b * correction_x;
453 b->position.y += inv_mass_b * correction_y;
454 }
455}
@ GM_COLLIDER_RECT
Definition body.h:18
@ GM_COLLIDER_CIRCLE
Definition body.h:17
Provides a dynamic, NULL-terminated pointer list implementation.
#define gm_ptr_list_for_each(item, list)
A macro for iterating over a gmPtrList.
Definition body_list.h:232
gmPtrList gm_ptr_list_push(gmPtrList list, void *obj)
Adds a pointer to the end of the list.
Definition body_list.h:78
void ** gmPtrList
A dynamic, NULL-terminated array of generic void* pointers.
Definition body_list.h:29
Defines collision structures and provides functions for 2D collision detection.
struct gm_collision gmCollision
Structure to store detailed information about a collision between two bodies.
Graphics API (GAPI) abstraction layer for Gama.
void free(void *ptr)
Custom implementation of free for memory allocated by malloc (this custom version).
Definition malloc.h:189
double fmin(double a, double b)
Returns the smaller of two double values.
Definition math.h:413
double fmax(double a, double b)
Returns the larger of two double values.
Definition math.h:428
double fabs(double x)
Calculates the absolute value of a double.
Definition math.h:369
double sqrt(double x)
Calculates the square root of x.
Definition math.h:339
gmCollision * gm_collision_detect(gmBody *, gmBody *)
Detects a collision between two physics bodies.
Definition collision.h:110
void gm_system_update_dt(gmSystem *sys, double unit, double dt)
Updates a physics system over a given total time step, performing sub-steps for stable collision dete...
Definition physics.h:125
double gm_collision_penetration_normals(gmBody *a, gmBody *b, double *normal_x, double *normal_y)
Calculates the penetration depth and optionally the normal vector for a collision between two bodies.
Definition physics.h:255
int gm_system_get_collision(gmCollision *collision, gmSystem *sys, gmBody *a, gmBody *b)
Gets the collision information for two specific bodies in a system.
Definition physics.h:195
double gm_system_frame_time
Default time step for physics system frame updates.
Definition physics.h:224
void gm_system_update_body_dt(gmSystem *sys, gmBody *body, double dt)
Updates a single physics body's position and velocity based on applied accelerations and damping.
Definition physics.h:34
void gm_body_update_dt(gmBody *body, double dt)
Updates a single physics body's position and velocity using a specified time step,...
Definition physics.h:72
void gm_collision_resolve(gmCollision *collision)
Resolves a collision between two bodies by applying appropriate forces and corrections.
Definition physics.h:393
void gm_body_update(gmBody *body)
Updates a single physics body's position and velocity using the engine's global delta time (gm_dt()),...
Definition physics.h:81
double gm_collision_penetration(gmBody *a, gmBody *b)
Calculates the penetration depth for a collision between two bodies.
Definition physics.h:384
gmPos normals
Definition collision.h:20
double since
Definition collision.h:22
double penetration
Definition collision.h:21
gmBody * bodies[2]
Definition collision.h:19
gmSystem * sys
Definition collision.h:23
gmPos acceleration
Definition system.h:27
double damping
Definition system.h:29
gmPos velocity
Definition system.h:26
struct gm_collision ** collisions
Definition system.h:24
int is_active
Definition system.h:21
gmBodies bodies
Definition system.h:22
Structure representing a physics body with properties for collision and movement.
Definition body.h:25
double width
Definition body.h:35
gmPos acceleration
Definition body.h:33
gmColliderType collider_type
Definition body.h:30
double height
Definition body.h:35
double restitution
Definition body.h:39
uint8_t is_active
Definition body.h:27
double radius
Definition body.h:35
double mass
Definition body.h:38
gmPos velocity
Definition body.h:32
gmPos position
Definition body.h:31
uint8_t is_static
Definition body.h:28
double x
Definition position.h:9
double y
Definition position.h:9
Manages physics bodies, their interactions, and collision detection within a simulation.
struct gm_system gmSystem
Structure representing a physics system containing bodies and collision information.