From d1eba3330529205c33fee6a8ac5242f8d87f0ccc Mon Sep 17 00:00:00 2001 From: Sean Barrett Date: Sun, 17 Apr 2016 08:12:52 -0700 Subject: [PATCH] fix bug w/ large clumps per cluster; reduce time spent on global union-find by skipping clumps that don't touch edges --- stb_connected_components.h | 120 ++++++++++++++++++++++++++++++++++++++++----- tests/grid_reachability.c | 26 +++++++++- 2 files changed, 133 insertions(+), 13 deletions(-) diff --git a/stb_connected_components.h b/stb_connected_components.h index f72c151..afa44fc 100644 --- a/stb_connected_components.h +++ b/stb_connected_components.h @@ -353,7 +353,7 @@ static void stbcc__build_connected_components_for_clumps(stbcc_grid *g) 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) { + for (k=0; k < (int) cluster->num_edge_clumps; ++k) { stbcc__global_clumpid m; m.f.clump_index = k; m.f.cluster_x = i; @@ -367,7 +367,7 @@ static void stbcc__build_connected_components_for_clumps(stbcc_grid *g) 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) { + for (k=0; k < (int) cluster->num_edge_clumps; ++k) { stbcc__clump *clump = &cluster->clump[k]; stbcc__unpacked_clumpid m; stbcc__relative_clumpid *adj; @@ -388,7 +388,7 @@ static void stbcc__build_connected_components_for_clumps(stbcc_grid *g) 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) { + for (k=0; k < (int) cluster->num_edge_clumps; ++k) { stbcc__global_clumpid m; m.f.clump_index = k; m.f.cluster_x = i; @@ -507,10 +507,15 @@ static void stbcc__build_all_connections_for_cluster(stbcc_grid *g, int cx, int } assert(total <= STBCC__CLUSTER_ADJACENCY_COUNT); + assert(g->w); stbcc__add_connections_to_adjacent_cluster(g, cx, cy, -1, 0); + assert(g->w); stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 1, 0); + assert(g->w); stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 0,-1); + assert(g->w); stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 0, 1); + assert(g->w); // make sure all of the above succeeded. assert(g->cluster[cy][cx].rebuild_adjacency == 0); } @@ -654,11 +659,13 @@ static void stbcc__add_clump_connection(stbcc_grid *g, int x1, int y1, int x2, i 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; } } @@ -755,6 +762,7 @@ static void stbcc__add_connections_to_adjacent_cluster(stbcc_grid *g, int cx, in 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)))) { + assert((c>>3) < sizeof(connected)); connected[c>>3] |= 1 << (c & 7); stbcc__add_clump_connection(g, x+i, y+j, x+i+dx, y+j+dy); if (g->cluster[cy][cx].rebuild_adjacency) @@ -844,6 +852,14 @@ static void stbcc__incluster_union(stbcc__cluster_build_info *cbi, int x1, int y 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; @@ -874,9 +890,69 @@ static void stbcc__build_clumps_for_cluster(stbcc_grid *g, int cx, int cy) stbcc__incluster_union(&cbi, i,j, i+1,j); } - // label all non-empty leaders + // 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) { - for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) { + 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++; + } + } + } + + // 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)) @@ -894,13 +970,17 @@ static void stbcc__build_clumps_for_cluster(stbcc_grid *g, int cx, int cy) 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 = &g->cluster[cy][cx]; c->num_clumps = label; - for (i=0; i < label; ++i) + for (i=0; i < label; ++i) { c->clump[i].num_adjacent = 0; + c->clump[i].on_edge = 0; + } for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) { @@ -910,20 +990,24 @@ static void stbcc__build_clumps_for_cluster(stbcc_grid *g, int cx, int cy) for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) { int d = cbi.label[0][i]; - if (d != STBCC__NULL_CLUMPID) + if (d != STBCC__NULL_CLUMPID) { c->clump[d].on_edge = 1; + } d = cbi.label[STBCC__CLUSTER_SIZE_Y-1][i]; - if (d != STBCC__NULL_CLUMPID) + if (d != STBCC__NULL_CLUMPID) { c->clump[d].on_edge = 1; + } } - for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) { + for (j=1; j < STBCC__CLUSTER_SIZE_Y-1; ++j) { int d = cbi.label[j][0]; - if (d != STBCC__NULL_CLUMPID) + if (d != STBCC__NULL_CLUMPID) { c->clump[d].on_edge = 1; + } d = cbi.label[j][STBCC__CLUSTER_SIZE_X-1]; - if (d != STBCC__NULL_CLUMPID) + if (d != STBCC__NULL_CLUMPID) { c->clump[d].on_edge = 1; + } } num_on_edge = 0; @@ -931,6 +1015,20 @@ static void stbcc__build_clumps_for_cluster(stbcc_grid *g, int cx, int cy) num_on_edge += c->clump[i].on_edge; c->num_edge_clumps = num_on_edge; + for (i=0; i < (int) c->num_edge_clumps; ++i) + assert(c->clump[i].on_edge); + for (; i < (int) c->num_clumps; ++i) + assert(!c->clump[i].on_edge); + + // 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 + 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 } diff --git a/tests/grid_reachability.c b/tests/grid_reachability.c index c1c49b7..cb7ff07 100644 --- a/tests/grid_reachability.c +++ b/tests/grid_reachability.c @@ -143,12 +143,16 @@ int main(int argc, char **argv) assert(map); - stbi_write_png("tests/output/stbcc/reference.png", w, h, 1, map, 0); - for (j=0; j < h; ++j) for (i=0; i < w; ++i) map[j*w+i] = ~map[j*w+i]; + for (i=0; i < w; ++i) + for (j=0; j < h; ++j) + map[j*w+i] = (i ^ j) & 1 ? 255 : 0; + + stbi_write_png("tests/output/stbcc/reference.png", w, h, 1, map, 0); + //reference(map, w, h); _mkdir("tests/output/stbcc"); @@ -183,6 +187,24 @@ int main(int argc, char **argv) printf("Cluster size: %d,%d\n", STBCC__CLUSTER_SIZE_X, STBCC__CLUSTER_SIZE_Y); #if 1 + for (j=0; j < 10; ++j) { + for (i=0; i < 5000; ++i) { + loc[i][0] = stb_rand() % w; + loc[i][1] = stb_rand() % h; + } + start_timer("updating 500"); + for (i=0; i < 500; ++i) { + if (stbcc_query_grid_open(g, loc[i][0], loc[i][1])) + stbcc_update_grid(g, loc[i][0], loc[i][1], 1); + else + stbcc_update_grid(g, loc[i][0], loc[i][1], 0); + } + end_timer(); + write_map(g, w, h, stb_sprintf("tests/output/stbcc/update_random_%d.png", j*i)); + } + #endif + + #if 0 start_timer("removing"); count = 0; for (i=0; i < 1800; ++i) {