kaboom: match original X11 physics

Use fixed-point x positions, strong upward initial velocity (-15 to
-21), gravity of 1 pixel/frame², only ~50%% of tiles, and run until
all pieces leave the screen. Full-screen scene buffer so pieces
can fly freely.
This commit is contained in:
lkn 2026-06-23 20:30:52 +02:00
parent 503cdb5dca
commit ac74064d1c
1 changed files with 83 additions and 51 deletions

View File

@ -1,6 +1,7 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
@ -16,14 +17,16 @@
#ifdef NORMAL_ICON_KABOOM
#define PIECE_SIZE ICON_KABOOM_PIECE_SIZE
#define ANIM_FRAMES 12
#define ANIM_INTERVAL_MS 30
#define GRAVITY 2
#define KAB_PRECISION 4
#define ANIM_INTERVAL_MS 20
struct kaboom_piece {
int x, y;
int vx, vy;
uint32_t pixels[PIECE_SIZE * PIECE_SIZE];
int px; /* x in fixed-point (<<KAB_PRECISION) */
int py; /* y in pixels */
int vx; /* x velocity in fixed-point */
int vy; /* y velocity in pixels */
int src_x, src_y; /* source tile coords */
bool alive;
};
struct kaboom_state {
@ -31,11 +34,14 @@ struct kaboom_state {
struct wlr_scene_buffer *scene_buf;
struct wlr_buffer base;
pixman_image_t *image;
pixman_image_t *src_image; /* copy of original icon pixels */
struct wl_event_source *timer;
struct kaboom_piece *pieces;
int npieces;
int alive_count;
int width, height;
int frame;
int scr_w, scr_h;
int origin_x, origin_y;
};
static void kaboom_destroy(struct kaboom_state *state)
@ -46,6 +52,8 @@ static void kaboom_destroy(struct kaboom_state *state)
wlr_scene_node_destroy(&state->tree->node);
if (state->image)
pixman_image_unref(state->image);
if (state->src_image)
pixman_image_unref(state->src_image);
wlr_buffer_drop(&state->base);
free(state->pieces);
free(state);
@ -82,19 +90,27 @@ static void kaboom_render(struct kaboom_state *state)
{
/* Clear to transparent */
uint32_t *dst = pixman_image_get_data(state->image);
int stride = pixman_image_get_stride(state->image) / 4;
memset(dst, 0, stride * 4 * state->height);
int dst_stride = pixman_image_get_stride(state->image) / 4;
int buf_w = state->scr_w;
int buf_h = state->scr_h;
memset(dst, 0, (size_t)dst_stride * 4 * buf_h);
/* Draw each alive piece at its screen-relative position */
uint32_t *src = pixman_image_get_data(state->src_image);
int src_stride = pixman_image_get_stride(state->src_image) / 4;
/* Draw each piece at its current position */
for (int i = 0; i < state->npieces; i++) {
struct kaboom_piece *p = &state->pieces[i];
for (int py = 0; py < PIECE_SIZE; py++) {
int dy = p->y + py;
if (dy < 0 || dy >= state->height) continue;
for (int px = 0; px < PIECE_SIZE; px++) {
int dx = p->x + px;
if (dx < 0 || dx >= state->width) continue;
dst[dy * stride + dx] = p->pixels[py * PIECE_SIZE + px];
if (!p->alive) continue;
int dx = (p->px >> KAB_PRECISION) - state->origin_x;
int dy = p->py - state->origin_y;
for (int row = 0; row < PIECE_SIZE; row++) {
int yd = dy + row;
if (yd < 0 || yd >= buf_h) continue;
for (int col = 0; col < PIECE_SIZE; col++) {
int xd = dx + col;
if (xd < 0 || xd >= buf_w) continue;
dst[yd * dst_stride + xd] = src[(p->src_y + row) * src_stride + p->src_x + col];
}
}
}
@ -103,19 +119,24 @@ static void kaboom_render(struct kaboom_state *state)
static int kaboom_timer_cb(void *data)
{
struct kaboom_state *state = data;
state->frame++;
if (state->frame >= ANIM_FRAMES) {
if (state->alive_count <= 0) {
kaboom_destroy(state);
return 0;
}
/* Update piece positions */
/* Update piece positions (matches original X11 physics) */
for (int i = 0; i < state->npieces; i++) {
struct kaboom_piece *p = &state->pieces[i];
p->vy += GRAVITY;
p->x += p->vx;
p->y += p->vy;
if (!p->alive) continue;
p->px += p->vx;
p->py += p->vy;
p->vy++; /* gravity: 1 pixel/frame² */
int sx = p->px >> KAB_PRECISION;
if (sx < -state->width || sx > state->scr_w || p->py >= state->scr_h) {
p->alive = false;
state->alive_count--;
}
}
kaboom_render(state);
@ -126,7 +147,6 @@ static int kaboom_timer_cb(void *data)
void wl_do_kaboom(WScreen *scr, WNativeWindow win, int x, int y)
{
(void)scr;
struct wl_frame_buf *fb = frame_buf_find(win);
if (!fb || !fb->image || !wl_state.scene)
return;
@ -135,58 +155,70 @@ void wl_do_kaboom(WScreen *scr, WNativeWindow win, int x, int y)
int h = fb->height;
int cols = w / PIECE_SIZE;
int rows = h / PIECE_SIZE;
int npieces = cols * rows;
if (npieces <= 0)
if (cols <= 0 || rows <= 0)
return;
/* Allocate pieces (only ~50% used, matching original) */
int max_pieces = cols * rows;
struct kaboom_state *state = calloc(1, sizeof(*state));
if (!state) return;
state->pieces = calloc(npieces, sizeof(struct kaboom_piece));
state->pieces = calloc(max_pieces, sizeof(struct kaboom_piece));
if (!state->pieces) { free(state); return; }
state->npieces = npieces;
state->width = w;
state->height = h;
state->frame = 0;
state->scr_w = scr->scr_width;
state->scr_h = scr->scr_height;
state->origin_x = 0;
state->origin_y = 0;
/* Copy pixel data from icon into pieces */
/* Copy source icon pixels */
state->src_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, w, h, NULL, 0);
if (!state->src_image) { free(state->pieces); free(state); return; }
uint32_t *src = pixman_image_get_data(fb->image);
uint32_t *sdst = pixman_image_get_data(state->src_image);
int src_stride = pixman_image_get_stride(fb->image) / 4;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
struct kaboom_piece *p = &state->pieces[r * cols + c];
p->x = c * PIECE_SIZE;
p->y = r * PIECE_SIZE;
/* Random velocity: horizontal spread, slight upward */
p->vx = (rand() % 7) - 3;
p->vy = -(rand() % 4) - 1;
for (int py = 0; py < PIECE_SIZE; py++) {
for (int px = 0; px < PIECE_SIZE; px++) {
int sx = c * PIECE_SIZE + px;
int sy = r * PIECE_SIZE + py;
if (sx < w && sy < h)
p->pixels[py * PIECE_SIZE + px] = src[sy * src_stride + sx];
}
int sdst_stride = pixman_image_get_stride(state->src_image) / 4;
for (int row = 0; row < h; row++)
memcpy(sdst + row * sdst_stride, src + row * src_stride, w * 4);
/* Initialize pieces with original kaboom physics */
int k = 0;
for (int i = 0; i < cols && k < max_pieces; i++) {
for (int j = 0; j < rows && k < max_pieces; j++) {
if (rand() % 2) {
struct kaboom_piece *p = &state->pieces[k];
p->src_x = i * PIECE_SIZE;
p->src_y = j * PIECE_SIZE;
p->px = (x + i * PIECE_SIZE) << KAB_PRECISION;
p->py = y + j * PIECE_SIZE;
p->vx = rand() % (1 << (KAB_PRECISION + 3)) - (1 << (KAB_PRECISION + 3)) / 2;
p->vy = -15 - rand() % 7;
p->alive = true;
k++;
}
}
}
state->npieces = k;
state->alive_count = k;
/* Create scene buffer for animation */
state->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, w, h, NULL, 0);
if (!state->image) { free(state->pieces); free(state); return; }
/* Scene buffer covers full screen so pieces can fly anywhere */
state->image = pixman_image_create_bits(PIXMAN_a8r8g8b8,
state->scr_w, state->scr_h, NULL, 0);
if (!state->image) { kaboom_destroy(state); return; }
wlr_buffer_init(&state->base, &kaboom_buf_impl, w, h);
wlr_buffer_init(&state->base, &kaboom_buf_impl, state->scr_w, state->scr_h);
struct wlr_scene_tree *parent = wl_state.level_trees[WL_LAYER_OVERLAY]
? wl_state.level_trees[WL_LAYER_OVERLAY] : &wl_state.scene->tree;
state->tree = wlr_scene_tree_create(parent);
if (!state->tree) { kaboom_destroy(state); return; }
wlr_scene_node_set_position(&state->tree->node, x, y);
wlr_scene_node_set_position(&state->tree->node, 0, 0);
state->scene_buf = wlr_scene_buffer_create(state->tree, &state->base);
if (!state->scene_buf) { kaboom_destroy(state); return; }
/* Render first frame (original positions) and hide original icon */
/* Render first frame and hide original icon */
kaboom_render(state);
wlr_scene_buffer_set_buffer_with_damage(state->scene_buf, &state->base, NULL);
if (fb->tree)