123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050 |
- // stb_connected_components - v0.96 - 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 (1<<(LOG2/2)) on each axis (e.g. 32 if LOG2=10,
- // 16 if LOG2=8, etc.) (You can just pad your map with untraversable space.)
- //
- // MEMORY USAGE
- //
- // Uses about 6-7 bytes per grid square (e.g. 7MB for a 1024x1024 grid).
- // Uses a single worst-case allocation which you pass in.
- //
- // PERFORMANCE
- //
- // On a core i7-2700K at 3.5 Ghz, for a particular 1024x1024 map (map_03.png):
- //
- // Creating map : 44.85 ms
- // Making one square traversable: 0.27 ms (average over 29,448 calls)
- // Making one square untraversable: 0.23 ms (average over 30,123 calls)
- // Reachability query: 0.00001 ms (average over 4,000,000 calls)
- //
- // On non-degenerate maps update time is O(N^0.5), but on degenerate maps like
- // checkerboards or 50% random, update time is O(N^0.75) (~2ms on above machine).
- //
- // CHANGELOG
- //
- // 0.96 (2019-03-04) Fix warnings
- // 0.95 (2016-10-16) Bugfix if multiple clumps in one cluster connect to same clump in another
- // 0.94 (2016-04-17) Bugfix & optimize worst case (checkerboard & random)
- // 0.93 (2016-04-16) Reduce memory by 10x for 1Kx1K map; small speedup
- // 0.92 (2016-04-16) Compute sqrt(N) cluster size by default
- // 0.91 (2016-04-15) Initial release
- //
- // TODO:
- // - better API documentation
- // - more comments
- // - try re-integrating naive algorithm & compare performance
- // - more optimized batching (current approach still recomputes local clumps many times)
- // - function for setting a grid of squares at once (just use batching)
- //
- // LICENSE
- //
- // See end of file for license information.
- //
- // 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.
- #ifndef INCLUDE_STB_CONNECTED_COMPONENTS_H
- #define INCLUDE_STB_CONNECTED_COMPONENTS_H
- #include <stdlib.h>
- typedef struct st_stbcc_grid stbcc_grid;
- #ifdef __cplusplus
- extern "C" {
- #endif
- //////////////////////////////////////////////////////////////////////////////////////////
- //
- // 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
- //
- // 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
- 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);
- #define STBCC_NULL_UNIQUE_ID 0xffffffff // returned for closed map squares
- #ifdef __cplusplus
- }
- #endif
- #endif // INCLUDE_STB_CONNECTED_COMPONENTS_H
- #ifdef STB_CONNECTED_COMPONENTS_IMPLEMENTATION
- #include <assert.h>
- #include <string.h> // memset
- #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 (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
- #ifndef STBCC_CLUSTER_SIZE_Y_LOG2
- #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
- #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_CLUSTER (STBCC__CLUSTER_SIZE_X + STBCC__CLUSTER_SIZE_Y) // 64 for 32x32
- #define STBCC__MAX_EXITS_PER_CLUMP (STBCC__CLUSTER_SIZE_X + STBCC__CLUSTER_SIZE_Y) // 64 for 32x32
- #define STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER (STBCC__MAX_EXITS_PER_CLUMP)
- // 2^19 * 2^6 => 2^25 exits => 2^26 => 64MB for 1024x1024
- // Logic for above on 4x4 grid:
- //
- // Many clumps: One clump:
- // + + + +
- // +X.X. +XX.X+
- // .X.X+ .XXX
- // +X.X. XXX.
- // .X.X+ +X.XX+
- // + + + +
- //
- // 8 exits either way
- 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; // 4
- unsigned char num_adjacent; // 1
- unsigned char max_adjacent; // 1
- unsigned char adjacent_clump_list_index; // 1
- unsigned char reserved;
- } stbcc__clump; // 8
- #define STBCC__CLUSTER_ADJACENCY_COUNT (STBCC__MAX_EXITS_PER_CLUSTER*2)
- typedef struct
- {
- short num_clumps;
- unsigned char num_edge_clumps;
- unsigned char rebuild_adjacency;
- stbcc__clump clump[STBCC__MAX_CLUMPS_PER_CLUSTER]; // 8 * 2^9 = 4KB
- stbcc__relative_clumpid adjacency_storage[STBCC__CLUSTER_ADJACENCY_COUNT]; // 256 bytes
- } stbcc__cluster;
- struct st_stbcc_grid
- {
- 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
- 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 4.5KB = 4.5MB
- };
- 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);
- assert(!g->in_batched_update);
- 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);
- 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;
- }
- 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;
- 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_edge_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_edge_clumps; ++k) {
- stbcc__clump *clump = &cluster->clump[k];
- stbcc__unpacked_clumpid m;
- stbcc__relative_clumpid *adj;
- m.clump_index = k;
- m.cluster_x = i;
- m.cluster_y = j;
- adj = &cluster->adjacency_storage[clump->adjacent_clump_list_index];
- for (h=0; h < clump->num_adjacent; ++h) {
- unsigned int clump_index = adj[h].clump_index;
- unsigned int x = adj[h].cluster_dx + i;
- unsigned int y = adj[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_edge_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);
- }
- }
- }
- }
- static void stbcc__build_all_connections_for_cluster(stbcc_grid *g, int cx, int cy)
- {
- // in this particular case, we are fully non-incremental. that means we
- // can discover the correct sizes for the arrays, but requires we build
- // the data into temporary data structures, or just count the sizes, so
- // for simplicity we do the latter
- stbcc__cluster *cluster = &g->cluster[cy][cx];
- unsigned char connected[STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER][STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER/8]; // 64 x 8 => 1KB
- unsigned char num_adj[STBCC__MAX_CLUMPS_PER_CLUSTER] = { 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, m, dx, dy, total;
- int extra;
- g->cluster[cy][cx].rebuild_adjacency = 0;
- total = 0;
- for (m=0; m < 4; ++m) {
- switch (m) {
- case 0:
- dx = 1, dy = 0;
- step_x = 0, step_y = 1;
- i = STBCC__CLUSTER_SIZE_X-1;
- j = 0;
- n = STBCC__CLUSTER_SIZE_Y;
- break;
- case 1:
- dx = -1, dy = 0;
- i = 0;
- j = 0;
- step_x = 0;
- step_y = 1;
- n = STBCC__CLUSTER_SIZE_Y;
- break;
- case 2:
- dy = -1, dx = 0;
- i = 0;
- j = 0;
- step_x = 1;
- step_y = 0;
- n = STBCC__CLUSTER_SIZE_X;
- break;
- case 3:
- dy = 1, dx = 0;
- i = 0;
- j = STBCC__CLUSTER_SIZE_Y-1;
- step_x = 1;
- step_y = 0;
- n = STBCC__CLUSTER_SIZE_X;
- break;
- }
- if (cx+dx < 0 || cx+dx >= g->cw || cy+dy < 0 || cy+dy >= g->ch)
- continue;
- memset(connected, 0, sizeof(connected));
- 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 src = g->clump_for_node[y+j][x+i];
- stbcc__clumpid dest = g->clump_for_node[y+j+dy][x+i+dx];
- if (0 == (connected[src][dest>>3] & (1 << (dest & 7)))) {
- connected[src][dest>>3] |= 1 << (dest & 7);
- ++num_adj[src];
- ++total;
- }
- }
- i += step_x;
- j += step_y;
- }
- }
- assert(total <= STBCC__CLUSTER_ADJACENCY_COUNT);
- // decide how to apportion unused adjacency slots; only clumps that lie
- // on the edges of the cluster need adjacency slots, so divide them up
- // evenly between those clumps
- // we want:
- // extra = (STBCC__CLUSTER_ADJACENCY_COUNT - total) / cluster->num_edge_clumps;
- // but we efficiently approximate this without a divide, because
- // ignoring edge-vs-non-edge with 'num_adj[i]*2' was faster than
- // 'num_adj[i]+extra' with the divide
- if (total + (cluster->num_edge_clumps<<2) <= STBCC__CLUSTER_ADJACENCY_COUNT)
- extra = 4;
- else if (total + (cluster->num_edge_clumps<<1) <= STBCC__CLUSTER_ADJACENCY_COUNT)
- extra = 2;
- else if (total + (cluster->num_edge_clumps<<0) <= STBCC__CLUSTER_ADJACENCY_COUNT)
- extra = 1;
- else
- extra = 0;
- total = 0;
- for (i=0; i < (int) cluster->num_edge_clumps; ++i) {
- int alloc = num_adj[i]+extra;
- if (alloc > STBCC__MAX_EXITS_PER_CLUSTER)
- alloc = STBCC__MAX_EXITS_PER_CLUSTER;
- assert(total < 256); // must fit in byte
- cluster->clump[i].adjacent_clump_list_index = (unsigned char) total;
- cluster->clump[i].max_adjacent = alloc;
- cluster->clump[i].num_adjacent = 0;
- total += alloc;
- }
- assert(total <= STBCC__CLUSTER_ADJACENCY_COUNT);
- 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);
- // make sure all of the above succeeded.
- assert(g->cluster[cy][cx].rebuild_adjacency == 0);
- }
- static void stbcc__add_connections_to_adjacent_cluster_with_rebuild(stbcc_grid *g, int cx, int cy, int dx, int dy)
- {
- if (cx >= 0 && cx < g->cw && cy >= 0 && cy < g->ch) {
- stbcc__add_connections_to_adjacent_cluster(g, cx, cy, dx, dy);
- if (g->cluster[cy][cx].rebuild_adjacency)
- stbcc__build_all_connections_for_cluster(g, cx, cy);
- }
- }
- 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__build_all_connections_for_cluster(g, cx, cy);
- stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx-1, cy, 1, 0);
- stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx+1, cy, -1, 0);
- stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx, cy-1, 0, 1);
- stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx, cy+1, 0,-1);
- if (!g->in_batched_update)
- 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)
- {
- 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;
- 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 (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__build_all_connections_for_cluster(g, i, j);
- 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__cluster *cluster;
- 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;
- cluster = &g->cluster[cy1][cx1];
- clump = &cluster->clump[c1];
- assert(clump->num_adjacent <= clump->max_adjacent);
- if (clump->num_adjacent == clump->max_adjacent)
- g->cluster[cy1][cx1].rebuild_adjacency = 1;
- else {
- stbcc__relative_clumpid *adj = &cluster->adjacency_storage[clump->adjacent_clump_list_index];
- assert(clump->num_adjacent < STBCC__MAX_EXITS_PER_CLUMP);
- assert(clump->adjacent_clump_list_index + clump->num_adjacent <= STBCC__CLUSTER_ADJACENCY_COUNT);
- adj[clump->num_adjacent++] = rc;
- }
- }
- static void stbcc__remove_clump_connection(stbcc_grid *g, int x1, int y1, int x2, int y2)
- {
- stbcc__cluster *cluster;
- stbcc__clump *clump;
- stbcc__relative_clumpid *adj;
- 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;
- cluster = &g->cluster[cy1][cx1];
- clump = &cluster->clump[c1];
- adj = &cluster->adjacency_storage[clump->adjacent_clump_list_index];
- for (i=0; i < clump->num_adjacent; ++i)
- if (rc.clump_index == adj[i].clump_index &&
- rc.cluster_dx == adj[i].cluster_dx &&
- rc.cluster_dy == adj[i].cluster_dy)
- break;
- if (i < clump->num_adjacent)
- adj[i] = adj[--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_EDGE_CLUMPS_PER_CLUSTER][STBCC__MAX_EDGE_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;
- if (g->cluster[cy][cx].rebuild_adjacency)
- 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);
- return;
- }
- 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 src = g->clump_for_node[y+j][x+i];
- stbcc__clumpid dest = g->clump_for_node[y+j+dy][x+i+dx];
- if (0 == (connected[src][dest>>3] & (1 << (dest & 7)))) {
- assert((dest>>3) < sizeof(connected));
- connected[src][dest>>3] |= 1 << (dest & 7);
- stbcc__add_clump_connection(g, x+i, y+j, x+i+dx, y+j+dy);
- if (g->cluster[cy][cx].rebuild_adjacency)
- break;
- }
- }
- 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_EDGE_CLUMPS_PER_CLUSTER][STBCC__MAX_EDGE_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);
- return;
- }
- 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 src = g->clump_for_node[y+j][x+i];
- stbcc__clumpid dest = g->clump_for_node[y+j+dy][x+i+dx];
- if (0 == (disconnected[src][dest>>3] & (1 << (dest & 7)))) {
- disconnected[src][dest>>3] |= 1 << (dest & 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__switch_root(stbcc__cluster_build_info *cbi, int x, int y, stbcc__tinypoint p)
- {
- cbi->parent[p.y][p.x].x = x;
- cbi->parent[p.y][p.x].y = y;
- cbi->parent[y][x].x = x;
- cbi->parent[y][x].y = y;
- }
- 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 clumps along edges so that all edge clumps are first
- // in list; this means in degenerate case we can skip traversing non-edge clumps.
- // because in the first pass we only label leaders, we swap the leader to the
- // edge first
- // first put solid labels on all the edges; these will get overwritten if they're open
- for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j)
- cbi.label[j][0] = cbi.label[j][STBCC__CLUSTER_SIZE_X-1] = STBCC__NULL_CLUMPID;
- for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i)
- cbi.label[0][i] = cbi.label[STBCC__CLUSTER_SIZE_Y-1][i] = STBCC__NULL_CLUMPID;
- for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) {
- i = 0;
- if (STBCC__MAP_OPEN(g, x+i, y+j)) {
- stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j);
- if (p.x == i && p.y == j)
- // if this is the leader, give it a label
- cbi.label[j][i] = label++;
- else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) {
- // if leader is in interior, promote this edge node to leader and label
- stbcc__switch_root(&cbi, i, j, p);
- cbi.label[j][i] = label++;
- }
- // else if leader is on edge, do nothing (it'll get labelled when we reach it)
- }
- i = STBCC__CLUSTER_SIZE_X-1;
- if (STBCC__MAP_OPEN(g, x+i, y+j)) {
- stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j);
- if (p.x == i && p.y == j)
- cbi.label[j][i] = label++;
- else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) {
- stbcc__switch_root(&cbi, i, j, p);
- cbi.label[j][i] = label++;
- }
- }
- }
- for (i=1; i < STBCC__CLUSTER_SIZE_Y-1; ++i) {
- j = 0;
- if (STBCC__MAP_OPEN(g, x+i, y+j)) {
- stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j);
- if (p.x == i && p.y == j)
- cbi.label[j][i] = label++;
- else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) {
- stbcc__switch_root(&cbi, i, j, p);
- cbi.label[j][i] = label++;
- }
- }
- j = STBCC__CLUSTER_SIZE_Y-1;
- if (STBCC__MAP_OPEN(g, x+i, y+j)) {
- stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j);
- if (p.x == i && p.y == j)
- cbi.label[j][i] = label++;
- else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) {
- stbcc__switch_root(&cbi, i, j, p);
- cbi.label[j][i] = label++;
- }
- }
- }
- c = &g->cluster[cy][cx];
- c->num_edge_clumps = label;
- // label any internal clusters
- for (j=1; j < STBCC__CLUSTER_SIZE_Y-1; ++j) {
- for (i=1; i < STBCC__CLUSTER_SIZE_X-1; ++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];
- }
- if (STBCC__MAP_OPEN(g,x+i,y+j))
- assert(cbi.label[j][i] != STBCC__NULL_CLUMPID);
- }
- }
- c->num_clumps = label;
- for (i=0; i < label; ++i) {
- c->clump[i].num_adjacent = 0;
- c->clump[i].max_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);
- }
- // set the global label for all interior clumps since they can't have connections,
- // so we don't have to do this on the global pass (brings from O(N) to O(N^0.75))
- for (i=(int) c->num_edge_clumps; i < (int) c->num_clumps; ++i) {
- stbcc__global_clumpid gc;
- gc.f.cluster_x = cx;
- gc.f.cluster_y = cy;
- gc.f.clump_index = i;
- c->clump[i].global_label = gc;
- }
- c->rebuild_adjacency = 1; // flag that it has no valid adjacency data
- }
- #endif // STB_CONNECTED_COMPONENTS_IMPLEMENTATION
- /*
- ------------------------------------------------------------------------------
- This software is available under 2 licenses -- choose whichever you prefer.
- ------------------------------------------------------------------------------
- ALTERNATIVE A - MIT License
- Copyright (c) 2017 Sean Barrett
- Permission is hereby granted, free of charge, to any person obtaining a copy of
- this software and associated documentation files (the "Software"), to deal in
- the Software without restriction, including without limitation the rights to
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- of the Software, and to permit persons to whom the Software is furnished to do
- so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
- ------------------------------------------------------------------------------
- ALTERNATIVE B - Public Domain (www.unlicense.org)
- This is free and unencumbered software released into the public domain.
- Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
- software, either in source code form or as a compiled binary, for any purpose,
- commercial or non-commercial, and by any means.
- In jurisdictions that recognize copyright laws, the author or authors of this
- software dedicate any and all copyright interest in the software to the public
- domain. We make this dedication for the benefit of the public at large and to
- the detriment of our heirs and successors. We intend this dedication to be an
- overt act of relinquishment in perpetuity of all present and future rights to
- this software under copyright law.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- ------------------------------------------------------------------------------
- */
|