Banjo API 1.0.0-rc.2
Low-level C99 game development API
Loading...
Searching...
No Matches
physics_particle.c
Go to the documentation of this file.
1
17
18#include <banjo/app.h>
19#include <banjo/main.h>
20#include <banjo/assert.h>
21#include <banjo/bitmap.h>
22#include <banjo/draw.h>
23#include <banjo/event.h>
24#include <banjo/log.h>
25#include <banjo/mat.h>
26#include <banjo/physics.h>
27#include <banjo/physics_2d.h>
28#include <banjo/pixel.h>
29#include <banjo/system.h>
30#include <banjo/time.h>
31#include <banjo/vec.h>
32#include <banjo/window.h>
33
34#include <stdlib.h>
35#include <time.h>
36
37#define SCREEN_WIDTH 800
38#define SCREEN_HEIGHT 600
39#define CANVAS_WIDTH SCREEN_WIDTH
40#define CANVAS_HEIGHT SCREEN_HEIGHT
41
42#define FB_PIXEL_MODE BJ_PIXEL_MODE_XRGB8888
43
46
47static void on_draw(struct bj_window*, struct bj_render_target*,
48 const struct bj_rect*, void*);
49
50// Physics constants for the solar system simulation.
51// G_SUN: gravitational constant (tuned for visual appeal, not realistic)
52// SOFTENING: prevents infinite forces when particles get very close
53// Masses: relative to Earth (Jupiter is 317.8× Earth's mass)
54#define G_SUN BJ_F(120.0)
55#define SOFTENING BJ_F(6.0)
56#define M_SUN BJ_F(1000.0)
57#define M_MERCURY BJ_F(0.055)
58#define M_VENUS BJ_F(0.815)
59#define M_EARTH BJ_F(1.0)
60#define M_MARS BJ_F(0.107)
61#define M_JUPITER BJ_F(317.8)
62
63// bj_particle_2d contains: position, velocity, forces, inverse_mass, damping
64// Forces accumulate during a frame, then bj_step_particle_2d() integrates them
65// into velocity and position changes.
66typedef struct {
69 uint32_t color;
70} planet_t;
71
72#define N_PLANETS 5
75
76#define N_ASTEROIDS 800
79
80static void update_projection() {
81 bj_mat3x3 ortho, viewport;
83 bj_mat3_set_viewport(&viewport, 0.f, 0.f, SCREEN_WIDTH, SCREEN_HEIGHT);
84 bj_mat3_mul(&projection, &viewport, &ortho);
85}
86
87// Calculate stable orbital velocity for a circular orbit with softening.
88// For realistic orbits, velocity must balance gravitational force.
89// Formula: v = sqrt(G*M*r² / (r² + eps²)^1.5)
90// Without this, planets would either spiral in or fly away.
92 const bj_real r2 = r*r;
93 const bj_real denom = bj_pow(r2 + eps*eps, BJ_F(1.5));
94 return (denom > BJ_FZERO) ? bj_sqrt( (G*M) * r2 / denom ) : BJ_FZERO;
95}
96
97static void init_sun() {
98 sun.damping = BJ_F(1.0);
99 sun.inverse_mass = BJ_F(1.0) / M_SUN;
100}
101
102// Initialise a planet in a stable circular orbit.
103// Setting up particles: configure position, velocity, mass, damping.
104// - Position: place at orbital radius r, angle phase
105// - Velocity: perpendicular to position vector, magnitude = orbital speed
106// - Mass: stored as inverse_mass for efficiency (F = a / inverse_mass)
107// - Damping: 1.0 = no damping (perfect conservation of energy)
108static void init_planet(planet_t* p, bj_real r, bj_real mass, uint32_t color, bj_real draw_r, bj_real phase) {
109 const bj_real a = phase;
110 p->body.position.x = r * bj_cos(a);
111 p->body.position.y = r * bj_sin(a);
112
114 p->body.velocity.x = -v * bj_sin(a);
115 p->body.velocity.y = v * bj_cos(a);
116
118 p->body.damping = BJ_F(1.0);
119 p->body.inverse_mass = BJ_F(1.0) / mass;
120
121 p->radius = draw_r;
122 p->color = color;
123}
124
125static void init_asteroids() {
127 for (size_t i = 0; i < N_ASTEROIDS; ++i) {
128 const bj_real rmin = BJ_F(190.0), rmax = BJ_F(260.0);
129 const bj_real t = (bj_real)rand() / (bj_real)RAND_MAX;
130 const bj_real u = (bj_real)rand() / (bj_real)RAND_MAX;
131 const bj_real r = rmin + (rmax - rmin) * t;
132 const bj_real a = BJ_TAU * u;
133
134 asteroids[i].position.x = r * bj_cos(a);
135 asteroids[i].position.y = r * bj_sin(a);
136
138 asteroids[i].velocity.x = -v * bj_sin(a);
139 asteroids[i].velocity.y = v * bj_cos(a);
140
141 asteroids[i].forces = BJ_VEC2_ZERO;
142 asteroids[i].damping = BJ_F(1.0);
143 asteroids[i].inverse_mass = 1.0;
144 }
145}
146
147static void initialize() {
148 init_sun();
149
150 uint32_t col_mercury = bj_get_pixel_value(FB_PIXEL_MODE, 0xC8, 0xC8, 0xC8);
151 uint32_t col_venus = bj_get_pixel_value(FB_PIXEL_MODE, 0xD4, 0xA3, 0x58);
152 uint32_t col_earth = bj_get_pixel_value(FB_PIXEL_MODE, 0x30, 0xA0, 0xFF);
153 uint32_t col_mars = bj_get_pixel_value(FB_PIXEL_MODE, 0xD0, 0x50, 0x30);
154 uint32_t col_jupiter = bj_get_pixel_value(FB_PIXEL_MODE, 0xD2, 0xB4, 0x8C);
155
156 init_planet(&planets[0], BJ_F(60.0), M_MERCURY, col_mercury, BJ_F(2.0), BJ_F(0.0));
157 init_planet(&planets[1], BJ_F(90.0), M_VENUS, col_venus, BJ_F(3.0), BJ_F(1.2));
158 init_planet(&planets[2], BJ_F(130.0), M_EARTH, col_earth, BJ_F(3.2), BJ_F(2.0));
159 init_planet(&planets[3], BJ_F(170.0), M_MARS, col_mars, BJ_F(2.6), BJ_F(2.6));
160 init_planet(&planets[4], BJ_F(260.0), M_JUPITER, col_jupiter, BJ_F(6.0), BJ_F(0.8));
161
163}
164
165// The particle physics loop: apply forces, then integrate. Called from
166// fixed_step so dt is constant (1 / fixed_step_rate); the simulation
167// timestep is configured to 1/120 s in main() to keep the integrator
168// stable for this gravity setup.
169//
170// 1. bj_apply_point_gravity_softened_2d(): Accumulates gravitational force
171// into particle.forces based on distance and masses. Softening prevents
172// infinite forces when particles are very close.
173//
174// 2. bj_step_particle_2d(): Integrates accumulated forces:
175// - acceleration = forces * inverse_mass
176// - velocity += acceleration * dt (with damping)
177// - position += velocity * dt
178// - forces = zero (ready for next frame)
179//
180// This pattern allows multiple forces (gravity, wind, springs) to combine
181// naturally. Each force function just adds to particle.forces, then step
182// integrates them all at once.
183static void physics(bj_real dt) {
184 for (size_t i = 0; i < N_PLANETS; ++i) {
186 bj_step_particle_2d(&planets[i].body, dt);
187 }
188 for (size_t i = 0; i < N_ASTEROIDS; ++i) {
191 }
192}
193
194static void draw(bj_bitmap* framebuffer) {
195 bj_clear_bitmap(framebuffer);
196
197 const uint32_t col_sun = bj_get_pixel_value(FB_PIXEL_MODE, 0xFF, 0xCC, 0x44);
198
199 bj_vec3 c = { sun.position.x, sun.position.y, BJ_F(1.0) };
201 bj_draw_filled_circle(framebuffer, pc.x, pc.y, BJ_F(10.0), col_sun);
202
203 for (size_t i = 0; i < N_PLANETS; ++i) {
204 c.x = planets[i].body.position.x; c.y = planets[i].body.position.y;
206 bj_draw_filled_circle(framebuffer, pc.x, pc.y, planets[i].radius, planets[i].color);
207 }
208
209 for (size_t i = 0; i < N_ASTEROIDS; ++i) {
210 c.x = asteroids[i].position.x; c.y = asteroids[i].position.y;
212 bj_put_pixel(framebuffer, (int)pc.x, (int)pc.y, asteroid_color);
213 }
214}
215
216static void* setup(struct bj_app* app, void* init_data) {
217 (void)init_data;
218
219 bj_set_fixed_step_rate(app, 120);
220
221 srand((unsigned)time(NULL));
222
223 if (!bj_begin(BJ_VIDEO_SYSTEM, 0)) {
224 bj_quit_app(app, 1);
225 return 0;
226 }
227
228 window = bj_bind_window("2D Solar System + Asteroids", 100, 100, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0);
229
232
234 initialize();
235 return 0;
236}
237
238static void on_draw(
239 struct bj_window* w,
240 struct bj_render_target* target,
241 const struct bj_rect* dirty,
242 void* user_data
243) {
244 (void)w; (void)dirty; (void)user_data;
246}
247
248// Particle physics is integrated in fixed_step so the orbits are stable
249// regardless of how often step gets to fire. The 120 Hz rate set in main
250// keeps dt = 1/120 s, matching what the integrator was already clamping
251// to under the old variable-rate model.
252//
253// Particles vs Kinematics choice:
254// - Use particles when forces change (orbiting bodies, springs, interactions)
255// - Use kinematics when acceleration is constant (projectiles with gravity only)
256// Here, gravity direction/magnitude changes as planets orbit, so particles are needed.
257static void fixed_step(struct bj_app* app, struct bj_tick_info tick, void* user_data) {
258 (void)app; (void)user_data;
259 physics(tick.delta);
260}
261
262static void step(struct bj_app* app, struct bj_tick_info tick, void* user_data) {
263 (void)tick; (void)user_data;
266
268 bj_quit_app(app, 0);
269 }
270}
271
272static void teardown(struct bj_app* app, void* user_data) {
273 (void)user_data;
275 bj_end();
276}
277
278int main(int argc, char* argv[]) {
279 (void)argc; (void)argv;
281}
Application lifecycle: callback-driven setup, step, and teardown.
Assertion facility for Banjo API.
int main(int argc, char *argv[])
Definition audio_pcm.c:177
static void step(struct bj_app *app, struct bj_tick_info tick, void *user_data)
Definition audio_pcm.c:144
static void teardown(struct bj_app *app, void *user_data)
Definition audio_pcm.c:170
static void * setup(struct bj_app *app, void *init_data)
Definition audio_pcm.c:107
Header file for Bitmap type.
static void on_draw(struct bj_window *w, struct bj_render_target *target, const struct bj_rect *dirty, void *user_data)
Definition bitmap_blit.c:32
bj_window * window
Definition bitmap_blit.c:24
Header file for Bitmap drawing functions.
void draw(bj_bitmap *bmp)
Definition drawing_2d.c:133
Sytem event management API.
bj_real delta
Definition app.h:107
int bj_run_app(bj_app_setup_fn setup, bj_app_step_fn step, bj_app_fixed_step_fn fixed_step, bj_app_teardown_fn teardown, void *init_data)
Drive the application lifecycle.
void bj_set_fixed_step_rate(struct bj_app *app, int hz)
Configure the fixed-step rate (in Hz) for app.
void bj_quit_app(struct bj_app *app, int exit_code)
Signal the given application to exit on the next iteration.
Timing snapshot handed to the step and fixed-step callbacks.
Definition app.h:106
void bj_clear_bitmap(struct bj_bitmap *bitmap)
Fills the entire bitmap with the clear colour.
void bj_put_pixel(struct bj_bitmap *bitmap, size_t x, size_t y, uint32_t value)
Change the pixel colour at given coordinate.
struct bj_render_target bj_render_target
Definition api.h:346
struct bj_bitmap bj_bitmap
Definition api.h:328
struct bj_window bj_window
Definition api.h:354
void bj_draw_filled_circle(struct bj_bitmap *bitmap, int cx, int cy, int radius, uint32_t color)
Draw a filled circle onto a bitmap.
void bj_dispatch_events(void)
Poll and dispatch all pending events.
void bj_close_on_escape(struct bj_window *window, const struct bj_key_event *event, void *user_data)
Handle the ESC key to close a window.
bj_key_callback_fn bj_set_key_callback(bj_key_callback_fn callback, void *user_data)
Set the global callback for keyboard key events.
bj_real x
X component.
Definition vec.h:52
bj_real x
X component.
Definition vec.h:65
bj_real y
Y component.
Definition vec.h:53
bj_real y
Y component.
Definition vec.h:66
#define BJ_TAU
TAU in the selected bj_real precision.
Definition math.h:113
static struct bj_vec3 bj_mat3_transform_vec3(const struct bj_mat3x3 *restrict M, struct bj_vec3 v)
Multiply a 3×3 matrix by a 3D vector: r = M * v.
Definition mat.h:245
#define BJ_VEC2_ZERO
Zero 2D vector literal {0, 0}.
Definition vec.h:57
static void bj_mat3_set_ortho(struct bj_mat3x3 *restrict M, bj_real l, bj_real r, bj_real b, bj_real t)
Build a 2D orthographic projection into a 3×3 matrix.
Definition mat.h:479
#define bj_sin
Sine.
Definition math.h:246
#define BJ_FZERO
Zero constant in bj_real.
Definition math.h:89
#define bj_pow
Power.
Definition math.h:244
#define bj_cos
Cosine.
Definition math.h:237
static void bj_mat3_mul(struct bj_mat3x3 *restrict out, const struct bj_mat3x3 *restrict A, const struct bj_mat3x3 *restrict B)
Matrix product: out = A * B.
Definition mat.h:219
#define BJ_F(x)
Literal suffix helper for bj_real when float is selected.
Definition math.h:78
#define bj_sqrt
Square root.
Definition math.h:247
float bj_real
Selected real type for float configuration.
Definition math.h:76
static void bj_mat3_set_viewport(struct bj_mat3x3 *restrict M, bj_real x, bj_real y, bj_real w, bj_real h)
Build a 2D viewport transform into a 3×3 matrix.
Definition mat.h:506
3×3 column-major matrix.
Definition mat.h:59
Axis-aligned rectangle: a top-left corner plus a width and height.
Definition rect.h:33
3D vector of bj_real components.
Definition vec.h:64
struct bj_vec2 position
Definition physics_2d.h:99
bj_real damping
Definition physics_2d.h:103
struct bj_vec2 velocity
Definition physics_2d.h:100
struct bj_vec2 forces
Definition physics_2d.h:102
bj_real inverse_mass
Definition physics_2d.h:104
void bj_step_particle_2d(struct bj_particle_2d *particle, bj_real dt)
Semi-implicit Euler step for a particle.
void bj_apply_point_gravity_softened_2d(struct bj_particle_2d *restrict particle_from, const struct bj_particle_2d *restrict particle_to, const bj_real gravity_factor, const bj_real epsilon)
Apply softened point gravity to avoid singularities at small r.
2D point mass state and physical properties.
Definition physics_2d.h:98
uint32_t bj_get_pixel_value(enum bj_pixel_mode mode, uint8_t red, uint8_t green, uint8_t blue)
Returns an opaque value representing a pixel colour, given its RGB composition.
bj_bool bj_begin(int systems, struct bj_error **error)
Initialises the system.
void bj_end(void)
De-initialises the system.
@ BJ_VIDEO_SYSTEM
Definition system.h:81
void bj_set_draw_callback(struct bj_window *window, bj_window_draw_fn fn, void *user_data)
Register the redraw callback for window.
static void bj_invalidate_window(struct bj_window *window)
Mark the whole window as needing a repaint.
Definition window.h:470
struct bj_window * bj_bind_window(const char *title, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t flags, struct bj_error **error)
Create a new struct bj_window with the specified attributes.
struct bj_bitmap * bj_render_target_bitmap(struct bj_render_target *target)
Reach the software framebuffer behind a render target.
bj_bool bj_should_close_window(struct bj_window *window)
Get the close flag state of a window.
void bj_unbind_window(struct bj_window *window)
Deletes a struct bj_window object and releases associated memory.
static void fixed_step(struct bj_app *app, struct bj_tick_info tick, void *ud)
Logging utility functions.
Portable main() replacement with platform-aware entry shim.
Matrix types and operations for 2D and 3D transforms.
Physics helpers (SI units, but dimensionally consistent with any unit system).
Physics helpers (SI units, but dimensionally consistent with any unit system).
#define SCREEN_WIDTH
#define SCREEN_HEIGHT
#define FB_PIXEL_MODE
static void update_projection()
#define M_VENUS
uint32_t color
#define M_EARTH
bj_particle_2d body
static void init_planet(planet_t *p, bj_real r, bj_real mass, uint32_t color, bj_real draw_r, bj_real phase)
#define N_PLANETS
#define N_ASTEROIDS
bj_real radius
uint32_t asteroid_color
#define M_MARS
bj_particle_2d sun
planet_t planets[5]
#define M_JUPITER
static void init_sun()
static void init_asteroids()
#define SOFTENING
#define CANVAS_WIDTH
static bj_real orbital_speed_soft(bj_real G, bj_real M, bj_real r, bj_real eps)
bj_particle_2d asteroids[800]
static void initialize()
#define CANVAS_HEIGHT
#define M_SUN
#define M_MERCURY
bj_mat3x3 projection
static void physics(bj_real dt)
#define G_SUN
Header file for general pixel manipulation facilities.
Header file for system interactions.
Header file for time manipulation utilities.
Fixed-size vector types (2D, 3D, 4D) and inline operations.
Header file for bj_window type.