Gama C Library
Gama C API Documentation
Loading...
Searching...
No Matches
physics.h
Go to the documentation of this file.
1#pragma once
2
3#include "body.h"
4#include "body_list.h"
5#include "collision.h"
6#include "gapi.h"
7#include "position.h"
8#include "system.h"
9#include <math.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 body in the system by integrating its position and
24 * velocity over time.
25 * @param sys Pointer to the system containing the body (can be NULL).
26 * @param body Pointer to the body to update.
27 * @param dt The time step for the update.
28 */
29void gm_system_update_body_dt(gmSystem *sys, gmBody *body, double dt) {
30 if (body->is_static || body == NULL || (sys != NULL && !sys->is_active) ||
31 !body->is_active) {
32 return; // Don't update inactive or static bodies
33 }
34 // Step 1: Update velocity based on accelerations (both body and system)
35 body->velocity.x += body->acceleration.x * dt;
36 body->velocity.y += body->acceleration.y * dt;
37
38 if (sys != NULL) {
39 body->velocity.x += sys->acceleration.x * dt;
40 body->velocity.y += sys->acceleration.y * dt;
41 }
42
43 // Step 2: Apply damping to velocity (after all force updates)
44 if (sys != NULL) {
45 double damp_factor = 1.0 / (1.0 + (sys->damping * dt));
46 body->velocity.x *= damp_factor;
47 body->velocity.y *= damp_factor;
48 }
49
50 // Step 3: Update position based on the new velocity
51 body->position.x += body->velocity.x * dt;
52 body->position.y += body->velocity.y * dt;
53
54 if (sys != NULL) {
55 body->position.x +=
56 sys->velocity.x * dt; // Update position with system velocity
57 body->position.y += sys->velocity.y * dt;
58 }
59}
60
61/**
62 * @brief Updates a single body by integrating its position and
63 * velocity over time.
64 * @param body Pointer to the body to update.
65 * @param dt The time step for the update.
66 */
67void gm_body_update_dt(gmBody *body, double dt) {
68 return gm_system_update_body_dt(NULL, body, dt);
69}
70
71/**
72 * @brief Updates a single body by integrating its position and
73 * velocity over time.
74 * @param body Pointer to the body to update.
75 */
77 return gm_system_update_body_dt(NULL, body, gm_dt());
78}
79
80/**
81 * @brief Detects collision between two bodies.
82 * @param a Pointer to the first body.
83 * @param b Pointer to the second body.
84 * @return A pointer to a gmCollision structure if collision occurs, NULL
85 * otherwise.
86 */
88
89/**
90 * @brief Checks if two bodies are involved in a collision.
91 * @param c Pointer to the collision to check.
92 * @param a Pointer to the first body.
93 * @param b Pointer to the second body.
94 * @return 1 if the bodies are involved in the collision, 0 otherwise.
95 */
96static inline int gm_collision_bodies_are(gmCollision *c, gmBody *a,
97 gmBody *b) {
98 return c->bodies[0] == a && c->bodies[1] == b ||
99 c->bodies[0] == b && c->bodies[1] == a;
100}
101
102/**
103 * @brief Updates the physics system with collision detection at specified time
104 * intervals.
105 * @param sys Pointer to the system to update.
106 * @param unit The time unit for sub-step calculations.
107 * @param dt The total time step to simulate.
108 */
109void gm_system_update_dt(gmSystem *sys, double unit, double dt) {
110 if (sys == NULL || !sys->is_active)
111 return;
112
113 gmCollision **newCollisions = NULL;
114 gmCollision **prevCollisions = sys->collisions;
115
116 const unsigned int subSteps = (dt / unit) + 1;
117 const double sub_dt = gm_dt() / subSteps;
118 const unsigned count = gm_system_size(sys);
119
120 for (int i = 0; i < subSteps; i++) {
121 for (int j = 0; j < count; j++) {
122 gm_system_update_body_dt(sys, sys->bodies[j], sub_dt);
123 }
124
125 for (int j = 0; j < count; j++) {
126 for (int k = j + 1; k < count; k++) {
127 if (!sys->bodies[j]->is_active || !sys->bodies[k]->is_active) {
128 continue;
129 }
130
131 gmCollision *collision =
132 gm_collision_detect(sys->bodies[j], sys->bodies[k]);
133 if (collision != NULL) {
134 collision->sys = sys;
135 gm_collision_resolve(collision);
136 newCollisions = (gmCollision **)gm_ptr_list_push(
137 (gmPtrList)newCollisions, collision);
138 }
139 }
140 }
141 }
142
143 gmCollision *prevC, *newC;
144 gm_ptr_list_for_each(prevC, prevCollisions) {
145 int found = 0;
146 gm_ptr_list_for_each(newC, newCollisions) {
147 if (gm_collision_bodies_are(prevC, newC->bodies[0], newC->bodies[1])) {
148 newC->since = prevC->since + dt;
149 found = 1;
150 break;
151 }
152 }
153 // Only free previous collisions that are not in the new list
154 if (!found)
155 free(prevC);
156 }
157
158 // Free the previous list container, not its elements which are either freed
159 // or carried over
160 if (prevCollisions)
161 free(prevCollisions);
162
163 sys->collisions = newCollisions;
164}
165
166/**
167 * @brief Gets the collision information for two specific bodies in a system.
168 * @param collision Pointer to the collision where to copy the result, or NULL.
169 * @param sys Pointer to the system to search in.
170 * @param a Pointer to the first body.
171 * @param b Pointer to the second body.
172 * @return 1 if it found a collision else 0.
173 */
175 gmBody *b) {
176 gmCollision *coll;
177 gm_ptr_list_for_each(coll, sys->collisions) {
178 if (gm_collision_bodies_are(coll, a, b)) {
179 if (collision != NULL)
180 *collision = *coll;
181 return 1;
182 }
183 }
184 return 0;
185}
186
187/**
188 * @brief Updates the physics system with a specified time unit.
189 * @param sys Pointer to the system to update.
190 * @param unit The time unit for sub-step calculations.
191 */
192static inline void gm_system_update_unit(gmSystem *sys, double unit) {
193 return gm_system_update_dt(sys, unit, gm_dt());
194}
195
196/**
197 * @brief Default time step for physics system frame updates.
198 */
199double gm_system_frame_time = 0.001; // ~15 updates per frame.
200
201/**
202 * @brief Updates the physics system using the default frame time.
203 * @param sys Pointer to the system to update.
204 */
205static inline void gm_system_update(gmSystem *sys) {
206 return gm_system_update_unit(sys, gm_system_frame_time);
207}
208
209// ---------------------------------------------------------------------------
210// ------------------------------ Collision Response -------------------------
211// ---------------------------------------------------------------------------
212
213/**
214 * @brief Calculates the penetration depth and normal vector for a collision
215 * between two bodies.
216 * @param a Pointer to the first body.
217 * @param b Pointer to the second body.
218 * @param normal_x Pointer to store the x component of the collision normal (can
219 * be NULL).
220 * @param normal_y Pointer to store the y component of the collision normal (can
221 * be NULL).
222 * @return The penetration depth between the bodies.
223 */
224double gm_collision_penetration_normals(gmBody *a, gmBody *b, double *normal_x,
225 double *normal_y) {
226 double penetration_depth;
227 // CASE: Circle vs Circle
230 double dx = b->position.x - a->position.x;
231 double dy = b->position.y - a->position.y;
232 double distance = sqrt(dx * dx + dy * dy);
233 if (distance == 0) {
234 distance = 0.001;
235 dx = 0.001; // Avoid division by zero
236 }
237 penetration_depth = a->radius + b->radius - distance;
238 if (penetration_depth > 0 && normal_x != NULL && normal_y != NULL) {
239 *normal_x = dx / distance;
240 *normal_y = dy / distance;
241 }
242 }
243 // CASE: Rect vs Rect
244 else if (a->collider_type == GM_COLLIDER_RECT &&
246 double dx = b->position.x - a->position.x;
247 double overlap_x = (a->width / 2 + b->width / 2) - fabs(dx);
248 if (overlap_x > 0) {
249 double dy = b->position.y - a->position.y;
250 double overlap_y = (a->height / 2 + b->height / 2) - fabs(dy);
251 if (overlap_y > 0) {
252 if (normal_x != NULL && normal_y != NULL) {
253 if (overlap_x < overlap_y) {
254 penetration_depth = overlap_x;
255 *normal_x = (dx < 0) ? -1 : 1;
256 *normal_y = 0;
257 } else {
258 penetration_depth = overlap_y;
259 *normal_x = 0;
260 *normal_y = (dy < 0) ? -1 : 1;
261 }
262 }
263 }
264 }
265 }
266 // CASE: Circle vs Rectangle
267 else {
268 gmBody *circle = (a->collider_type == GM_COLLIDER_CIRCLE) ? a : b;
269 gmBody *rect = (a->collider_type == GM_COLLIDER_RECT) ? a : b;
270
271 double half_w = rect->width * 0.5;
272 double half_h = rect->height * 0.5;
273
274 // Find the point on the rectangle edge closest to the circle center
275 double closest_x =
276 fmax(rect->position.x - half_w,
277 fmin(circle->position.x, rect->position.x + half_w));
278
279 double closest_y =
280 fmax(rect->position.y - half_h,
281 fmin(circle->position.y, rect->position.y + half_h));
282
283 double dx = circle->position.x - closest_x;
284 double dy = circle->position.y - closest_y;
285 double distance_sq = dx * dx + dy * dy;
286
287 // Check if collision occurred (distance < radius)
288 if (distance_sq < circle->radius * circle->radius) {
289 double distance = sqrt(distance_sq);
290
291 // Sub-case A: Center is OUTSIDE the rectangle
292 if (distance > 0.0001 && normal_x != NULL && normal_y != NULL) {
293 // Normal points from Rect Surface -> Circle Center
294 *normal_x = dx / distance;
295 *normal_y = dy / distance;
296 penetration_depth = circle->radius - distance;
297 }
298 // Sub-case B: Center is INSIDE the rectangle
299 else {
300 // Calculate distance to all 4 edges to find the shortest path out
301 double left_pen = circle->position.x - (rect->position.x - half_w);
302 double right_pen = (rect->position.x + half_w) - circle->position.x;
303 double bottom_pen = circle->position.y - (rect->position.y - half_h);
304 double top_pen = (rect->position.y + half_h) - circle->position.y;
305
306 double min_x = fmin(left_pen, right_pen);
307 double min_y = fmin(bottom_pen, top_pen);
308
309 // We add radius because we need to push the circle entirely out
310 // so its edge touches the rect edge, not just its center.
311 if (normal_x != NULL && normal_y != NULL) {
312 if (min_x < min_y) {
313 penetration_depth = min_x + circle->radius;
314 *normal_x = (left_pen < right_pen) ? -1 : 1;
315 *normal_y = 0;
316 } else {
317 penetration_depth = min_y + circle->radius;
318 *normal_x = 0;
319 *normal_y = (bottom_pen < top_pen) ? -1 : 1;
320 }
321 }
322 }
323
324 // --- CRITICAL FIX ---
325 // The logic above calculates a normal pointing Rect -> Circle.
326 // We must ensure the final 'normal' used for resolution points from A ->
327 // B.
328 if (a == circle) {
329 // Current normal is B(Rect) -> A(Circle). We want A -> B. Invert.
330 *normal_x = -*normal_x;
331 *normal_y = -*normal_y;
332 }
333 // If a is Rect, current normal is A(Rect) -> B(Circle). Keep as is.
334 }
335 }
336 return penetration_depth;
337}
338
339/**
340 * @brief Calculates the penetration depth for a collision between two bodies.
341 * @param a Pointer to the first body.
342 * @param b Pointer to the second body.
343 * @return The penetration depth between the bodies.
344 */
346 return gm_collision_penetration_normals(a, a, NULL, NULL);
347}
348
349/**
350 * @brief Resolves a collision by adjusting positions and velocities of
351 * colliding bodies.
352 * @param coll Pointer to the collision to resolve.
353 */
355 if (coll == NULL)
356 return;
357 gmBody *a = coll->bodies[0];
358 gmBody *b = coll->bodies[1];
360 &coll->normals.y);
361
362 if (coll->penetration <= 0) {
363 return; // No collision to resolve
364 }
365
366 // --- 2. Resolve Velocity ---
367 double rel_vx = b->velocity.x - a->velocity.x;
368 double rel_vy = b->velocity.y - a->velocity.y;
369 double vel_along_normal = rel_vx * coll->normals.x + rel_vy * coll->normals.y;
370
371 // Do not resolve if velocities are separating
372 if (vel_along_normal > 0) {
373 return;
374 }
375
376 double e = fmin(a->restitution, b->restitution);
377 double j = -(1 + e) * vel_along_normal;
378
379 double inv_mass_a = (a->mass > 0) ? 1.0 / a->mass : 0;
380 double inv_mass_b = (b->mass > 0) ? 1.0 / b->mass : 0;
381
382 if (inv_mass_a + inv_mass_b == 0)
383 return;
384
385 j /= (inv_mass_a + inv_mass_b);
386
387 double impulse_x = j * coll->normals.x;
388 double impulse_y = j * coll->normals.y;
389
390 if (a->mass > 0) {
391 a->velocity.x -= inv_mass_a * impulse_x;
392 a->velocity.y -= inv_mass_a * impulse_y;
393 }
394 if (b->mass > 0) {
395 b->velocity.x += inv_mass_b * impulse_x;
396 b->velocity.y += inv_mass_b * impulse_y;
397 }
398
399 // --- 3. Positional Correction ---
400 const double percent = 0.2; // Percentage of penetration to correct
401 const double slop = 0.01; // Penetration allowance to prevent jitter
402 double correction_amount =
403 fmax(coll->penetration - slop, 0.0) / (inv_mass_a + inv_mass_b) * percent;
404
405 double correction_x = correction_amount * coll->normals.x;
406 double correction_y = correction_amount * coll->normals.y;
407
408 if (a->mass > 0) {
409 a->position.x -= inv_mass_a * correction_x;
410 a->position.y -= inv_mass_a * correction_y;
411 }
412 if (b->mass > 0) {
413 b->position.x += inv_mass_b * correction_x;
414 b->position.y += inv_mass_b * correction_y;
415 }
416}
@ GM_COLLIDER_RECT
Definition body.h:13
@ GM_COLLIDER_CIRCLE
Definition body.h:12
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:223
gmPtrList gm_ptr_list_push(gmPtrList list, void *obj)
Adds a pointer to the end of the list.
Definition body_list.h:73
void ** gmPtrList
A dynamic, NULL-terminated array of generic pointers.
Definition body_list.h:25
gmCollision * gm_collision_detect(gmBody *, gmBody *)
Detects collision between two bodies.
Definition collision.h:59
void gm_system_update_dt(gmSystem *sys, double unit, double dt)
Updates the physics system with collision detection at specified time intervals.
Definition physics.h:109
double gm_collision_penetration_normals(gmBody *a, gmBody *b, double *normal_x, double *normal_y)
Calculates the penetration depth and normal vector for a collision between two bodies.
Definition physics.h:224
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:174
double gm_system_frame_time
Default time step for physics system frame updates.
Definition physics.h:199
void gm_system_update_body_dt(gmSystem *sys, gmBody *body, double dt)
Updates a single body in the system by integrating its position and velocity over time.
Definition physics.h:29
void gm_body_update_dt(gmBody *body, double dt)
Updates a single body by integrating its position and velocity over time.
Definition physics.h:67
void gm_collision_resolve(gmCollision *collision)
Resolves a collision between two bodies by applying appropriate forces and corrections.
Definition physics.h:354
void gm_body_update(gmBody *body)
Updates a single body by integrating its position and velocity over time.
Definition physics.h:76
double gm_collision_penetration(gmBody *a, gmBody *b)
Calculates the penetration depth for a collision between two bodies.
Definition physics.h:345
gmPos acceleration
Definition system.h:30
double damping
Definition system.h:32
gmCollision ** collisions
Definition system.h:27
gmPos velocity
Definition system.h:29
int is_active
Definition system.h:24
gmBodies bodies
Definition system.h:25
Structure representing a physics body with properties for collision and movement.
Definition body.h:19
double width
Definition body.h:28
gmPos acceleration
Definition body.h:26
gmColliderType collider_type
Definition body.h:23
double height
Definition body.h:28
double restitution
Definition body.h:30
uint8_t is_active
Definition body.h:20
double radius
Definition body.h:28
double mass
Definition body.h:29
gmPos velocity
Definition body.h:25
gmPos position
Definition body.h:24
uint8_t is_static
Definition body.h:21
Structure representing a collision between two bodies.
Definition system.h:10
gmPos normals
Definition system.h:16
double since
Definition system.h:15
gmBody * bodies[2]
Definition system.h:13
double penetration
Definition system.h:14
struct gm_system * sys
Definition system.h:11
double x
Definition position.h:5
double y
Definition position.h:5
struct gm_system gmSystem
Structure representing a physics system containing bodies and collision information.