diff --git a/README.md b/README.md index f813a5b..05c9d25 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,12 @@ library | lastest version | category | LoC | description **stb_herringbone_wa...** | 0.6 | game dev | 1220 | herringbone Wang tile map generator **stb_c_lexer.h** | 0.07 | parsing | 816 | simplify writing parsers for C-like languages **stb_divide.h** | 0.91 | math | 379 | more useful 32-bit modulus e.g. "euclidean divide" +**stb_connected_comp...** | 0.90 | misc | 653 | incrementally compute reachability on grids **stb.h** | 2.27 | misc | 14185 | helper functions for C, mostly redundant in C++; basically author's personal stuff **stb_leakcheck.h** | 0.2 | misc | 124 | quick-and-dirty malloc/free leak-checking -Total libraries: 18 -Total lines of C code: 46814 +Total libraries: 19 +Total lines of C code: 47467 FAQ diff --git a/stb_connected_components.h b/stb_connected_components.h new file mode 100644 index 0000000..7b91d42 --- /dev/null +++ b/stb_connected_components.h @@ -0,0 +1,653 @@ +// stb_connected_components - v0.90 - public domain connected components on grids +// http://github.com/nothings/stb +// +// Finds connected components on 2D grids for testing reachability between +// two points, with fast updates when changing reachability (e.g. on one machine +// it was typically 0.2ms w/ 1024x1024 grid). Each grid square must be "open" or +// "closed" (traversable or untraversable), and grid squares are only connected +// to their orthogonal neighbors, not diagonally. +// +// In one source file, create the implementation by doing something like this: +// +// #define STBCC_GRID_COUNT_X_LOG2 10 +// #define STBCC_GRID_COUNT_Y_LOG2 10 +// #define STB_CONNECTED_COMPONENTS_IMPLEMENTATION +// #include "stb_connected_components.h" +// +// The above creates an implementation that can run on maps up to 1024x1024. +// Map sizes must be a multiple of 32 on each axis. +// +// LICENSE +// +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// + +#ifndef INCLUDE_STB_CONNECTED_COMPONENTS_H +#define INCLUDE_STB_CONNECTED_COMPONENTS_H + +#include +#include + +typedef struct st_stbcc_grid stbcc_grid; + +////////////////////////////////////////////////////////////////////////////////////////// +// +// initialization +// + +// you allocate the grid data structure to this size (note that it will be very big!!!) +extern size_t stbcc_grid_sizeof(void); + +// initialize the grid, value of map[] is 0 = traversable, non-0 is solid +extern void stbcc_init_grid(stbcc_grid *g, unsigned char *map, int w, int h); + + +////////////////////////////////////////////////////////////////////////////////////////// +// +// main functionality +// + +// update a grid square state, 0 = traversable, non-0 is solid +// i can add a batch-update if it's needed +extern void stbcc_update_grid(stbcc_grid *g, int x, int y, int solid); + +// query if two grid squares are reachable from each other +extern int stbcc_query_grid_node_connection(stbcc_grid *g, int x1, int y1, int x2, int y2); + + +////////////////////////////////////////////////////////////////////////////////////////// +// +// bonus functions +// + +// query the grid data structure for whether a given square is open or not +extern int stbcc_query_grid_open(stbcc_grid *g, int x, int y); + +// get a unique id for the connected component this is in; it's not necessarily +// small, you'll need a hash table or something to remap it (or just use +extern unsigned int stbcc_get_unique_id(stbcc_grid *g, int x, int y); + +#endif // INCLUDE_STB_CONNECTED_COMPONENTS_H + +#ifdef STB_CONNECTED_COMPONENTS_IMPLEMENTATION + +#if !defined(STBCC_GRID_COUNT_X_LOG2) || !defined(STBCC_GRID_COUNT_Y_LOG2) + #error "You must define STBCC_GRID_COUNT_X_LOG2 and STBCC_GRID_COUNT_Y_LOG2 to define the max grid supported." +#endif + +#define STBCC__GRID_COUNT_X (1 << STBCC_GRID_COUNT_X_LOG2) +#define STBCC__GRID_COUNT_Y (1 << STBCC_GRID_COUNT_Y_LOG2) + +#define STBCC__MAP_STRIDE (1 << (STBCC_GRID_COUNT_X_LOG2-3)) + +#ifndef STBCC_CLUSTER_SIZE_X_LOG2 +#define STBCC_CLUSTER_SIZE_X_LOG2 5 +#endif + +#ifndef STBCC_CLUSTER_SIZE_Y_LOG2 +#define STBCC_CLUSTER_SIZE_Y_LOG2 5 +#endif + +#define STBCC__CLUSTER_SIZE_X (1 << STBCC_CLUSTER_SIZE_X_LOG2) +#define STBCC__CLUSTER_SIZE_Y (1 << STBCC_CLUSTER_SIZE_Y_LOG2) + +#define STBCC__CLUSTER_COUNT_X_LOG2 (STBCC_GRID_COUNT_X_LOG2 - STBCC_CLUSTER_SIZE_X_LOG2) +#define STBCC__CLUSTER_COUNT_Y_LOG2 (STBCC_GRID_COUNT_Y_LOG2 - STBCC_CLUSTER_SIZE_Y_LOG2) + +#define STBCC__CLUSTER_COUNT_X (1 << STBCC__CLUSTER_COUNT_X_LOG2) +#define STBCC__CLUSTER_COUNT_Y (1 << STBCC__CLUSTER_COUNT_Y_LOG2) + +#if STBCC__CLUSTER_SIZE_X >= STBCC__GRID_COUNT_X || STBCC__CLUSTER_SIZE_Y >= STBCC__GRID_COUNT_Y + #error "STBCC_CLUSTER_SIZE_X/Y_LOG2 must be smaller than STBCC_GRID_COUNT_X/Y_LOG2" +#endif + +// worst case # of clumps per cluster +#define STBCC__MAX_CLUMPS_PER_CLUSTER_LOG2 (STBCC_CLUSTER_SIZE_X_LOG2 + STBCC_CLUSTER_SIZE_Y_LOG2-1) +#define STBCC__MAX_CLUMPS_PER_CLUSTER (1 << STBCC__MAX_CLUMPS_PER_CLUSTER_LOG2) +#define STBCC__MAX_CLUMPS (STBCC__MAX_CLUMPS_PER_CLUSTER * STBCC__CLUSTER_COUNT_X * STBCC__CLUSTER_COUNT_Y) +#define STBCC__NULL_CLUMPID STBCC__MAX_CLUMPS_PER_CLUSTER + +#define STBCC__CLUSTER_X_FOR_COORD_X(x) ((x) >> STBCC_CLUSTER_SIZE_X_LOG2) +#define STBCC__CLUSTER_Y_FOR_COORD_Y(y) ((y) >> STBCC_CLUSTER_SIZE_Y_LOG2) + +#define STBCC__MAP_BYTE_MASK(x,y) (1 << ((x) & 7)) +#define STBCC__MAP_BYTE(g,x,y) ((g)->map[y][(x) >> 3]) +#define STBCC__MAP_OPEN(g,x,y) (STBCC__MAP_BYTE(g,x,y) & STBCC__MAP_BYTE_MASK(x,y)) + +typedef unsigned short stbcc__clumpid; +typedef unsigned char stbcc__verify_max_clumps[STBCC__MAX_CLUMPS_PER_CLUSTER < (1 << (8*sizeof(stbcc__clumpid))) ? 1 : -1]; + +#define STBCC__MAX_EXITS_PER_CLUMP (STBCC__CLUSTER_SIZE_X + STBCC__CLUSTER_SIZE_Y) // 64 +// 2^19 * 2^6 => 2^25 exits => 2^26 => 64MB for 1024x1024 + +typedef unsigned char stbcc__verify_max_exits[STBCC__MAX_EXITS_PER_CLUMP <= 256]; + +typedef struct +{ + unsigned short clump_index:12; + signed short cluster_dx:2; + signed short cluster_dy:2; +} stbcc__relative_clumpid; + +typedef union +{ + struct { + unsigned int clump_index:12; + unsigned int cluster_x:10; + unsigned int cluster_y:10; + } f; + unsigned int c; +} stbcc__global_clumpid; + +// rebuilt cluster 3,4 + +// what changes in cluster 2,4 + +typedef struct +{ + stbcc__global_clumpid global_label; + unsigned char num_adjacent; + stbcc__relative_clumpid adjacent_clumps[STBCC__MAX_EXITS_PER_CLUMP]; +} stbcc__clump; + +typedef struct +{ + unsigned int num_clumps; + stbcc__clump clump[STBCC__MAX_CLUMPS_PER_CLUSTER]; +} stbcc__cluster; + +struct st_stbcc_grid +{ + int w,h,cw,ch; + unsigned char map[STBCC__GRID_COUNT_Y][STBCC__MAP_STRIDE]; // 1K x 1K => 1K x 128 => 128KB + stbcc__clumpid clump_for_node[STBCC__GRID_COUNT_Y][STBCC__GRID_COUNT_X]; // 1K x 1K x 2 = 2MB + stbcc__cluster cluster[STBCC__CLUSTER_COUNT_Y][STBCC__CLUSTER_COUNT_X]; // 1K x 1K x 0.5 x 64 x 2 = 64MB +}; + +int stbcc_query_grid_node_connection(stbcc_grid *g, int x1, int y1, int x2, int y2) +{ + stbcc__global_clumpid label1, label2; + stbcc__clumpid c1 = g->clump_for_node[y1][x1]; + stbcc__clumpid c2 = g->clump_for_node[y2][x2]; + int cx1 = STBCC__CLUSTER_X_FOR_COORD_X(x1); + int cy1 = STBCC__CLUSTER_Y_FOR_COORD_Y(y1); + int cx2 = STBCC__CLUSTER_X_FOR_COORD_X(x2); + int cy2 = STBCC__CLUSTER_Y_FOR_COORD_Y(y2); + if (c1 == STBCC__NULL_CLUMPID || c2 == STBCC__NULL_CLUMPID) + return 0; + label1 = g->cluster[cy1][cx1].clump[c1].global_label; + label2 = g->cluster[cy2][cx2].clump[c2].global_label; + if (label1.c == label2.c) + return 1; + return 0; +} + +int stbcc_query_grid_open(stbcc_grid *g, int x, int y) +{ + return STBCC__MAP_OPEN(g, x, y) != 0; +} + +unsigned int stbcc_get_unique_id(stbcc_grid *g, int x, int y) +{ + stbcc__clumpid c = g->clump_for_node[y][x]; + int cx = STBCC__CLUSTER_X_FOR_COORD_X(x); + int cy = STBCC__CLUSTER_Y_FOR_COORD_Y(y); + return g->cluster[cy][cx].clump[c].global_label.c; +} + +typedef struct +{ + unsigned char x,y; +} stbcc__tinypoint; + +typedef struct +{ + stbcc__tinypoint parent[STBCC__CLUSTER_SIZE_Y][STBCC__CLUSTER_SIZE_X]; // 32x32 => 2KB + stbcc__clumpid label[STBCC__CLUSTER_SIZE_Y][STBCC__CLUSTER_SIZE_X]; +} stbcc__cluster_build_info; + +static void stbcc__build_clumps_for_cluster(stbcc_grid *g, int cx, int cy); +static void stbcc__remove_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy); +static void stbcc__add_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy); + +static stbcc__global_clumpid stbcc__clump_find(stbcc_grid *g, stbcc__global_clumpid n) +{ + stbcc__global_clumpid q; + stbcc__clump *c = &g->cluster[n.f.cluster_y][n.f.cluster_x].clump[n.f.clump_index]; + + if (c->global_label.c == n.c) + return n; + + q = stbcc__clump_find(g, c->global_label); + c->global_label = q; + return q; +} + +typedef struct +{ + unsigned int cluster_x; + unsigned int cluster_y; + unsigned int clump_index; +} stbcc__unpacked_clumpid; + +// @OPTIMIZE: pass in these parameters unpacked, not packed +static void stbcc__clump_union(stbcc_grid *g, stbcc__unpacked_clumpid m, int x, int y, int idx) +{ + stbcc__clump *mc = &g->cluster[m.cluster_y][m.cluster_x].clump[m.clump_index]; + stbcc__clump *nc = &g->cluster[y][x].clump[idx]; + stbcc__global_clumpid mp = stbcc__clump_find(g, mc->global_label); + stbcc__global_clumpid np = stbcc__clump_find(g, nc->global_label); + + if (mp.c == np.c) + return; + + g->cluster[mp.f.cluster_y][mp.f.cluster_x].clump[mp.f.clump_index].global_label = np; +} + +static void stbcc__build_connected_components_for_clumps(stbcc_grid *g) +{ + int i,j,k,h; + + for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j) { + for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i) { + stbcc__cluster *cluster = &g->cluster[j][i]; + for (k=0; k < (int) cluster->num_clumps; ++k) { + stbcc__global_clumpid m; + m.f.clump_index = k; + m.f.cluster_x = i; + m.f.cluster_y = j; + assert((int) m.f.clump_index == k && (int) m.f.cluster_x == i && (int) m.f.cluster_y == j); + cluster->clump[k].global_label = m; + } + } + } + + for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j) { + for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i) { + stbcc__cluster *cluster = &g->cluster[j][i]; + for (k=0; k < (int) cluster->num_clumps; ++k) { + stbcc__clump *clump = &cluster->clump[k]; + stbcc__unpacked_clumpid m; + m.clump_index = k; + m.cluster_x = i; + m.cluster_y = j; + for (h=0; h < clump->num_adjacent; ++h) { + unsigned int clump_index = clump->adjacent_clumps[h].clump_index; + unsigned int x = clump->adjacent_clumps[h].cluster_dx + i; + unsigned int y = clump->adjacent_clumps[h].cluster_dy + j; + stbcc__clump_union(g, m, x, y, clump_index); + } + } + } + } + + for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j) { + for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i) { + stbcc__cluster *cluster = &g->cluster[j][i]; + for (k=0; k < (int) cluster->num_clumps; ++k) { + stbcc__global_clumpid m; + m.f.clump_index = k; + m.f.cluster_x = i; + m.f.cluster_y = j; + stbcc__clump_find(g, m); + } + } + } +} + +void stbcc_update_grid(stbcc_grid *g, int x, int y, int solid) +{ + int cx,cy; + + if (!solid) { + if (STBCC__MAP_OPEN(g,x,y)) + return; + } else { + if (!STBCC__MAP_OPEN(g,x,y)) + return; + } + + cx = STBCC__CLUSTER_X_FOR_COORD_X(x); + cy = STBCC__CLUSTER_Y_FOR_COORD_Y(y); + + stbcc__remove_connections_to_adjacent_cluster(g, cx-1, cy, 1, 0); + stbcc__remove_connections_to_adjacent_cluster(g, cx+1, cy, -1, 0); + stbcc__remove_connections_to_adjacent_cluster(g, cx, cy-1, 0, 1); + stbcc__remove_connections_to_adjacent_cluster(g, cx, cy+1, 0,-1); + + if (!solid) + STBCC__MAP_BYTE(g,x,y) |= STBCC__MAP_BYTE_MASK(x,y); + else + STBCC__MAP_BYTE(g,x,y) &= ~STBCC__MAP_BYTE_MASK(x,y); + + stbcc__build_clumps_for_cluster(g, cx, cy); + + stbcc__add_connections_to_adjacent_cluster(g, cx, cy, -1, 0); + stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 1, 0); + stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 0,-1); + stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 0, 1); + + stbcc__add_connections_to_adjacent_cluster(g, cx-1, cy, 1, 0); + stbcc__add_connections_to_adjacent_cluster(g, cx+1, cy, -1, 0); + stbcc__add_connections_to_adjacent_cluster(g, cx, cy-1, 0, 1); + stbcc__add_connections_to_adjacent_cluster(g, cx, cy+1, 0,-1); + + stbcc__build_connected_components_for_clumps(g); +} + +size_t stbcc_grid_sizeof(void) +{ + return sizeof(stbcc_grid); +} + +void stbcc_init_grid(stbcc_grid *g, unsigned char *map, int w, int h) +{ + int i,j,k; + assert(w % STBCC__CLUSTER_SIZE_X == 0); + assert(h % STBCC__CLUSTER_SIZE_Y == 0); + assert(w % 8 == 0); + + g->w = w; + g->h = h; + g->cw = w >> STBCC_CLUSTER_SIZE_X_LOG2; + g->ch = h >> STBCC_CLUSTER_SIZE_Y_LOG2; + + for (j=0; j < h; ++j) { + for (i=0; i < w; i += 8) { + unsigned char c = 0; + for (k=0; k < 8; ++k) + if (map[j*w + (i+k)] == 0) + c |= (1 << k); + g->map[j][i>>3] = c; + } + } + + for (j=0; j < g->ch; ++j) + for (i=0; i < g->cw; ++i) + stbcc__build_clumps_for_cluster(g, i, j); + + for (j=0; j < g->ch; ++j) { + for (i=0; i < g->cw; ++i) { + stbcc__add_connections_to_adjacent_cluster(g, i, j, -1, 0); + stbcc__add_connections_to_adjacent_cluster(g, i, j, 1, 0); + stbcc__add_connections_to_adjacent_cluster(g, i, j, 0,-1); + stbcc__add_connections_to_adjacent_cluster(g, i, j, 0, 1); + } + } + + stbcc__build_connected_components_for_clumps(g); + + for (j=0; j < g->h; ++j) + for (i=0; i < g->w; ++i) + assert(g->clump_for_node[j][i] <= STBCC__NULL_CLUMPID); +} + + +static void stbcc__add_clump_connection(stbcc_grid *g, int x1, int y1, int x2, int y2) +{ + stbcc__clump *clump; + + int cx1 = STBCC__CLUSTER_X_FOR_COORD_X(x1); + int cy1 = STBCC__CLUSTER_Y_FOR_COORD_Y(y1); + int cx2 = STBCC__CLUSTER_X_FOR_COORD_X(x2); + int cy2 = STBCC__CLUSTER_Y_FOR_COORD_Y(y2); + + stbcc__clumpid c1 = g->clump_for_node[y1][x1]; + stbcc__clumpid c2 = g->clump_for_node[y2][x2]; + + stbcc__relative_clumpid rc; + + assert(cx1 != cx2 || cy1 != cy2); + assert(abs(cx1-cx2) + abs(cy1-cy2) == 1); + + // add connection to c2 in c1 + + rc.clump_index = c2; + rc.cluster_dx = x2-x1; + rc.cluster_dy = y2-y1; + + clump = &g->cluster[cy1][cx1].clump[c1]; + assert(clump->num_adjacent < STBCC__MAX_EXITS_PER_CLUMP); + clump->adjacent_clumps[clump->num_adjacent++] = rc; +} + +static void stbcc__remove_clump_connection(stbcc_grid *g, int x1, int y1, int x2, int y2) +{ + stbcc__clump *clump; + int i; + + int cx1 = STBCC__CLUSTER_X_FOR_COORD_X(x1); + int cy1 = STBCC__CLUSTER_Y_FOR_COORD_Y(y1); + int cx2 = STBCC__CLUSTER_X_FOR_COORD_X(x2); + int cy2 = STBCC__CLUSTER_Y_FOR_COORD_Y(y2); + + stbcc__clumpid c1 = g->clump_for_node[y1][x1]; + stbcc__clumpid c2 = g->clump_for_node[y2][x2]; + + stbcc__relative_clumpid rc; + + assert(cx1 != cx2 || cy1 != cy2); + assert(abs(cx1-cx2) + abs(cy1-cy2) == 1); + + // add connection to c2 in c1 + + rc.clump_index = c2; + rc.cluster_dx = x2-x1; + rc.cluster_dy = y2-y1; + + clump = &g->cluster[cy1][cx1].clump[c1]; + + for (i=0; i < clump->num_adjacent; ++i) + if (rc.clump_index == clump->adjacent_clumps[i].clump_index && + rc.cluster_dx == clump->adjacent_clumps[i].cluster_dx && + rc.cluster_dy == clump->adjacent_clumps[i].cluster_dy) + break; + + if (i < clump->num_adjacent) + clump->adjacent_clumps[i] = clump->adjacent_clumps[--clump->num_adjacent]; + else + assert(0); +} + +static void stbcc__add_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy) +{ + unsigned char connected[STBCC__MAX_CLUMPS_PER_CLUSTER/8] = { 0 }; + int x = cx * STBCC__CLUSTER_SIZE_X; + int y = cy * STBCC__CLUSTER_SIZE_Y; + int step_x, step_y=0, i, j, k, n; + + if (cx < 0 || cx >= g->cw || cy < 0 || cy >= g->ch) + return; + + if (cx+dx < 0 || cx+dx >= g->cw || cy+dy < 0 || cy+dy >= g->ch) + return; + + assert(abs(dx) + abs(dy) == 1); + + if (dx == 1) { + i = STBCC__CLUSTER_SIZE_X-1; + j = 0; + step_x = 0; + step_y = 1; + n = STBCC__CLUSTER_SIZE_Y; + } else if (dx == -1) { + i = 0; + j = 0; + step_x = 0; + step_y = 1; + n = STBCC__CLUSTER_SIZE_Y; + } else if (dy == -1) { + i = 0; + j = 0; + step_x = 1; + step_y = 0; + n = STBCC__CLUSTER_SIZE_X; + } else if (dy == 1) { + i = 0; + j = STBCC__CLUSTER_SIZE_Y-1; + step_x = 1; + step_y = 0; + n = STBCC__CLUSTER_SIZE_X; + } else { + assert(0); + } + + for (k=0; k < n; ++k) { + if (STBCC__MAP_OPEN(g, x+i, y+j) && STBCC__MAP_OPEN(g, x+i+dx, y+j+dy)) { + stbcc__clumpid c = g->clump_for_node[y+j+dy][x+i+dx]; + if (0 == (connected[c>>3] & (1 << (c & 7)))) { + connected[c>>3] |= 1 << (c & 7); + stbcc__add_clump_connection(g, x+i, y+j, x+i+dx, y+j+dy); + } + } + i += step_x; + j += step_y; + } +} + +static void stbcc__remove_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy) +{ + unsigned char disconnected[STBCC__MAX_CLUMPS_PER_CLUSTER/8] = { 0 }; + int x = cx * STBCC__CLUSTER_SIZE_X; + int y = cy * STBCC__CLUSTER_SIZE_Y; + int step_x, step_y=0, i, j, k, n; + + if (cx < 0 || cx >= g->cw || cy < 0 || cy >= g->ch) + return; + + if (cx+dx < 0 || cx+dx >= g->cw || cy+dy < 0 || cy+dy >= g->ch) + return; + + assert(abs(dx) + abs(dy) == 1); + + if (dx == 1) { + i = STBCC__CLUSTER_SIZE_X-1; + j = 0; + step_x = 0; + step_y = 1; + n = STBCC__CLUSTER_SIZE_Y; + } else if (dx == -1) { + i = 0; + j = 0; + step_x = 0; + step_y = 1; + n = STBCC__CLUSTER_SIZE_Y; + } else if (dy == -1) { + i = 0; + j = 0; + step_x = 1; + step_y = 0; + n = STBCC__CLUSTER_SIZE_X; + } else if (dy == 1) { + i = 0; + j = STBCC__CLUSTER_SIZE_Y-1; + step_x = 1; + step_y = 0; + n = STBCC__CLUSTER_SIZE_X; + } else { + assert(0); + } + + for (k=0; k < n; ++k) { + if (STBCC__MAP_OPEN(g, x+i, y+j) && STBCC__MAP_OPEN(g, x+i+dx, y+j+dy)) { + stbcc__clumpid c = g->clump_for_node[y+j+dy][x+i+dx]; + if (0 == (disconnected[c>>3] & (1 << (c & 7)))) { + disconnected[c>>3] |= 1 << (c & 7); + stbcc__remove_clump_connection(g, x+i, y+j, x+i+dx, y+j+dy); + } + } + i += step_x; + j += step_y; + } +} + +static stbcc__tinypoint stbcc__incluster_find(stbcc__cluster_build_info *cbi, int x, int y) +{ + stbcc__tinypoint p,q; + p = cbi->parent[y][x]; + if (p.x == x && p.y == y) + return p; + q = stbcc__incluster_find(cbi, p.x, p.y); + cbi->parent[y][x] = q; + return q; +} + +static void stbcc__incluster_union(stbcc__cluster_build_info *cbi, int x1, int y1, int x2, int y2) +{ + stbcc__tinypoint p = stbcc__incluster_find(cbi, x1,y1); + stbcc__tinypoint q = stbcc__incluster_find(cbi, x2,y2); + + if (p.x == q.x && p.y == q.y) + return; + + cbi->parent[p.y][p.x] = q; +} + +static void stbcc__build_clumps_for_cluster(stbcc_grid *g, int cx, int cy) +{ + stbcc__cluster *c; + stbcc__cluster_build_info cbi; + int label=0; + int i,j; + int x = cx * STBCC__CLUSTER_SIZE_X; + int y = cy * STBCC__CLUSTER_SIZE_Y; + + // set initial disjoint set forest state + for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) { + for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) { + cbi.parent[j][i].x = i; + cbi.parent[j][i].y = j; + } + } + + // join all sets that are connected + for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) { + // check down only if not on bottom row + if (j < STBCC__CLUSTER_SIZE_Y-1) + for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) + if (STBCC__MAP_OPEN(g,x+i,y+j) && STBCC__MAP_OPEN(g,x+i ,y+j+1)) + stbcc__incluster_union(&cbi, i,j, i,j+1); + // check right for everything but rightmost column + for (i=0; i < STBCC__CLUSTER_SIZE_X-1; ++i) + if (STBCC__MAP_OPEN(g,x+i,y+j) && STBCC__MAP_OPEN(g,x+i+1,y+j )) + stbcc__incluster_union(&cbi, i,j, i+1,j); + } + + // label all non-empty leaders + for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) { + for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) { + stbcc__tinypoint p = cbi.parent[j][i]; + if (p.x == i && p.y == j) + if (STBCC__MAP_OPEN(g,x+i,y+j)) + cbi.label[j][i] = label++; + else + cbi.label[j][i] = STBCC__NULL_CLUMPID; + } + } + + // label all other nodes + for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) { + for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) { + stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j); + if (p.x != i || p.y != j) { + if (STBCC__MAP_OPEN(g,x+i,y+j)) + cbi.label[j][i] = cbi.label[p.y][p.x]; + } + } + } + + c = &g->cluster[cy][cx]; + c->num_clumps = label; + for (i=0; i < label; ++i) + c->clump[i].num_adjacent = 0; + + for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) + for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) { + g->clump_for_node[y+j][x+i] = cbi.label[j][i]; // @OPTIMIZE: remove cbi.label entirely + assert(g->clump_for_node[y+j][x+i] <= STBCC__NULL_CLUMPID); + } +} + +#endif // STB_CONNECTED_COMPONENTS_IMPLEMENTATION diff --git a/tools/README.list b/tools/README.list index 5ad58a1..b5c3e5b 100644 --- a/tools/README.list +++ b/tools/README.list @@ -14,5 +14,6 @@ stb_tilemap_editor.h | game dev | embeddable tilemap editor stb_herringbone_wang_tile.h | game dev | herringbone Wang tile map generator stb_c_lexer.h | parsing | simplify writing parsers for C-like languages stb_divide.h | math | more useful 32-bit modulus e.g. "euclidean divide" +stb_connected_components.h | misc | incrementally compute reachability on grids stb.h | misc | helper functions for C, mostly redundant in C++; basically author's personal stuff stb_leakcheck.h | misc | quick-and-dirty malloc/free leak-checking