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

Smooth rendering on top of a slow fixed-step simulation, using tick.alpha.

Smooth rendering on top of a slow fixed-step simulation, using tick.alpha.This demo exists to make one idea visible: when your simulation runs at a fixed rate (via a bj_app_fixed_step_fn callback) but your screen redraws faster, most frames fall between two simulation ticks. If you draw the raw simulation position, motion looks like it jumps once per tick. The [0, 1) factor tick.alpha lets you draw the in-between position instead, so motion looks smooth.

The program drives the physics at a deliberately low 10 Hz and draws two balls moving left to right:

  • The top ball is drawn at prev + (curr - prev) * alpha, the interpolated position between the last two ticks. It glides.
  • The bottom ball is drawn at the raw current position. With only ten updates a second it visibly stutters.

Same simulation, same data; the only difference is whether alpha is used at draw time.

How alpha gets to the drawing code

tick.alpha arrives in the bj_app_step_fn callback, but the drawing happens in the window's draw callback (bj_set_draw_callback), which is not handed a bj_tick_info. The demo bridges the two with a single variable: step stashes tick.alpha, and on_draw reads it. That is the normal pattern.

What you'll learn

  • Why a fixed-step simulation needs interpolation to look smooth at higher render rates.
  • How to keep the previous and current simulation state and blend them with tick.alpha.
  • That the fixed-step rate (bj_set_fixed_step_rate) and the render rate (the default frame cap, bj_set_frame_rate) are independent: here 10 Hz physics under 60 Hz rendering.
See also
bj_tick_info, bj_app_fixed_step_fn, physics_particle.c, physics_kinematics.c
#include <banjo/app.h>
#include <banjo/main.h>
#include <banjo/bitmap.h>
#include <banjo/draw.h>
#include <banjo/event.h>
#include <banjo/system.h>
#include <banjo/window.h>
#define WIDTH 640
#define HEIGHT 360
#define BALL_R 24
#define SPEED 320.0f /* pixels per second */
#define FIXED_HZ 10 /* deliberately low so the stutter is obvious */
#define TOP_Y 120 /* smooth (interpolated) ball */
#define BOT_Y 240 /* raw (un-interpolated) ball */
static bj_window* window = 0;
/* Physics state, advanced only in fixed_step at FIXED_HZ. We keep the
previous position alongside the current one so the draw callback can
interpolate between them. */
static float prev_x;
static float curr_x;
static float vx = SPEED;
/* The interpolation factor for the current render. The step callback
receives it in tick.alpha and stashes it here; on_draw reads it. The
draw callback isn't handed a bj_tick_info, so this hand-off is how
alpha reaches the place that actually draws. */
static float render_alpha;
/* Advance the simulation by exactly one fixed tick (1 / FIXED_HZ s).
Save the old position first so we can interpolate toward the new one. */
static void fixed_step(struct bj_app* app, struct bj_tick_info tick, void* ud) {
(void)app; (void)ud;
curr_x += vx * tick.delta;
if (curr_x < BALL_R) { curr_x = BALL_R; vx = +SPEED; }
if (curr_x > WIDTH - BALL_R) { curr_x = WIDTH - BALL_R; vx = -SPEED; }
}
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;
const uint32_t green = bj_make_bitmap_pixel(fb, 0x40, 0xE0, 0x60);
const uint32_t red = bj_make_bitmap_pixel(fb, 0xE0, 0x50, 0x50);
/* Smooth: render between the last two physics positions using alpha. */
const float smooth_x = prev_x + (curr_x - prev_x) * render_alpha;
bj_draw_filled_circle(fb, (int)smooth_x, TOP_Y, BALL_R, green);
/* Raw: render the current physics position directly. At FIXED_HZ it
only changes 10 times a second, so it visibly jumps. */
}
static void step(struct bj_app* app, struct bj_tick_info tick, void* user_data) {
(void)user_data;
render_alpha = (float)tick.alpha; /* hand alpha to on_draw */
bj_invalidate_window(window); /* render every frame at the cap rate */
bj_quit_app(app, 0);
}
}
static void* setup(struct bj_app* app, void* init_data) {
(void)init_data;
bj_set_fixed_step_rate(app, FIXED_HZ); /* slow physics on purpose */
bj_quit_app(app, 1);
return 0;
}
window = bj_bind_window("Interpolation: smooth (top) vs raw (bottom), ESC to quit",
100, 100, WIDTH, HEIGHT, 0, 0);
return 0;
}
static void teardown(struct bj_app* app, void* user_data) {
(void)app; (void)user_data;
bj_end();
}
int main(int argc, char* argv[]) {
(void)argc; (void)argv;
/* Frame rate stays at the default 60, so ~6 renders happen between
each physics tick. That gap is exactly what alpha interpolates. */
}
Application lifecycle: callback-driven setup, step, and teardown.
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.
Sytem event management API.
bj_real delta
Definition app.h:107
bj_real alpha
Definition app.h:108
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.
uint32_t bj_make_bitmap_pixel(struct bj_bitmap *bitmap, uint8_t red, uint8_t green, uint8_t blue)
Returns an opaque value representing a pixel colour, given its RGB composition.
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.
Axis-aligned rectangle: a top-left corner plus a width and height.
Definition rect.h:33
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 float render_alpha
#define WIDTH
#define FIXED_HZ
#define BALL_R
static float curr_x
static float vx
#define SPEED
#define BOT_Y
#define TOP_Y
#define HEIGHT
static void fixed_step(struct bj_app *app, struct bj_tick_info tick, void *ud)
static float prev_x
Portable main() replacement with platform-aware entry shim.
Header file for system interactions.
Header file for bj_window type.