Gama C Library
Gama C API Documentation
body.h
Go to the documentation of this file.
1#pragma once
2
3/**
4 * @def gnothing
5 * @brief Macro representing a NULL pointer, for use where a generic null is needed.
6 */
7#define gnothing NULL
8
9#include "mouse.h"
10#include "position.h"
11#include <stdint.h>
12
13/**
14 * @brief Enum to define the type of collider for a physics body.
15 */
16typedef enum {
17 GM_COLLIDER_CIRCLE, /**< Circular collider */
18 GM_COLLIDER_RECT /**< Rectangular collider */
20
21/**
22 * @brief Structure representing a physics body with properties for collision
23 * and movement.
24 */
25typedef struct {
26 uint8_t
27 is_active; /**< Whether the body is active in the physics simulation. Set to 0 to disable. */
28 uint8_t is_static; /**< Whether the body is static (immovable). Static bodies do not move or respond to forces. */
29
30 gmColliderType collider_type; /**< Type of collider (rectangle or circle). */
31 gmPos position; /**< Current position of the body. */
32 gmPos velocity; /**< Current velocity of the body. */
33 gmPos acceleration; /**< Current acceleration of the body. */
34
35 double width, height, radius; /**< Dimensions of the body (width/height for
36 rectangles, radius for circles). For circles,
37 width and height are typically set to 2*radius. */
38 double mass; /**< Mass of the body. Set to 0 for infinite mass (similar to static but can move). */
39 double restitution; /**< Bounciness of the body (0.0 = no bounce, 1.0+ =
40 more bounce). */
41 double friction; /**< Friction coefficient (not currently used in the code). */
42} gmBody;
43
44/**
45 * @brief Creates a new physics body with specified properties.
46 *
47 * The `radius` for circular colliders is automatically set based on `w` (or `h` if smaller).
48 * For rectangular colliders, `radius` is set to `w < h ? w : h`.
49 *
50 * @param mass The mass of the body. Set to 0 for infinite mass.
51 * @param x The x-coordinate of the body's initial position.
52 * @param y The y-coordinate of the body's initial position.
53 * @param w The width of the body (or diameter for circles).
54 * @param h The height of the body (or diameter for circles).
55 * @param c The type of collider for the body (`GM_COLLIDER_RECT` or `GM_COLLIDER_CIRCLE`).
56 * @return A new `gmBody` instance initialized with default active/non-static properties.
57 */
58gmBody gm_body_create(double mass, double x, double y, double w, double h,
60 gmBody body = {
61 .is_active = 1,
62 .is_static = 0,
63 .collider_type = c,
64 .position = {.x = x, .y = y},
65 .velocity = {.x = 0, .y = 0},
66 .acceleration = {.x = 0, .y = 0},
67 .width = w,
68 .height = h,
69 .radius = w < h ? w : h, // Radius is set from minimum of width/height
70 .mass = mass,
71 .restitution = 1,
72 };
73 return body;
74}
75
76/**
77 * @brief Limits the maximum speed of a body.
78 *
79 * If the body's current speed exceeds `max_speed`, its velocity is scaled
80 * down to match `max_speed` while preserving its direction.
81 *
82 * @param body Pointer to the body to modify.
83 * @param max_speed The maximum allowed speed.
84 */
85void gm_max_speed(gmBody *body, double max_speed) {
86 double current_speed = gm_pos_magniture(body->velocity);
87 if (current_speed > max_speed) {
88 double factor = max_speed / current_speed;
89 body->velocity.x *= factor;
90 body->velocity.y *= factor;
91 }
92}
93
94/**
95 * @brief Sets the minimum speed of a body.
96 *
97 * If the body's current speed falls below `min_speed` (and is not zero),
98 * its velocity is scaled up to match `min_speed` while preserving its direction.
99 * If the body is stationary (speed is 0), its velocity remains 0.
100 *
101 * @param body Pointer to the body to modify.
102 * @param min_speed The minimum allowed speed.
103 */
104void gm_min_speed(gmBody *body, double min_speed) {
105 double current_speed = gm_pos_magniture(body->velocity);
106 if (current_speed < min_speed) {
107 // Avoid division by zero if body is not moving.
108 if (current_speed == 0)
109 return;
110
111 double factor = min_speed / current_speed;
112 body->velocity.x *= factor;
113 body->velocity.y *= factor;
114 }
115}
116
117/**
118 * @brief Sets the speed of a body while preserving its current direction.
119 *
120 * If the body is stationary, it starts moving along the X-axis with the
121 * specified `speed`.
122 *
123 * @param body Pointer to the body to modify.
124 * @param speed The target speed to set.
125 */
126void gm_speed(gmBody *body, double speed) {
127 double current_speed = gm_pos_magniture(body->velocity);
128 if (current_speed == speed)
129 return;
130
131 if (current_speed == 0) {
132 // If speed is 0, we have no direction. Assume movement along X axis.
133 body->velocity.x = speed;
134 body->velocity.y = 0;
135 } else {
136 double factor = speed / current_speed;
137 body->velocity.x *= factor;
138 body->velocity.y *= factor;
139 }
140}
141
142/**
143 * @brief Limits the maximum speed of a body using an animation function.
144 *
145 * This function applies a smooth animation to the body's velocity components
146 * if its speed exceeds `max_speed`.
147 *
148 * @param body Pointer to the body to modify.
149 * @param max_speed The maximum allowed speed.
150 * @param animator Function pointer to animate the velocity change.
151 * @param dt Delta time for animation.
152 * @param t Time parameter for animation.
153 */
154void gm_max_speed_anim(gmBody *body, double max_speed,
155 void animator(double *value, double target, double dt,
156 double t),
157 double dt, double t) {
158 double current_speed = gm_pos_magniture(body->velocity);
159 if (current_speed > max_speed) {
160 double factor = max_speed / current_speed;
161 double x_target = body->velocity.x * factor;
162 double y_target = body->velocity.y * factor;
163 animator(&body->velocity.x, x_target, dt, t);
164 animator(&body->velocity.y, y_target, dt, t);
165 }
166}
167
168/**
169 * @brief Sets the minimum speed of a body using an animation function.
170 *
171 * This function applies a smooth animation to the body's velocity components
172 * if its speed falls below `min_speed` (and is not zero).
173 *
174 * @param body Pointer to the body to modify.
175 * @param min_speed The minimum allowed speed.
176 * @param animator Function pointer to animate the velocity change.
177 * @param dt Delta time for animation.
178 * @param t Time parameter for animation.
179 */
180void gm_min_speed_anim(gmBody *body, double min_speed,
181 void animator(double *value, double target, double dt,
182 double t),
183 double dt, double t) {
184 double current_speed = gm_pos_magniture(body->velocity);
185 if (current_speed < min_speed) {
186 // Avoid division by zero if body is not moving.
187 if (current_speed == 0)
188 return;
189 double factor = min_speed / current_speed;
190 double x_target = body->velocity.x * factor;
191 double y_target = body->velocity.y * factor;
192 animator(&body->velocity.x, x_target, dt, t);
193 animator(&body->velocity.y, y_target, dt, t);
194 }
195}
196
197/**
198 * @brief Sets the speed of a body using an animation function, preserving its direction.
199 *
200 * This function applies a smooth animation to the body's velocity components
201 * to reach the `speed`. If the body is stationary, it starts moving along
202 * the X-axis.
203 *
204 * @param body Pointer to the body to modify.
205 * @param speed The target speed to set.
206 * @param animator Function pointer to animate the velocity change.
207 * @param dt Delta time for animation.
208 * @param t Time parameter for animation.
209 */
210void gm_speed_anim(gmBody *body, double speed,
211 void animator(double *value, double target, double dt,
212 double t),
213 double dt, double t) {
214 double current_speed = gm_pos_magniture(body->velocity);
215 if (current_speed != speed) {
216 double x_target, y_target;
217 if (current_speed == 0) {
218 // If speed is 0, we have no direction. Assume movement along X axis.
219 x_target = speed;
220 y_target = 0;
221 } else {
222 double factor = speed / current_speed;
223 x_target = body->velocity.x * factor;
224 y_target = body->velocity.y * factor;
225 }
226 animator(&body->velocity.x, x_target, dt, t);
227 animator(&body->velocity.y, y_target, dt, t);
228 }
229}
230
231/**
232 * @brief Creates a rectangular physics body.
233 * @param m The mass of the body.
234 * @param x The x-coordinate of the body's position.
235 * @param y The y-coordinate of the body's position.
236 * @param w The width of the body.
237 * @param h The height of the body.
238 * @return A new rectangular gmBody instance.
239 */
240gmBody gm_rectangle_body(double m, double x, double y, double w, double h) {
241 return gm_body_create(m, x, y, w, h, GM_COLLIDER_RECT);
242}
243
244/**
245 * @brief Creates a circular physics body.
246 * @param m The mass of the body.
247 * @param x The x-coordinate of the body's position.
248 * @param y The y-coordinate of the body's position.
249 * @param r The radius of the body.
250 * @return A new circular gmBody instance.
251 */
252gmBody gm_circle_body(double m, double x, double y, double r) {
253 return gm_body_create(m, x, y, r * 2, r * 2, GM_COLLIDER_CIRCLE); // Pass diameter for w/h
254}
255
256/**
257 * @brief Checks if a point is contained within a body's collider.
258 * @param body Pointer to the body to check.
259 * @param x The x-coordinate of the point.
260 * @param y The y-coordinate of the point.
261 * @return 1 if the point is inside the body, 0 otherwise.
262 */
263int gm_body_contains(gmBody *body, double x, double y);
264
265/**
266 * @brief Checks if a body is currently being hovered over by the mouse.
267 * @param body Pointer to the body to check.
268 * @return 1 if the body is being hovered over, 0 otherwise.
269 */
270static inline int gm_hovered(gmBody *body) {
271 return gm_body_contains(body, gm_mouse.position.x, gm_mouse.position.y);
272}
273
274/**
275 * @brief Checks if a body is currently being clicked by the mouse.
276 * @param body Pointer to the body to check.
277 * @return 1 if the body is being clicked, 0 otherwise.
278 */
279static inline int gm_clicked(gmBody *body) {
280 return gm_mouse.clicked && gm_hovered(body);
281}
282
283/**
284 * @brief Constrains a body within specified rectangular boundaries by clipping its
285 * position.
286 *
287 * If a boundary parameter (bx, ex, by, ey) is 0, that boundary is ignored.
288 * The body's position is clamped to remain within the specified range.
289 *
290 * @param body Pointer to the body to constrain.
291 * @param bx The beginning x-coordinate of the boundary. If 0, the left boundary is ignored.
292 * @param ex The ending x-coordinate of the boundary. If 0, the right boundary is ignored.
293 * @param by The beginning y-coordinate of the boundary. If 0, the bottom boundary is ignored.
294 * @param ey The ending y-coordinate of the boundary. If 0, the top boundary is ignored.
295 * @return A bitmask indicating which boundaries were exceeded and clipped:
296 * - Bit 3 (0b1000): Left boundary (`body->position.x < bx`)
297 * - Bit 2 (0b0100): Right boundary (`body->position.x > ex`)
298 * - Bit 1 (0b0010): Bottom boundary (`body->position.y < by`)
299 * - Bit 0 (0b0001): Top boundary (`body->position.y > ey`)
300 */
301static inline uint8_t gm_body_bound_clip(gmBody *body, double bx, double ex,
302 double by, double ey) {
303
304 int8_t exited = 0;
305 if (bx != 0 || ex != 0) { // Only apply if bounds are specified
306 if (body->position.x < bx) {
307 body->position.x = bx;
308 exited |= 0b1000;
309 } else if (body->position.x > ex) {
310 body->position.x = ex;
311 exited |= 0b0100;
312 }
313 }
314 if (by != 0 || ey != 0) { // Only apply if bounds are specified
315 if (body->position.y < by) {
316 body->position.y = by;
317 exited |= 0b0010;
318 } else if (body->position.y > ey) {
319 body->position.y = ey;
320 exited |= 0b0001;
321 }
322 }
323 return exited;
324}
325
326/**
327 * @brief Wraps a body around specified rectangular boundaries (like a torus).
328 *
329 * If the body exits one side, it reappears on the opposite side.
330 * Note: If a boundary parameter is 0, that boundary check is skipped.
331 *
332 * @param body Pointer to the body to wrap.
333 * @param bx The beginning x-coordinate of the boundary.
334 * @param ex The ending x-coordinate of the boundary.
335 * @param by The beginning y-coordinate of the boundary.
336 * @param ey The ending y-coordinate of the boundary.
337 * @return A bitmask indicating which boundaries were exceeded:
338 * - Bit 3 (0b1000): Left boundary (`body->position.x < bx`)
339 * - Bit 2 (0b0100): Right boundary (`body->position.x > ex`)
340 * - Bit 1 (0b0010): Bottom boundary (`body->position.y < by`)
341 * - Bit 0 (0b0001): Top boundary (`body->position.y > ey`)
342 */
343static inline int8_t gm_body_bound_reflect(gmBody *body, double bx, double ex,
344 double by, double ey) {
345 int8_t exited = 0;
346 if (body->position.x < bx) {
347 body->position.x = ex;
348 exited |= 0b1000;
349 } else if (body->position.x > ex) {
350 body->position.x = bx;
351 exited |= 0b0100;
352 }
353 if (body->position.y < by) {
354 body->position.y = ey;
355 exited |= 0b0010;
356 } else if (body->position.y > ey) {
357 body->position.y = by;
358 exited |= 0b0001;
359 }
360 return exited;
361}
362
363/**
364 * @brief Bounces a body when it exceeds specified rectangular boundaries.
365 *
366 * If the body's position goes beyond a boundary and its velocity is moving
367 * further out of bounds, its velocity component in that direction is reversed
368 * and scaled by the `restitution` factor.
369 *
370 * @param body Pointer to the body to bounce.
371 * @param bx The beginning x-coordinate of the boundary.
372 * @param ex The ending x-coordinate of the boundary.
373 * @param by The beginning y-coordinate of the boundary.
374 * @param ey The ending y-coordinate of the boundary.
375 * @param restitution The bounciness factor to apply (0.0 for no bounce, 1.0 for perfect bounce).
376 * @return A bitmask indicating which boundaries were exceeded:
377 * - Bit 3 (0b1000): Left boundary (`body->position.x < bx`)
378 * - Bit 2 (0b0100): Right boundary (`body->position.x > ex`)
379 * - Bit 1 (0b0010): Bottom boundary (`body->position.y < by`)
380 * - Bit 0 (0b0001): Top boundary (`body->position.y > ey`)
381 */
382static inline int8_t gm_body_bound_bounce(gmBody *body, double bx, double ex,
383 double by, double ey,
384 double restitution) {
385 int8_t exited = 0;
386 if (body->position.x < bx && body->velocity.x < 0) {
387 body->position.x = bx; // Snap to boundary to prevent sinking
388 body->velocity.x = -body->velocity.x * restitution;
389 exited |= 0b1000;
390 } else if (body->position.x > ex && body->velocity.x > 0) {
391 body->position.x = ex; // Snap to boundary
392 body->velocity.x = -body->velocity.x * restitution;
393 exited |= 0b0100;
394 }
395 if (body->position.y < by && body->velocity.y < 0) {
396 body->position.y = by; // Snap to boundary
397 body->velocity.y = -body->velocity.y * restitution;
398 exited |= 0b0010;
399 } else if (body->position.y > ey && body->velocity.y > 0) {
400 body->position.y = ey; // Snap to boundary
401 body->velocity.y = -body->velocity.y * restitution;
402 exited |= 0b0001;
403 }
404 return exited;
405}
void gm_speed_anim(gmBody *body, double speed, void animator(double *value, double target, double dt, double t), double dt, double t)
Sets the speed of a body using an animation function, preserving its direction.
Definition body.h:210
void gm_max_speed(gmBody *body, double max_speed)
Limits the maximum speed of a body.
Definition body.h:85
gmBody gm_rectangle_body(double m, double x, double y, double w, double h)
Creates a rectangular physics body.
Definition body.h:240
void gm_speed(gmBody *body, double speed)
Sets the speed of a body while preserving its current direction.
Definition body.h:126
gmBody gm_body_create(double mass, double x, double y, double w, double h, gmColliderType c)
Creates a new physics body with specified properties.
Definition body.h:58
void gm_min_speed(gmBody *body, double min_speed)
Sets the minimum speed of a body.
Definition body.h:104
gmColliderType
Enum to define the type of collider for a physics body.
Definition body.h:16
@ GM_COLLIDER_RECT
Definition body.h:18
@ GM_COLLIDER_CIRCLE
Definition body.h:17
gmBody gm_circle_body(double m, double x, double y, double r)
Creates a circular physics body.
Definition body.h:252
int gm_body_contains(gmBody *body, double x, double y)
Checks if a point is contained within a body's collider.
Definition collision.h:151
void gm_min_speed_anim(gmBody *body, double min_speed, void animator(double *value, double target, double dt, double t), double dt, double t)
Sets the minimum speed of a body using an animation function.
Definition body.h:180
void gm_max_speed_anim(gmBody *body, double max_speed, void animator(double *value, double target, double dt, double t), double dt, double t)
Limits the maximum speed of a body using an animation function.
Definition body.h:154
struct _gmMouse gm_mouse
Definition mouse.h:32
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 friction
Definition body.h:41
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
Represents a 2D position or vector.
Definition position.h:8
double x
Definition position.h:9
double y
Definition position.h:9