mirror of
https://github.com/nothings/stb
synced 2024-12-16 04:42:42 +03:00
untested simple batching;
document algorithm; automatically use sqrt(N) instead of 32 as cluster size
This commit is contained in:
parent
1392344cdd
commit
0214a3c71f
@ -24,18 +24,49 @@
|
|||||||
// publish, and distribute this file as you see fit.
|
// publish, and distribute this file as you see fit.
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
// CHANGELOG
|
||||||
|
//
|
||||||
|
// 0.92 (2016-04-16) Compute sqrt(N) cluster size by default
|
||||||
|
// 0.91 (2016-04-15) Initial release
|
||||||
|
//
|
||||||
// TODO:
|
// TODO:
|
||||||
// - test C++ compile
|
|
||||||
// - better API documentation
|
// - better API documentation
|
||||||
// - internals documentation (including algorithm)
|
// - more comments
|
||||||
// - try re-integrating naive algorithm & compare performance
|
// - try re-integrating naive algorithm & compare performance
|
||||||
// - batching (keep data structure w/ dirty clusters)
|
// - more optimized batching (current approach still recomputes clumps many times)
|
||||||
// - function for setting a grid of squares at once (just use batching)
|
// - function for setting a grid of squares at once (just use batching)
|
||||||
// - shrink data by storing only, say, 2X max exits
|
// - shrink data by storing only, say, 2X max exits
|
||||||
// (instead of max exits per clump), and repack cluster
|
// (instead of max exits per clump), and repack cluster
|
||||||
// if it runs out (possibly by just rebuilding from scratch,
|
// if it runs out (possibly by just rebuilding from scratch,
|
||||||
// could even use dirty-cluster data structure)
|
// could even use dirty-cluster data structure)
|
||||||
// should reduce 1Kx1K from ~66MB to ~8MB
|
// should reduce 1Kx1K from ~66MB to ~8MB
|
||||||
|
//
|
||||||
|
// ALGORITHM
|
||||||
|
//
|
||||||
|
// The NxN grid map is split into sqrt(N) x sqrt(N) blocks called
|
||||||
|
// "clusters". Each cluster independently computes a set of connected
|
||||||
|
// components within that cluster (ignoring all connectivity out of
|
||||||
|
// that cluster) using a union-find disjoint set forest. This produces a bunch
|
||||||
|
// of locally connected components called "clumps". Each clump is (a) connected
|
||||||
|
// within its cluster, (b) does not directly connect to any other clumps in the
|
||||||
|
// cluster (though it may connect to them by paths that lead outside the cluster,
|
||||||
|
// but those are ignored at this step), and (c) maintains an adjacency list of
|
||||||
|
// all clumps in adjacent clusters that it _is_ connected to. Then a second
|
||||||
|
// union-find disjoint set forest is used to compute connected clumps
|
||||||
|
// globally, across the whole map. Reachability is then computed by
|
||||||
|
// finding which clump each input point belongs to, and checking whether
|
||||||
|
// those clumps are in the same "global" connected component.
|
||||||
|
//
|
||||||
|
// The above data structure can be updated efficiently; on a change
|
||||||
|
// of a single grid square on the map, only one cluster changes its
|
||||||
|
// purely-local state, so only one cluster needs its clumps fully
|
||||||
|
// recomputed. Clumps in adjacent clusters need their adjacency lists
|
||||||
|
// updated: first to remove all references to the old clumps in the
|
||||||
|
// rebuilt cluster, then to add new references to the new clumps. Both
|
||||||
|
// of these operations can use the existing "find which clump each input
|
||||||
|
// point belongs to" query to compute that adjacency information rapidly.
|
||||||
|
// In one 1024x1024 test on a specific machine, a one-tile update was
|
||||||
|
// about 250 times faster than a full disjoint-set-forest on the full map.
|
||||||
|
|
||||||
#ifndef INCLUDE_STB_CONNECTED_COMPONENTS_H
|
#ifndef INCLUDE_STB_CONNECTED_COMPONENTS_H
|
||||||
#define INCLUDE_STB_CONNECTED_COMPONENTS_H
|
#define INCLUDE_STB_CONNECTED_COMPONENTS_H
|
||||||
@ -79,12 +110,18 @@ extern int stbcc_query_grid_node_connection(stbcc_grid *g, int x1, int y1, int x
|
|||||||
// bonus functions
|
// bonus functions
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// wrap multiple stbcc_update_grid calls in these function to compute
|
||||||
|
// multiple updates more efficiently; cannot make queries inside batch
|
||||||
|
extern void stbcc_update_batch_begin(stbcc_grid *g);
|
||||||
|
extern void stbcc_update_batch_end(stbcc_grid *g);
|
||||||
|
|
||||||
// query the grid data structure for whether a given square is open or not
|
// 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);
|
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
|
// 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
|
// 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);
|
extern unsigned int stbcc_get_unique_id(stbcc_grid *g, int x, int y);
|
||||||
|
#define STBCC_NULL_UNIQUE_ID 0xffffffff // returned for closed map squares
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
@ -104,11 +141,19 @@ extern unsigned int stbcc_get_unique_id(stbcc_grid *g, int x, int y);
|
|||||||
#define STBCC__MAP_STRIDE (1 << (STBCC_GRID_COUNT_X_LOG2-3))
|
#define STBCC__MAP_STRIDE (1 << (STBCC_GRID_COUNT_X_LOG2-3))
|
||||||
|
|
||||||
#ifndef STBCC_CLUSTER_SIZE_X_LOG2
|
#ifndef STBCC_CLUSTER_SIZE_X_LOG2
|
||||||
#define STBCC_CLUSTER_SIZE_X_LOG2 5
|
#define STBCC_CLUSTER_SIZE_X_LOG2 (STBCC_GRID_COUNT_X_LOG2/2) // log2(sqrt(2^N)) = 1/2 * log2(2^N)) = 1/2 * N
|
||||||
|
#if STBCC_CLUSTER_SIZE_X_LOG2 > 6
|
||||||
|
#undef STBCC_CLUSTER_SIZE_X_LOG2
|
||||||
|
#define STBCC_CLUSTER_SIZE_X_LOG2 6
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef STBCC_CLUSTER_SIZE_Y_LOG2
|
#ifndef STBCC_CLUSTER_SIZE_Y_LOG2
|
||||||
#define STBCC_CLUSTER_SIZE_Y_LOG2 5
|
#define STBCC_CLUSTER_SIZE_Y_LOG2 (STBCC_GRID_COUNT_Y_LOG2/2)
|
||||||
|
#if STBCC_CLUSTER_SIZE_Y_LOG2 > 6
|
||||||
|
#undef STBCC_CLUSTER_SIZE_Y_LOG2
|
||||||
|
#define STBCC_CLUSTER_SIZE_Y_LOG2 6
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define STBCC__CLUSTER_SIZE_X (1 << STBCC_CLUSTER_SIZE_X_LOG2)
|
#define STBCC__CLUSTER_SIZE_X (1 << STBCC_CLUSTER_SIZE_X_LOG2)
|
||||||
@ -182,6 +227,8 @@ typedef struct
|
|||||||
struct st_stbcc_grid
|
struct st_stbcc_grid
|
||||||
{
|
{
|
||||||
int w,h,cw,ch;
|
int w,h,cw,ch;
|
||||||
|
int in_batched_update;
|
||||||
|
//unsigned char cluster_dirty[STBCC__CLUSTER_COUNT_Y][STBCC__CLUSTER_COUNT_X]; // could bitpack, but: 1K x 1K => 1KB
|
||||||
unsigned char map[STBCC__GRID_COUNT_Y][STBCC__MAP_STRIDE]; // 1K x 1K => 1K x 128 => 128KB
|
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__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
|
stbcc__cluster cluster[STBCC__CLUSTER_COUNT_Y][STBCC__CLUSTER_COUNT_X]; // 1K x 1K x 0.5 x 64 x 2 = 64MB
|
||||||
@ -196,6 +243,7 @@ int stbcc_query_grid_node_connection(stbcc_grid *g, int x1, int y1, int x2, int
|
|||||||
int cy1 = STBCC__CLUSTER_Y_FOR_COORD_Y(y1);
|
int cy1 = STBCC__CLUSTER_Y_FOR_COORD_Y(y1);
|
||||||
int cx2 = STBCC__CLUSTER_X_FOR_COORD_X(x2);
|
int cx2 = STBCC__CLUSTER_X_FOR_COORD_X(x2);
|
||||||
int cy2 = STBCC__CLUSTER_Y_FOR_COORD_Y(y2);
|
int cy2 = STBCC__CLUSTER_Y_FOR_COORD_Y(y2);
|
||||||
|
assert(!g->in_batched_update);
|
||||||
if (c1 == STBCC__NULL_CLUMPID || c2 == STBCC__NULL_CLUMPID)
|
if (c1 == STBCC__NULL_CLUMPID || c2 == STBCC__NULL_CLUMPID)
|
||||||
return 0;
|
return 0;
|
||||||
label1 = g->cluster[cy1][cx1].clump[c1].global_label;
|
label1 = g->cluster[cy1][cx1].clump[c1].global_label;
|
||||||
@ -215,6 +263,8 @@ unsigned int stbcc_get_unique_id(stbcc_grid *g, int x, int y)
|
|||||||
stbcc__clumpid c = g->clump_for_node[y][x];
|
stbcc__clumpid c = g->clump_for_node[y][x];
|
||||||
int cx = STBCC__CLUSTER_X_FOR_COORD_X(x);
|
int cx = STBCC__CLUSTER_X_FOR_COORD_X(x);
|
||||||
int cy = STBCC__CLUSTER_Y_FOR_COORD_Y(y);
|
int cy = STBCC__CLUSTER_Y_FOR_COORD_Y(y);
|
||||||
|
assert(!g->in_batched_update);
|
||||||
|
if (c == STBCC__NULL_CLUMPID) return STBCC_NULL_UNIQUE_ID;
|
||||||
return g->cluster[cy][cx].clump[c].global_label.c;
|
return g->cluster[cy][cx].clump[c].global_label.c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,7 +405,25 @@ void stbcc_update_grid(stbcc_grid *g, int x, int y, int solid)
|
|||||||
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__add_connections_to_adjacent_cluster(g, cx, cy+1, 0,-1);
|
stbcc__add_connections_to_adjacent_cluster(g, cx, cy+1, 0,-1);
|
||||||
|
|
||||||
|
if (!g->in_batched_update)
|
||||||
stbcc__build_connected_components_for_clumps(g);
|
stbcc__build_connected_components_for_clumps(g);
|
||||||
|
#if 0
|
||||||
|
else
|
||||||
|
g->cluster_dirty[cy][cx] = 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void stbcc_update_batch_begin(stbcc_grid *g)
|
||||||
|
{
|
||||||
|
assert(!g->in_batched_update);
|
||||||
|
g->in_batched_update = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stbcc_update_batch_end(stbcc_grid *g)
|
||||||
|
{
|
||||||
|
assert(g->in_batched_update);
|
||||||
|
g->in_batched_update = 0;
|
||||||
|
stbcc__build_connected_components_for_clumps(g); // @OPTIMIZE: only do this if update was non-empty
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t stbcc_grid_sizeof(void)
|
size_t stbcc_grid_sizeof(void)
|
||||||
@ -374,6 +442,13 @@ void stbcc_init_grid(stbcc_grid *g, unsigned char *map, int w, int h)
|
|||||||
g->h = h;
|
g->h = h;
|
||||||
g->cw = w >> STBCC_CLUSTER_SIZE_X_LOG2;
|
g->cw = w >> STBCC_CLUSTER_SIZE_X_LOG2;
|
||||||
g->ch = h >> STBCC_CLUSTER_SIZE_Y_LOG2;
|
g->ch = h >> STBCC_CLUSTER_SIZE_Y_LOG2;
|
||||||
|
g->in_batched_update = 0;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j)
|
||||||
|
for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i)
|
||||||
|
g->cluster_dirty[j][i] = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
for (j=0; j < h; ++j) {
|
for (j=0; j < h; ++j) {
|
||||||
for (i=0; i < w; i += 8) {
|
for (i=0; i < w; i += 8) {
|
||||||
|
@ -106,6 +106,10 @@ SOURCE=..\stb_c_lexer.h
|
|||||||
# End Source File
|
# End Source File
|
||||||
# Begin Source File
|
# Begin Source File
|
||||||
|
|
||||||
|
SOURCE=..\stb_connected_components.h
|
||||||
|
# End Source File
|
||||||
|
# Begin Source File
|
||||||
|
|
||||||
SOURCE=..\stb_divide.h
|
SOURCE=..\stb_divide.h
|
||||||
# End Source File
|
# End Source File
|
||||||
# Begin Source File
|
# Begin Source File
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
#define STB_RECT_PACK_IMPLEMENTATION
|
#define STB_RECT_PACK_IMPLEMENTATION
|
||||||
#define STB_VOXEL_RENDER_IMPLEMENTATION
|
#define STB_VOXEL_RENDER_IMPLEMENTATION
|
||||||
#define STB_EASY_FONT_IMPLEMENTATION
|
#define STB_EASY_FONT_IMPLEMENTATION
|
||||||
#define STB_CONNECTED_COMPONENTS_IMPLEMENTATION
|
|
||||||
|
|
||||||
#include "stb_easy_font.h"
|
#include "stb_easy_font.h"
|
||||||
#include "stb_herringbone_wang_tile.h"
|
#include "stb_herringbone_wang_tile.h"
|
||||||
@ -22,10 +21,6 @@
|
|||||||
#include "stb_image_resize.h"
|
#include "stb_image_resize.h"
|
||||||
#include "stb_rect_pack.h"
|
#include "stb_rect_pack.h"
|
||||||
|
|
||||||
#define STBCC_GRID_COUNT_X_LOG2 10
|
|
||||||
#define STBCC_GRID_COUNT_Y_LOG2 10
|
|
||||||
#include "stb_connected_components.h"
|
|
||||||
|
|
||||||
#define STBVOX_CONFIG_MODE 1
|
#define STBVOX_CONFIG_MODE 1
|
||||||
#include "stb_voxel_render.h"
|
#include "stb_voxel_render.h"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user