Banjo API 1.0.0-rc.2
Low-level C99 game development API
Loading...
Searching...
No Matches
physics_particle.c

Particle physics with force integration for orbital mechanics.

Particle physics with force integration for orbital mechanics.Particle physics simulates objects with forces that change over time. Unlike kinematics (constant acceleration), particles accumulate forces each frame and integrate to update velocity and position. Use particles when:

  • Forces vary (gravity between moving objects, springs, drag)
  • Multiple forces act simultaneously
  • You need realistic physics interactions

The particle loop: accumulate forces → integrate → clear forces → repeat This example simulates a solar system where gravitational forces constantly change as planets move, creating realistic orbital mechanics.

#include <banjo/app.h>
#include <banjo/main.h>
#include <banjo/assert.h>
#include <banjo/bitmap.h>
#include <banjo/draw.h>
#include <banjo/event.h>
#include <banjo/log.h>
#include <banjo/mat.h>
#include <banjo/physics.h>
#include <banjo/pixel.h>
#include <banjo/system.h>
#include <banjo/time.h>
#include <banjo/vec.h>
#include <banjo/window.h>
#include <stdlib.h>
#include <time.h>
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
#define CANVAS_WIDTH SCREEN_WIDTH
#define CANVAS_HEIGHT SCREEN_HEIGHT
#define FB_PIXEL_MODE BJ_PIXEL_MODE_XRGB8888
static void on_draw(struct bj_window*, struct bj_render_target*,
const struct bj_rect*, void*);
// Physics constants for the solar system simulation.
// G_SUN: gravitational constant (tuned for visual appeal, not realistic)
// SOFTENING: prevents infinite forces when particles get very close
// Masses: relative to Earth (Jupiter is 317.8× Earth's mass)
#define G_SUN BJ_F(120.0)
#define SOFTENING BJ_F(6.0)
#define M_SUN BJ_F(1000.0)
#define M_MERCURY BJ_F(0.055)
#define M_VENUS BJ_F(0.815)
#define M_EARTH BJ_F(1.0)
#define M_MARS BJ_F(0.107)
#define M_JUPITER BJ_F(317.8)
// bj_particle_2d contains: position, velocity, forces, inverse_mass, damping
// Forces accumulate during a frame, then bj_step_particle_2d() integrates them
// into velocity and position changes.
typedef struct {
bj_real radius;
uint32_t color;
#define N_PLANETS 5
#define N_ASTEROIDS 800
uint32_t asteroid_color;
static void update_projection() {
bj_mat3x3 ortho, viewport;
bj_mat3_mul(&projection, &viewport, &ortho);
}
// Calculate stable orbital velocity for a circular orbit with softening.
// For realistic orbits, velocity must balance gravitational force.
// Formula: v = sqrt(G*M*r² / (r² + eps²)^1.5)
// Without this, planets would either spiral in or fly away.
const bj_real r2 = r*r;
const bj_real denom = bj_pow(r2 + eps*eps, BJ_F(1.5));
return (denom > BJ_FZERO) ? bj_sqrt( (G*M) * r2 / denom ) : BJ_FZERO;
}
static void init_sun() {
sun.damping = BJ_F(1.0);
sun.inverse_mass = BJ_F(1.0) / M_SUN;
}
// Initialise a planet in a stable circular orbit.
// Setting up particles: configure position, velocity, mass, damping.
// - Position: place at orbital radius r, angle phase
// - Velocity: perpendicular to position vector, magnitude = orbital speed
// - Mass: stored as inverse_mass for efficiency (F = a / inverse_mass)
// - Damping: 1.0 = no damping (perfect conservation of energy)
static void init_planet(planet_t* p, bj_real r, bj_real mass, uint32_t color, bj_real draw_r, bj_real phase) {
const bj_real a = phase;
p->body.position.x = r * bj_cos(a);
p->body.position.y = r * bj_sin(a);
p->body.velocity.x = -v * bj_sin(a);
p->body.velocity.y = v * bj_cos(a);
p->body.damping = BJ_F(1.0);
p->body.inverse_mass = BJ_F(1.0) / mass;
p->radius = draw_r;
p->color = color;
}
static void init_asteroids() {
for (size_t i = 0; i < N_ASTEROIDS; ++i) {
const bj_real rmin = BJ_F(190.0), rmax = BJ_F(260.0);
const bj_real t = (bj_real)rand() / (bj_real)RAND_MAX;
const bj_real u = (bj_real)rand() / (bj_real)RAND_MAX;
const bj_real r = rmin + (rmax - rmin) * t;
const bj_real a = BJ_TAU * u;
asteroids[i].position.x = r * bj_cos(a);
asteroids[i].position.y = r * bj_sin(a);
asteroids[i].velocity.x = -v * bj_sin(a);
asteroids[i].velocity.y = v * bj_cos(a);
asteroids[i].forces = BJ_VEC2_ZERO;
asteroids[i].damping = BJ_F(1.0);
asteroids[i].inverse_mass = 1.0;
}
}
static void initialize() {
uint32_t col_mercury = bj_get_pixel_value(FB_PIXEL_MODE, 0xC8, 0xC8, 0xC8);
uint32_t col_venus = bj_get_pixel_value(FB_PIXEL_MODE, 0xD4, 0xA3, 0x58);
uint32_t col_earth = bj_get_pixel_value(FB_PIXEL_MODE, 0x30, 0xA0, 0xFF);
uint32_t col_mars = bj_get_pixel_value(FB_PIXEL_MODE, 0xD0, 0x50, 0x30);
uint32_t col_jupiter = bj_get_pixel_value(FB_PIXEL_MODE, 0xD2, 0xB4, 0x8C);
init_planet(&planets[0], BJ_F(60.0), M_MERCURY, col_mercury, BJ_F(2.0), BJ_F(0.0));
init_planet(&planets[1], BJ_F(90.0), M_VENUS, col_venus, BJ_F(3.0), BJ_F(1.2));
init_planet(&planets[2], BJ_F(130.0), M_EARTH, col_earth, BJ_F(3.2), BJ_F(2.0));
init_planet(&planets[3], BJ_F(170.0), M_MARS, col_mars, BJ_F(2.6), BJ_F(2.6));
init_planet(&planets[4], BJ_F(260.0), M_JUPITER, col_jupiter, BJ_F(6.0), BJ_F(0.8));
}
// The particle physics loop: apply forces, then integrate. Called from
// fixed_step so dt is constant (1 / fixed_step_rate); the simulation
// timestep is configured to 1/120 s in main() to keep the integrator
// stable for this gravity setup.
//
// 1. bj_apply_point_gravity_softened_2d(): Accumulates gravitational force
// into particle.forces based on distance and masses. Softening prevents
// infinite forces when particles are very close.
//
// 2. bj_step_particle_2d(): Integrates accumulated forces:
// - acceleration = forces * inverse_mass
// - velocity += acceleration * dt (with damping)
// - position += velocity * dt
// - forces = zero (ready for next frame)
//
// This pattern allows multiple forces (gravity, wind, springs) to combine
// naturally. Each force function just adds to particle.forces, then step
// integrates them all at once.
static void physics(bj_real dt) {
for (size_t i = 0; i < N_PLANETS; ++i) {
}
for (size_t i = 0; i < N_ASTEROIDS; ++i) {
}
}
static void draw(bj_bitmap* framebuffer) {
bj_clear_bitmap(framebuffer);
const uint32_t col_sun = bj_get_pixel_value(FB_PIXEL_MODE, 0xFF, 0xCC, 0x44);
bj_vec3 c = { sun.position.x, sun.position.y, BJ_F(1.0) };
bj_draw_filled_circle(framebuffer, pc.x, pc.y, BJ_F(10.0), col_sun);
for (size_t i = 0; i < N_PLANETS; ++i) {
c.x = planets[i].body.position.x; c.y = planets[i].body.position.y;
bj_draw_filled_circle(framebuffer, pc.x, pc.y, planets[i].radius, planets[i].color);
}
for (size_t i = 0; i < N_ASTEROIDS; ++i) {
c.x = asteroids[i].position.x; c.y = asteroids[i].position.y;
bj_put_pixel(framebuffer, (int)pc.x, (int)pc.y, asteroid_color);
}
}
static void* setup(struct bj_app* app, void* init_data) {
(void)init_data;
srand((unsigned)time(NULL));
bj_quit_app(app, 1);
return 0;
}
window = bj_bind_window("2D Solar System + Asteroids", 100, 100, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0);
return 0;
}
static void on_draw(
struct bj_window* w,
struct bj_render_target* target,
const struct bj_rect* dirty,
void* user_data
) {
(void)w; (void)dirty; (void)user_data;
}
// Particle physics is integrated in fixed_step so the orbits are stable
// regardless of how often step gets to fire. The 120 Hz rate set in main
// keeps dt = 1/120 s, matching what the integrator was already clamping
// to under the old variable-rate model.
//
// Particles vs Kinematics choice:
// - Use particles when forces change (orbiting bodies, springs, interactions)
// - Use kinematics when acceleration is constant (projectiles with gravity only)
// Here, gravity direction/magnitude changes as planets orbit, so particles are needed.
static void fixed_step(struct bj_app* app, struct bj_tick_info tick, void* user_data) {
(void)app; (void)user_data;
physics(tick.delta);
}
static void step(struct bj_app* app, struct bj_tick_info tick, void* user_data) {
(void)tick; (void)user_data;
bj_quit_app(app, 0);
}
}
static void teardown(struct bj_app* app, void* user_data) {
(void)user_data;
bj_end();
}
int main(int argc, char* argv[]) {
(void)argc; (void)argv;
}
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.