// BUBBLES :: https://github.com/prideout/par // Simple C library for packing circles into hierarchical (or flat) diagrams. // // Implements "Visualization of Large Hierarchical Data by Circle Packing" from // Wang et al (2006). // // Also contains an implementation of Emo Welzl's "Smallest enclosing disks" // algorithm (1991) for enclosing points with circles, and a related method // from Mike Bostock for enclosing circles with the smallest possible circle. // // The API is divided into four sections: // // - Enclosing. Compute the smallest bounding circle for points or circles. // - Packing. Pack circles together, or into other circles. // - Queries. Given a touch point, pick a circle from a hierarchy, etc. // - Deep Zoom. Uses relative coordinate systems to support arbitrary depth. // // In addition to the comment block above each function declaration, the API // has informal documentation here: // // https://prideout.net/bubbles // // Distributed under the MIT License, see bottom of file. #ifndef PAR_BUBBLES_H #define PAR_BUBBLES_H #ifdef __cplusplus extern "C" { #endif #include #include // This can be any signed integer type. #ifndef PAR_BUBBLES_INT #define PAR_BUBBLES_INT int32_t #endif // This must be "float" or "double" or "long double". Note that you should not // need high precision if you use the relative coordinate systems API. #ifndef PAR_BUBBLES_FLT #define PAR_BUBBLES_FLT double #endif // Enclosing / Touching -------------------------------------------------------- // Read an array of (x,y) coordinates, write a single 3-tuple (x,y,radius). void par_bubbles_enclose_points(PAR_BUBBLES_FLT const* xy, PAR_BUBBLES_INT npts, PAR_BUBBLES_FLT* result); // Read an array of 3-tuples (x,y,radius), write a 3-tuple (x,y,radius). void par_bubbles_enclose_disks(PAR_BUBBLES_FLT const* xyr, PAR_BUBBLES_INT ndisks, PAR_BUBBLES_FLT* result); // Find the circle (x,y,radius) that is tangent to 3 points (x,y). void par_bubbles_touch_three_points(PAR_BUBBLES_FLT const* xy, PAR_BUBBLES_FLT* result); // Find a position for disk "c" that makes it tangent to "a" and "b". // Note that the ordering of a and b can affect where c will land. // All three arguments are pointers to three-tuples (x,y,radius). void par_bubbles_touch_two_disks(PAR_BUBBLES_FLT* c, PAR_BUBBLES_FLT const* a, PAR_BUBBLES_FLT const* b); // Returns the smallest circle that intersects the three specified circles. // This is the problem of Problem of Apollonius! void par_bubbles_touch_three_disks(PAR_BUBBLES_FLT const* xyr1, PAR_BUBBLES_FLT const* xyr2, PAR_BUBBLES_FLT const* xyr3, PAR_BUBBLES_FLT* result); // Packing --------------------------------------------------------------------- // Tiny POD structure returned by all packing functions. Private data is // attached after the public fields, so clients should call the provided // free function rather than freeing the memory manually. typedef struct { PAR_BUBBLES_FLT* xyr; // array of 3-tuples (x y radius) in input order PAR_BUBBLES_INT count; // number of 3-tuples in "xyr" PAR_BUBBLES_INT* ids; // populated by par_bubbles_cull } par_bubbles_t; void par_bubbles_free_result(par_bubbles_t*); // Entry point for unbounded non-hierarchical packing. Takes a list of radii. par_bubbles_t* par_bubbles_pack(PAR_BUBBLES_FLT const* radiuses, PAR_BUBBLES_INT nradiuses); // Consume a hierarchy defined by a list of integers. Each integer is an index // to its parent. The root node is its own parent, and it must be the first node // in the list. Clients do not have control over individual radiuses, only the // radius of the outermost enclosing disk. par_bubbles_t* par_bubbles_hpack_circle(PAR_BUBBLES_INT* nodes, PAR_BUBBLES_INT nnodes, PAR_BUBBLES_FLT radius); // Queries --------------------------------------------------------------------- // Find the node at the given position. Children are on top of their parents. // If the result is -1, there is no node at the given pick coordinate. PAR_BUBBLES_INT par_bubbles_pick(par_bubbles_t const*, PAR_BUBBLES_FLT x, PAR_BUBBLES_FLT y); // Get bounding box; take a pointer to 4 floats and set them to min xy, max xy. void par_bubbles_compute_aabb(par_bubbles_t const*, PAR_BUBBLES_FLT* aabb); // Check if the given circle (3-tuple) intersects the given aabb (4-tuple). bool par_bubbles_check_aabb(PAR_BUBBLES_FLT const* disk, PAR_BUBBLES_FLT const* aabb); // Clip the bubble diagram to the given AABB (4-tuple of left,bottom,right,top) // and return the result. Circles smaller than the given world-space // "minradius" are removed. Optionally, an existing diagram (dst) can be passed // in to receive the culled dataset, which reduces the number of memory allocs // when calling this function frequently. Pass null to "dst" to create a new // culled diagram. par_bubbles_t* par_bubbles_cull(par_bubbles_t const* src, PAR_BUBBLES_FLT const* aabb, PAR_BUBBLES_FLT minradius, par_bubbles_t* dst); // Dump out a SVG file for diagnostic purposes. void par_bubbles_export(par_bubbles_t const* bubbles, char const* filename); // Returns a pointer to a list of children nodes. void par_bubbles_get_children(par_bubbles_t const* bubbles, PAR_BUBBLES_INT idx, PAR_BUBBLES_INT** pchildren, PAR_BUBBLES_INT* nchildren); // Returns the given node's parent, or 0 if it's the root. PAR_BUBBLES_INT par_bubbles_get_parent(par_bubbles_t const* bubbles, PAR_BUBBLES_INT idx); // Finds the height of the tree and returns one of its deepest leaves. void par_bubbles_get_maxdepth(par_bubbles_t const* bubbles, PAR_BUBBLES_INT* maxdepth, PAR_BUBBLES_INT* leaf); // Finds the height of the tree at a certain node. PAR_BUBBLES_INT par_bubbles_get_depth(par_bubbles_t const* bubbles, PAR_BUBBLES_INT node); // Returns a 4-tuple (min xy, max xy) for the given node. void par_bubbles_compute_aabb_for_node(par_bubbles_t const* bubbles, PAR_BUBBLES_INT node, PAR_BUBBLES_FLT* aabb); // Find the deepest node that is an ancestor of both A and B. Classic! PAR_BUBBLES_INT par_bubbles_lowest_common_ancestor(par_bubbles_t const* bubbles, PAR_BUBBLES_INT node_a, PAR_BUBBLES_INT node_b); // Deep Zoom ------------------------------------------------------------------- // Similar to hpack, but maintains precision by storing disk positions within // the local coordinate system of their parent. After calling this function, // clients can use cull_local to flatten the coordinate systems. par_bubbles_t* par_bubbles_hpack_local(PAR_BUBBLES_INT* nodes, PAR_BUBBLES_INT nnodes); // Similar to par_bubbles_cull, but takes a root node rather than an AABB, // and returns a result within the local coordinate system of the new root. // In other words, the new root will have radius 1, centered at (0,0). The // minradius is also expressed in this coordinate system. par_bubbles_t* par_bubbles_cull_local(par_bubbles_t const* src, PAR_BUBBLES_FLT const* aabb, PAR_BUBBLES_FLT minradius, PAR_BUBBLES_INT root, par_bubbles_t* dst); // Finds the smallest node in the given bubble diagram that completely encloses // the given axis-aligned bounding box (min xy, max xy). The AABB coordinates // are expressed in the local coordinate system of the given root node. PAR_BUBBLES_INT par_bubbles_find_local(par_bubbles_t const* src, PAR_BUBBLES_FLT const* aabb, PAR_BUBBLES_INT root); // Similar to pick, but expects (x,y) to be in the coordinate system of the // given root node. PAR_BUBBLES_INT par_bubbles_pick_local(par_bubbles_t const*, PAR_BUBBLES_FLT x, PAR_BUBBLES_FLT y, PAR_BUBBLES_INT root, PAR_BUBBLES_FLT minradius); // Obtains the scale and translation (which should be applied in that order) // that can move a point from the node0 coord system to the node1 coord system. // The "xform" argument should point to three floats, which will be populated // with: x translation, y translation, and scale. bool par_bubbles_transform_local(par_bubbles_t const* bubbles, PAR_BUBBLES_FLT* xform, PAR_BUBBLES_INT node0, PAR_BUBBLES_INT node1); // Dump out a SVG file for diagnostic purposes. void par_bubbles_export_local(par_bubbles_t const* bubbles, PAR_BUBBLES_INT idx, char const* filename); typedef enum { PAR_BUBBLES_FILTER_DEFAULT, PAR_BUBBLES_FILTER_DISCARD_LAST_CHILD, PAR_BUBBLES_FILTER_KEEP_ONLY_LAST_CHILD } par_bubbles_filter; // Special-case function that affects the behavior of subsequent calls to // cull_local. Allows clients to filter the children list of each non-leaf // node, which is especially useful when using placeholder bubbles for labels. void par_bubbles_set_filter(par_bubbles_t* src, par_bubbles_filter f); typedef enum { PAR_BUBBLES_HORIZONTAL, PAR_BUBBLES_VERTICAL } par_bubbles_orientation; // Sets some global state that affect subsequent calls to hpack. The first two // children can either be placed horizontally (default) or vertically. The // effect of this is subtle, since overall layout is obviously circular. void par_bubbles_set_orientation(par_bubbles_orientation ); #ifndef PAR_PI #define PAR_PI (3.14159265359) #define PAR_MIN(a, b) (a > b ? b : a) #define PAR_MAX(a, b) (a > b ? a : b) #define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) #define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } #define PAR_SQR(a) ((a) * (a)) #endif #ifndef PAR_MALLOC #define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) #define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) #define PAR_REALLOC(T, BUF, N) ((T*) realloc(BUF, sizeof(T) * (N))) #define PAR_FREE(BUF) free(BUF) #endif #ifdef __cplusplus } #endif // ----------------------------------------------------------------------------- // END PUBLIC API // ----------------------------------------------------------------------------- #ifdef PAR_BUBBLES_IMPLEMENTATION #define PARINT PAR_BUBBLES_INT #define PARFLT PAR_BUBBLES_FLT #include #include #include #include #include static par_bubbles_orientation par_bubbles__ostate = PAR_BUBBLES_HORIZONTAL; typedef struct { PARINT prev; PARINT next; } par_bubbles__node; typedef struct { PARFLT* xyr; // results array PARINT count; // client-provided count PARINT* ids; // populated by par_bubbles_cull PARFLT const* radiuses; // client-provided radius list par_bubbles__node* chain; // counterclockwise enveloping chain PARINT const* graph_parents; // client-provided parent indices PARINT* graph_children; // flat list of children indices PARINT* graph_heads; // list of "pointers" to first child PARINT* graph_tails; // list of "pointers" to one-past-last child PARINT npacked; PARINT maxwidth; PARINT capacity; par_bubbles_filter filter; } par_bubbles__t; static PARFLT par_bubbles__len2(PARFLT const* a) { return a[0] * a[0] + a[1] * a[1]; } static void par_bubbles__initgraph(par_bubbles__t* bubbles) { PARINT const* parents = bubbles->graph_parents; PARINT* nchildren = PAR_CALLOC(PARINT, bubbles->count); for (PARINT i = 0; i < bubbles->count; i++) { nchildren[parents[i]]++; } PARINT c = 0; bubbles->graph_heads = PAR_CALLOC(PARINT, bubbles->count * 2); bubbles->graph_tails = bubbles->graph_heads + bubbles->count; for (PARINT i = 0; i < bubbles->count; i++) { bubbles->maxwidth = PAR_MAX(bubbles->maxwidth, nchildren[i]); bubbles->graph_heads[i] = bubbles->graph_tails[i] = c; c += nchildren[i]; } bubbles->graph_heads[0] = bubbles->graph_tails[0] = 1; bubbles->graph_children = PAR_MALLOC(PARINT, c); for (PARINT i = 1; i < bubbles->count; i++) { PARINT parent = parents[i]; bubbles->graph_children[bubbles->graph_tails[parent]++] = i; } PAR_FREE(nchildren); } static void par_bubbles__initflat(par_bubbles__t* bubbles) { PARFLT* xyr = bubbles->xyr; PARFLT const* radii = bubbles->radiuses; par_bubbles__node* chain = bubbles->chain; PARFLT x0, y0, x1, y1; if (par_bubbles__ostate == PAR_BUBBLES_HORIZONTAL) { x0 = -radii[0]; y0 = 0; x1 = radii[1]; y1 = 0; } else { x0 = 0; y0 = -radii[0]; x1 = 0; y1 = radii[1]; } *xyr++ = x0; *xyr++ = y0; *xyr++ = *radii++; if (bubbles->count == ++bubbles->npacked) { return; } *xyr++ = x1; *xyr++ = y1; *xyr++ = *radii++; if (bubbles->count == ++bubbles->npacked) { return; } xyr[2] = *radii; par_bubbles_touch_two_disks(xyr, xyr - 6, xyr - 3); if (bubbles->count == ++bubbles->npacked) { return; } chain[0].prev = 2; chain[0].next = 1; chain[1].prev = 0; chain[1].next = 2; chain[2].prev = 1; chain[2].next = 0; } // March forward or backward along the enveloping chain, starting with the // node at "cn" and testing for collision against the node at "ci". static PARINT par_bubbles__collide(par_bubbles__t* bubbles, PARINT ci, PARINT cn, PARINT* cj, PARINT direction) { PARFLT const* ci_xyr = bubbles->xyr + ci * 3; par_bubbles__node* chain = bubbles->chain; PARINT nsteps = 1; if (direction > 0) { for (PARINT i = chain[cn].next; i != cn; i = chain[i].next, ++nsteps) { PARFLT const* i_xyr = bubbles->xyr + i * 3; PARFLT dx = i_xyr[0] - ci_xyr[0]; PARFLT dy = i_xyr[1] - ci_xyr[1]; PARFLT dr = i_xyr[2] + ci_xyr[2]; if (0.999 * dr * dr > dx * dx + dy * dy) { *cj = i; return nsteps; } } return 0; } for (PARINT i = chain[cn].prev; i != cn; i = chain[i].prev, ++nsteps) { PARFLT const* i_xyr = bubbles->xyr + i * 3; PARFLT dx = i_xyr[0] - ci_xyr[0]; PARFLT dy = i_xyr[1] - ci_xyr[1]; PARFLT dr = i_xyr[2] + ci_xyr[2]; if (0.999 * dr * dr > dx * dx + dy * dy) { *cj = i; return nsteps; } } return 0; } static void par_bubbles__packflat(par_bubbles__t* bubbles) { PARFLT const* radii = bubbles->radiuses; PARFLT* xyr = bubbles->xyr; par_bubbles__node* chain = bubbles->chain; // Find the circle closest to the origin, known as "Cm" in the paper. PARINT cm = 0; PARFLT mindist = par_bubbles__len2(xyr + 0); PARFLT dist = par_bubbles__len2(xyr + 3); if (dist > mindist) { cm = 1; } dist = par_bubbles__len2(xyr + 6); if (dist > mindist) { cm = 2; } // In the paper, "Cn" is always the node that follows "Cm". PARINT ci, cn = chain[cm].next; for (ci = bubbles->npacked; ci < bubbles->count; ) { PARFLT* ci_xyr = xyr + ci * 3; ci_xyr[2] = radii[ci]; PARFLT* cm_xyr = xyr + cm * 3; PARFLT* cn_xyr = xyr + cn * 3; par_bubbles_touch_two_disks(ci_xyr, cn_xyr, cm_xyr); // Check for a collision. In the paper, "Cj" is the intersecting node. PARINT cj_f; PARINT nfsteps = par_bubbles__collide(bubbles, ci, cn, &cj_f, +1); if (!nfsteps) { chain[cm].next = ci; chain[ci].prev = cm; chain[ci].next = cn; chain[cn].prev = ci; cm = ci++; continue; } // Search backwards for a collision, in case it is closer. PARINT cj_b; PARINT nbsteps = par_bubbles__collide(bubbles, ci, cm, &cj_b, -1); // Intersection occurred after Cn. if (nfsteps <= nbsteps) { cn = cj_f; chain[cm].next = cn; chain[cn].prev = cm; continue; } // Intersection occurred before Cm. cm = cj_b; chain[cm].next = cn; chain[cn].prev = cm; } bubbles->npacked = bubbles->count; } static void par__disk_from_two_points(PARFLT const* xy1, PARFLT const* xy2, PARFLT* result) { PARFLT dx = xy1[0] - xy2[0]; PARFLT dy = xy1[1] - xy2[1]; result[0] = 0.5 * (xy1[0] + xy2[0]); result[1] = 0.5 * (xy1[1] + xy2[1]); result[2] = sqrt(dx * dx + dy * dy) / 2.0; } static PARINT par__disk_contains_point(PARFLT const* xyr, PARFLT const* xy) { PARFLT dx = xyr[0] - xy[0]; PARFLT dy = xyr[1] - xy[1]; return dx * dx + dy * dy <= PAR_SQR(xyr[2]); } static void par__easydisk_from_points(PARFLT* disk, PARFLT const* edgepts, PARINT nedgepts) { if (nedgepts == 0) { disk[0] = 0; disk[1] = 0; disk[2] = 0; return; } if (nedgepts == 1) { disk[0] = edgepts[0]; disk[1] = edgepts[1]; disk[2] = 0; return; } par__disk_from_two_points(edgepts, edgepts + 2, disk); if (nedgepts == 2 || par__disk_contains_point(disk, edgepts + 4)) { return; } par__disk_from_two_points(edgepts, edgepts + 4, disk); if (par__disk_contains_point(disk, edgepts + 2)) { return; } par__disk_from_two_points(edgepts + 2, edgepts + 4, disk); if (par__disk_contains_point(disk, edgepts)) { return; } par_bubbles_touch_three_points(edgepts, disk); } static void par__minidisk_points(PARFLT* disk, PARFLT const* pts, PARINT npts, PARFLT const* edgepts, PARINT nedgepts) { if (npts == 0 || nedgepts == 3) { par__easydisk_from_points(disk, edgepts, nedgepts); return; } PARFLT const* pt = pts + (--npts) * 2; par__minidisk_points(disk, pts, npts, edgepts, nedgepts); if (!par__disk_contains_point(disk, pt)) { PARFLT edgepts1[6]; for (PARINT i = 0; i < nedgepts * 2; i += 2) { edgepts1[i] = edgepts[i]; edgepts1[i + 1] = edgepts[i + 1]; } edgepts1[2 * nedgepts] = pt[0]; edgepts1[2 * nedgepts + 1] = pt[1]; par__minidisk_points(disk, pts, npts, edgepts1, ++nedgepts); } } // Returns true if the specified circle1 contains the specified circle2. static bool par__disk_contains_disk(PARFLT const* xyr1, PARFLT const* xyr2) { PARFLT xc0 = xyr1[0] - xyr2[0]; PARFLT yc0 = xyr1[1] - xyr2[1]; return sqrt(xc0 * xc0 + yc0 * yc0) < xyr1[2] - xyr2[2] + 1e-6; } // Returns the smallest circle that intersects the two specified circles. static void par__disk_from_two_disks(PARFLT const* xyr1, PARFLT const* xyr2, PARFLT* result) { PARFLT x1 = xyr1[0], y1 = xyr1[1], r1 = xyr1[2]; PARFLT x2 = xyr2[0], y2 = xyr2[1], r2 = xyr2[2]; PARFLT x12 = x2 - x1, y12 = y2 - y1, r12 = r2 - r1; PARFLT l = sqrt(x12 * x12 + y12 * y12); result[0] = (x1 + x2 + x12 / l * r12) / 2; result[1] = (y1 + y2 + y12 / l * r12) / 2; result[2] = (l + r1 + r2) / 2; } static void par__easydisk_from_disks(PARFLT* disk, PARFLT const* edgedisks, PARINT nedgedisks) { assert(nedgedisks <= 3); if (nedgedisks == 0) { disk[0] = 0; disk[1] = 0; disk[2] = 0; return; } if (nedgedisks == 1) { disk[0] = edgedisks[0]; disk[1] = edgedisks[1]; disk[2] = edgedisks[2]; return; } if (nedgedisks == 2) { par__disk_from_two_disks(edgedisks, edgedisks + 3, disk); return; } par_bubbles_touch_three_disks(edgedisks, edgedisks + 3, edgedisks + 6, disk); } static void par__minidisk_disks(PARFLT* result, PARFLT const* disks, PARINT ndisks, PARFLT const* edgedisks, PARINT nedgedisks) { if (ndisks == 0 || nedgedisks == 3) { par__easydisk_from_disks(result, edgedisks, nedgedisks); return; } PARFLT const* disk = disks + (--ndisks) * 3; par__minidisk_disks(result, disks, ndisks, edgedisks, nedgedisks); if (!par__disk_contains_disk(result, disk)) { PARFLT edgedisks1[9]; for (PARINT i = 0; i < nedgedisks * 3; i += 3) { edgedisks1[i] = edgedisks[i]; edgedisks1[i + 1] = edgedisks[i + 1]; edgedisks1[i + 2] = edgedisks[i + 2]; } edgedisks1[3 * nedgedisks] = disk[0]; edgedisks1[3 * nedgedisks + 1] = disk[1]; edgedisks1[3 * nedgedisks + 2] = disk[2]; par__minidisk_disks(result, disks, ndisks, edgedisks1, ++nedgedisks); } } static void par_bubbles__copy_disk(par_bubbles__t const* src, par_bubbles__t* dst, PARINT parent) { PARINT i = dst->count++; if (dst->capacity < dst->count) { dst->capacity = PAR_MAX(16, dst->capacity) * 2; dst->xyr = PAR_REALLOC(PARFLT, dst->xyr, 3 * dst->capacity); dst->ids = PAR_REALLOC(PARINT, dst->ids, dst->capacity); } PARFLT const* xyr = src->xyr + parent * 3; dst->xyr[i * 3] = xyr[0]; dst->xyr[i * 3 + 1] = xyr[1]; dst->xyr[i * 3 + 2] = xyr[2]; dst->ids[i] = parent; } void par_bubbles_enclose_points(PARFLT const* xy, PARINT npts, PARFLT* result) { par__minidisk_points(result, xy, npts, 0, 0); } void par_bubbles_enclose_disks(PARFLT const* xyr, PARINT ndisks, PARFLT* result) { par__minidisk_disks(result, xyr, ndisks, 0, 0); } void par_bubbles_touch_three_points(PARFLT const* xy, PARFLT* xyr) { // Many thanks to Stephen Schmitts: // http://www.abecedarical.com/zenosamples/zs_circle3pts.html PARFLT p1x = xy[0], p1y = xy[1]; PARFLT p2x = xy[2], p2y = xy[3]; PARFLT p3x = xy[4], p3y = xy[5]; PARFLT a = p2x - p1x, b = p2y - p1y; PARFLT c = p3x - p1x, d = p3y - p1y; PARFLT e = a * (p2x + p1x) * 0.5 + b * (p2y + p1y) * 0.5; PARFLT f = c * (p3x + p1x) * 0.5 + d * (p3y + p1y) * 0.5; PARFLT det = a*d - b*c; PARFLT cx = xyr[0] = (d*e - b*f) / det; PARFLT cy = xyr[1] = (-c*e + a*f) / det; xyr[2] = sqrt((p1x - cx)*(p1x - cx) + (p1y - cy)*(p1y - cy)); } void par_bubbles_touch_two_disks(PARFLT* c, PARFLT const* a, PARFLT const* b) { PARFLT db = a[2] + c[2], dx = b[0] - a[0], dy = b[1] - a[1]; if (db && (dx || dy)) { PARFLT da = b[2] + c[2], dc = dx * dx + dy * dy; da *= da; db *= db; PARFLT x = 0.5 + (db - da) / (2 * dc); PARFLT db1 = db - dc; PARFLT y0 = PAR_MAX(0, 2 * da * (db + dc) - db1 * db1 - da * da); PARFLT y = sqrt(y0) / (2 * dc); c[0] = a[0] + x * dx + y * dy; c[1] = a[1] + x * dy - y * dx; } else { c[0] = a[0] + db; c[1] = a[1]; } } void par_bubbles_touch_three_disks(PARFLT const* xyr1, PARFLT const* xyr2, PARFLT const* xyr3, PARFLT* result) { PARFLT x1 = xyr1[0], y1 = xyr1[1], r1 = xyr1[2], x2 = xyr2[0], y2 = xyr2[1], r2 = xyr2[2], x3 = xyr3[0], y3 = xyr3[1], r3 = xyr3[2], a2 = 2 * (x1 - x2), b2 = 2 * (y1 - y2), c2 = 2 * (r2 - r1), d2 = x1 * x1 + y1 * y1 - r1 * r1 - x2 * x2 - y2 * y2 + r2 * r2, a3 = 2 * (x1 - x3), b3 = 2 * (y1 - y3), c3 = 2 * (r3 - r1), d3 = x1 * x1 + y1 * y1 - r1 * r1 - x3 * x3 - y3 * y3 + r3 * r3, ab = a3 * b2 - a2 * b3, xa = (b2 * d3 - b3 * d2) / ab - x1, xb = (b3 * c2 - b2 * c3) / ab, ya = (a3 * d2 - a2 * d3) / ab - y1, yb = (a2 * c3 - a3 * c2) / ab, A = xb * xb + yb * yb - 1, B = 2 * (xa * xb + ya * yb + r1), C = xa * xa + ya * ya - r1 * r1, r = (-B - sqrt(B * B - 4 * A * C)) / (2 * A); result[0] = xa + xb * r + x1; result[1] = ya + yb * r + y1; result[2] = r; } void par_bubbles_free_result(par_bubbles_t* pubbub) { par_bubbles__t* bubbles = (par_bubbles__t*) pubbub; PAR_FREE(bubbles->graph_children); PAR_FREE(bubbles->graph_heads); PAR_FREE(bubbles->chain); PAR_FREE(bubbles->xyr); PAR_FREE(bubbles->ids); PAR_FREE(bubbles); } par_bubbles_t* par_bubbles_pack(PARFLT const* radiuses, PARINT nradiuses) { par_bubbles__t* bubbles = PAR_CALLOC(par_bubbles__t, 1); if (nradiuses > 0) { bubbles->radiuses = radiuses; bubbles->count = nradiuses; bubbles->chain = PAR_MALLOC(par_bubbles__node, nradiuses); bubbles->xyr = PAR_MALLOC(PARFLT, 3 * nradiuses); par_bubbles__initflat(bubbles); par_bubbles__packflat(bubbles); } return (par_bubbles_t*) bubbles; } // Assigns a radius to every node according to its number of descendants. void par_bubbles__generate_radii(par_bubbles__t* bubbles, par_bubbles__t* worker, PARINT parent) { PARINT head = bubbles->graph_heads[parent]; PARINT tail = bubbles->graph_tails[parent]; PARINT nchildren = tail - head; PARINT pr = parent * 3 + 2; bubbles->xyr[pr] = 1; if (nchildren == 0) { return; } for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = bubbles->graph_children[cindex]; par_bubbles__generate_radii(bubbles, worker, child); bubbles->xyr[pr] += bubbles->xyr[child * 3 + 2]; } // The following square root seems to produce a nicer, more space-filling, // distribution of radiuses in randomly-generated trees. bubbles->xyr[pr] = sqrtf(bubbles->xyr[pr]); } void par_bubbles__hpack(par_bubbles__t* bubbles, par_bubbles__t* worker, PARINT parent, bool local) { PARINT head = bubbles->graph_heads[parent]; PARINT tail = bubbles->graph_tails[parent]; PARINT nchildren = tail - head; if (nchildren == 0) { return; } // Cast away const because we're using the worker as a cache to avoid // a kazillion malloc / free calls. PARFLT* radiuses = (PARFLT*) worker->radiuses; // We perform flat layout twice: once without padding (to determine scale) // and then again with scaled padding. PARFLT enclosure[3]; PARFLT px = bubbles->xyr[parent * 3 + 0]; PARFLT py = bubbles->xyr[parent * 3 + 1]; PARFLT pr = bubbles->xyr[parent * 3 + 2]; const PARFLT PAR_HPACK_PADDING1 = 0.15; const PARFLT PAR_HPACK_PADDING2 = 0.025; PARFLT scaled_padding = 0.0; while (1) { worker->npacked = 0; worker->count = nchildren; PARINT c = 0; for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = bubbles->graph_children[cindex]; radiuses[c++] = bubbles->xyr[child * 3 + 2] + scaled_padding; } par_bubbles__initflat(worker); par_bubbles__packflat(worker); // Using Welzl's algorithm instead of a simple AABB enclosure is // slightly slower and doesn't yield much aesthetic improvement. #if PAR_BUBBLES_HPACK_WELZL par_bubbles_enclose_disks(worker->xyr, nchildren, enclosure); #else PARFLT aabb[6]; par_bubbles_compute_aabb((par_bubbles_t const*) worker, aabb); enclosure[0] = 0.5 * (aabb[0] + aabb[2]); enclosure[1] = 0.5 * (aabb[1] + aabb[3]); enclosure[2] = 0; for (PARINT c = 0; c < nchildren; c++) { PARFLT x = worker->xyr[c * 3 + 0] - enclosure[0]; PARFLT y = worker->xyr[c * 3 + 1] - enclosure[1]; PARFLT r = worker->xyr[c * 3 + 2]; enclosure[2] = PAR_MAX(enclosure[2], r + sqrtf(x * x + y * y)); } #endif if (scaled_padding || !PAR_HPACK_PADDING1) { break; } else { scaled_padding = PAR_HPACK_PADDING1 / enclosure[2]; } } PARFLT cx = enclosure[0], cy = enclosure[1], cr = enclosure[2]; scaled_padding *= cr; cr += PAR_HPACK_PADDING2 * cr; // Transform the children to fit nicely into either (a) the unit circle, // or (b) their parent. The former is used if "local" is true. PARFLT scale, tx, ty; if (local) { scale = 1.0 / cr; tx = 0; ty = 0; } else { scale = pr / cr; tx = px; ty = py; } PARFLT const* src = worker->xyr; for (PARINT cindex = head; cindex != tail; cindex++, src += 3) { PARFLT* dst = bubbles->xyr + 3 * bubbles->graph_children[cindex]; dst[0] = tx + scale * (src[0] - cx); dst[1] = ty + scale * (src[1] - cy); dst[2] = scale * (src[2] - scaled_padding); } // Recursion. TODO: It might be better to use our own stack here. for (PARINT cindex = head; cindex != tail; cindex++) { par_bubbles__hpack(bubbles, worker, bubbles->graph_children[cindex], local); } } par_bubbles_t* par_bubbles_hpack_circle(PARINT* nodes, PARINT nnodes, PARFLT radius) { par_bubbles__t* bubbles = PAR_CALLOC(par_bubbles__t, 1); if (nnodes > 0) { bubbles->graph_parents = nodes; bubbles->count = nnodes; bubbles->chain = PAR_MALLOC(par_bubbles__node, nnodes); bubbles->xyr = PAR_MALLOC(PARFLT, 3 * nnodes); par_bubbles__initgraph(bubbles); par_bubbles__t* worker = PAR_CALLOC(par_bubbles__t, 1); worker->radiuses = PAR_MALLOC(PARFLT, bubbles->maxwidth); worker->chain = PAR_MALLOC(par_bubbles__node, bubbles->maxwidth); worker->xyr = PAR_MALLOC(PARFLT, 3 * bubbles->maxwidth); par_bubbles__generate_radii(bubbles, worker, 0); bubbles->xyr[0] = 0; bubbles->xyr[1] = 0; bubbles->xyr[2] = radius; par_bubbles__hpack(bubbles, worker, 0, false); par_bubbles_free_result((par_bubbles_t*) worker); } return (par_bubbles_t*) bubbles; } // TODO: use a stack instead of recursion static PARINT par_bubbles__pick(par_bubbles__t const* bubbles, PARINT parent, PARFLT x, PARFLT y) { PARFLT const* xyr = bubbles->xyr + parent * 3; PARFLT d2 = PAR_SQR(x - xyr[0]) + PAR_SQR(y - xyr[1]); if (d2 > PAR_SQR(xyr[2])) { return -1; } PARINT head = bubbles->graph_heads[parent]; PARINT tail = bubbles->graph_tails[parent]; for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = bubbles->graph_children[cindex]; PARINT result = par_bubbles__pick(bubbles, child, x, y); if (result > -1) { return result; } } return parent; } PARINT par_bubbles_pick(par_bubbles_t const* cbubbles, PARFLT x, PARFLT y) { par_bubbles__t const* bubbles = (par_bubbles__t const*) cbubbles; if (bubbles->count == 0) { return -1; } return par_bubbles__pick(bubbles, 0, x, y); } void par_bubbles_compute_aabb(par_bubbles_t const* bubbles, PARFLT* aabb) { if (bubbles->count == 0) { return; } PARFLT const* xyr = bubbles->xyr; aabb[0] = aabb[2] = xyr[0]; aabb[1] = aabb[3] = xyr[1]; for (PARINT i = 0; i < bubbles->count; i++, xyr += 3) { aabb[0] = PAR_MIN(xyr[0] - xyr[2], aabb[0]); aabb[1] = PAR_MIN(xyr[1] - xyr[2], aabb[1]); aabb[2] = PAR_MAX(xyr[0] + xyr[2], aabb[2]); aabb[3] = PAR_MAX(xyr[1] + xyr[2], aabb[3]); } } bool par_bubbles_check_aabb(PARFLT const* disk, PARFLT const* aabb) { PARFLT cx = PAR_CLAMP(disk[0], aabb[0], aabb[2]); PARFLT cy = PAR_CLAMP(disk[1], aabb[1], aabb[3]); PARFLT dx = disk[0] - cx; PARFLT dy = disk[1] - cy; PARFLT d2 = dx * dx + dy * dy; return d2 < (disk[2] * disk[2]); } static void par_bubbles__cull(par_bubbles__t const* src, PARFLT const* aabb, PARFLT minradius, par_bubbles__t* dst, PARINT parent) { PARFLT const* xyr = src->xyr + parent * 3; if (xyr[2] < minradius || !par_bubbles_check_aabb(xyr, aabb)) { return; } par_bubbles__copy_disk(src, dst, parent); PARINT head = src->graph_heads[parent]; PARINT tail = src->graph_tails[parent]; for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = src->graph_children[cindex]; par_bubbles__cull(src, aabb, minradius, dst, child); } } par_bubbles_t* par_bubbles_cull(par_bubbles_t const* psrc, PARFLT const* aabb, PARFLT minradius, par_bubbles_t* pdst) { par_bubbles__t const* src = (par_bubbles__t const*) psrc; par_bubbles__t* dst = (par_bubbles__t*) pdst; if (!dst) { dst = PAR_CALLOC(par_bubbles__t, 1); pdst = (par_bubbles_t*) dst; } else { dst->count = 0; } if (src->count == 0) { return pdst; } par_bubbles__cull(src, aabb, minradius, dst, 0); return pdst; } void par_bubbles_export(par_bubbles_t const* bubbles, char const* filename) { PARFLT aabb[4]; par_bubbles_compute_aabb(bubbles, aabb); PARFLT maxextent = PAR_MAX(aabb[2] - aabb[0], aabb[3] - aabb[1]); PARFLT padding = 0.05 * maxextent; FILE* svgfile = fopen(filename, "wt"); if (!svgfile) { fprintf(stderr, "Unable to open %s for writing.\n", filename); return; } fprintf(svgfile, "\n" "\n" "\n", aabb[0] - padding, aabb[1] - padding, aabb[2] - aabb[0] + 2 * padding, aabb[3] - aabb[1] + 2 * padding, aabb[0] - padding, aabb[1] - padding); PARFLT const* xyr = bubbles->xyr; for (PARINT i = 0; i < bubbles->count; i++, xyr += 3) { fprintf(svgfile, "\n", xyr[2] * 0.01, xyr[0], xyr[1], xyr[2]); fprintf(svgfile, "%d\n", xyr[0], xyr[1] + xyr[2] * 0.125, xyr[2] * 0.5, (int) i); } fputs("\n", svgfile); fclose(svgfile); } void par_bubbles_get_children(par_bubbles_t const* pbubbles, PARINT node, PARINT** pchildren, PARINT* nchildren) { par_bubbles__t const* bubbles = (par_bubbles__t const*) pbubbles; *pchildren = bubbles->graph_children + bubbles->graph_heads[node]; *nchildren = bubbles->graph_tails[node] - bubbles->graph_heads[node]; } PARINT par_bubbles_get_parent(par_bubbles_t const* pbubbles, PARINT node) { par_bubbles__t const* bubbles = (par_bubbles__t const*) pbubbles; return bubbles->graph_parents[node]; } void par_bubbles__get_maxdepth(par_bubbles__t const* bubbles, PARINT* maxdepth, PARINT* leaf, PARINT parent, PARINT depth) { if (depth > *maxdepth) { *leaf = parent; *maxdepth = depth; } PARINT* children; PARINT nchildren; par_bubbles_t const* pbubbles = (par_bubbles_t const*) bubbles; par_bubbles_get_children(pbubbles, parent, &children, &nchildren); for (PARINT c = 0; c < nchildren; c++) { par_bubbles__get_maxdepth(bubbles, maxdepth, leaf, children[c], depth + 1); } } void par_bubbles_get_maxdepth(par_bubbles_t const* pbubbles, PARINT* maxdepth, PARINT* leaf) { par_bubbles__t const* bubbles = (par_bubbles__t const*) pbubbles; *maxdepth = -1; *leaf = -1; return par_bubbles__get_maxdepth(bubbles, maxdepth, leaf, 0, 0); } PARINT par_bubbles_get_depth(par_bubbles_t const* pbubbles, PARINT node) { par_bubbles__t const* bubbles = (par_bubbles__t const*) pbubbles; PARINT const* parents = bubbles->graph_parents; PARINT depth = 0; while (node) { node = parents[node]; depth++; } return depth; } void par_bubbles_compute_aabb_for_node(par_bubbles_t const* bubbles, PAR_BUBBLES_INT node, PAR_BUBBLES_FLT* aabb) { PARFLT const* xyr = bubbles->xyr + 3 * node; aabb[0] = aabb[2] = xyr[0]; aabb[1] = aabb[3] = xyr[1]; aabb[0] = PAR_MIN(xyr[0] - xyr[2], aabb[0]); aabb[1] = PAR_MIN(xyr[1] - xyr[2], aabb[1]); aabb[2] = PAR_MAX(xyr[0] + xyr[2], aabb[2]); aabb[3] = PAR_MAX(xyr[1] + xyr[2], aabb[3]); } PARINT par_bubbles_lowest_common_ancestor(par_bubbles_t const* bubbles, PARINT node_a, PARINT node_b) { if (node_a == node_b) { return node_a; } par_bubbles__t const* src = (par_bubbles__t const*) bubbles; PARINT depth_a = par_bubbles_get_depth(bubbles, node_a); PARINT* chain_a = PAR_MALLOC(PARINT, depth_a); for (PARINT i = depth_a - 1; i >= 0; i--) { chain_a[i] = node_a; node_a = src->graph_parents[node_a]; } PARINT depth_b = par_bubbles_get_depth(bubbles, node_b); PARINT* chain_b = PAR_MALLOC(PARINT, depth_b); for (PARINT i = depth_b - 1; i >= 0; i--) { chain_b[i] = node_b; node_b = src->graph_parents[node_b]; } PARINT lca = 0; for (PARINT i = 1; i < PAR_MIN(depth_a, depth_b); i++) { if (chain_a[i] != chain_b[i]) { break; } lca = chain_a[i]; } PAR_FREE(chain_a); PAR_FREE(chain_b); return lca; } void par_bubbles_export_local(par_bubbles_t const* bubbles, PAR_BUBBLES_INT root, char const* filename) { par_bubbles_t* clone = par_bubbles_cull_local(bubbles, 0, 0, root, 0); FILE* svgfile = fopen(filename, "wt"); fprintf(svgfile, "\n" "\n" "\n", -1.0, -1.0, 2.0, 2.0, -1.0, -1.0); PARFLT const* xyr = clone->xyr; for (PARINT i = 0; i < clone->count; i++, xyr += 3) { fprintf(svgfile, "\n", xyr[2] * 0.01, xyr[0], xyr[1], xyr[2]); } fputs("\n", svgfile); fclose(svgfile); par_bubbles_free_result(clone); } void par_bubbles_set_filter(par_bubbles_t* bubbles, par_bubbles_filter f) { par_bubbles__t* src = (par_bubbles__t*) bubbles; src->filter = f; } static void par_bubbles__copy_disk_local(par_bubbles__t const* src, par_bubbles__t* dst, PARINT parent, PARFLT const* xform) { PARINT i = dst->count++; if (dst->capacity < dst->count) { dst->capacity = PAR_MAX(16, dst->capacity) * 2; dst->xyr = PAR_REALLOC(PARFLT, dst->xyr, 3 * dst->capacity); dst->ids = PAR_REALLOC(PARINT, dst->ids, dst->capacity); } PARFLT const* xyr = src->xyr + parent * 3; dst->xyr[i * 3] = xyr[0] * xform[2] + xform[0]; dst->xyr[i * 3 + 1] = xyr[1] * xform[2] + xform[1]; dst->xyr[i * 3 + 2] = xyr[2] * xform[2]; dst->ids[i] = parent; } static void par_bubbles__cull_local(par_bubbles__t const* src, PARFLT const* aabb, PARFLT const* xform, PARFLT minradius, par_bubbles__t* dst, PARINT parent) { PARFLT const* xyr = src->xyr + parent * 3; PARFLT child_xform[3] = { xform[0] + xform[2] * xyr[0], xform[1] + xform[2] * xyr[1], xform[2] * xyr[2] }; if (aabb && !par_bubbles_check_aabb(child_xform, aabb)) { return; } if (child_xform[2] < minradius) { return; } par_bubbles__copy_disk_local(src, dst, parent, xform); xform = child_xform; PARINT head = src->graph_heads[parent]; PARINT tail = src->graph_tails[parent]; if (src->filter == PAR_BUBBLES_FILTER_DISCARD_LAST_CHILD) { tail--; } else if (src->filter == PAR_BUBBLES_FILTER_KEEP_ONLY_LAST_CHILD) { head = PAR_MAX(head, tail - 1); } for (PARINT cindex = head; cindex < tail; cindex++) { PARINT child = src->graph_children[cindex]; par_bubbles__cull_local(src, aabb, xform, minradius, dst, child); } } par_bubbles_t* par_bubbles_cull_local(par_bubbles_t const* psrc, PAR_BUBBLES_FLT const* aabb, PAR_BUBBLES_FLT minradius, PAR_BUBBLES_INT root, par_bubbles_t* pdst) { par_bubbles__t const* src = (par_bubbles__t const*) psrc; par_bubbles__t* dst = (par_bubbles__t*) pdst; if (!dst) { dst = PAR_CALLOC(par_bubbles__t, 1); pdst = (par_bubbles_t*) dst; } else { dst->count = 0; } if (src->count == 0) { return pdst; } PARFLT xform[3] = {0, 0, 1}; par_bubbles__copy_disk_local(src, dst, root, xform); dst->xyr[0] = dst->xyr[1] = 0; dst->xyr[2] = 1; PARINT head = src->graph_heads[root]; PARINT tail = src->graph_tails[root]; if (src->filter == PAR_BUBBLES_FILTER_DISCARD_LAST_CHILD) { tail--; } else if (src->filter == PAR_BUBBLES_FILTER_KEEP_ONLY_LAST_CHILD) { head = PAR_MAX(head, tail - 1); } for (PARINT cindex = head; cindex < tail; cindex++) { PARINT child = src->graph_children[cindex]; par_bubbles__cull_local(src, aabb, xform, minradius, dst, child); } return pdst; } par_bubbles_t* par_bubbles_hpack_local(PARINT* nodes, PARINT nnodes) { par_bubbles__t* bubbles = PAR_CALLOC(par_bubbles__t, 1); if (nnodes > 0) { bubbles->graph_parents = nodes; bubbles->count = nnodes; bubbles->chain = PAR_MALLOC(par_bubbles__node, nnodes); bubbles->xyr = PAR_MALLOC(PARFLT, 3 * nnodes); par_bubbles__initgraph(bubbles); par_bubbles__t* worker = PAR_CALLOC(par_bubbles__t, 1); worker->radiuses = PAR_MALLOC(PARFLT, bubbles->maxwidth); worker->chain = PAR_MALLOC(par_bubbles__node, bubbles->maxwidth); worker->xyr = PAR_MALLOC(PARFLT, 3 * bubbles->maxwidth); par_bubbles__generate_radii(bubbles, worker, 0); bubbles->xyr[0] = 0; bubbles->xyr[1] = 0; bubbles->xyr[2] = 1; par_bubbles__hpack(bubbles, worker, 0, true); par_bubbles_free_result((par_bubbles_t*) worker); } return (par_bubbles_t*) bubbles; } static bool par_bubbles__disk_encloses_aabb(PAR_BUBBLES_FLT cx, PAR_BUBBLES_FLT cy, PAR_BUBBLES_FLT r, PAR_BUBBLES_FLT const* aabb) { PAR_BUBBLES_FLT x, y; PAR_BUBBLES_FLT r2 = r * r; x = aabb[0]; y = aabb[1]; if (PAR_SQR(x - cx) + PAR_SQR(y - cy) > r2) { return false; } x = aabb[2]; y = aabb[1]; if (PAR_SQR(x - cx) + PAR_SQR(y - cy) > r2) { return false; } x = aabb[0]; y = aabb[3]; if (PAR_SQR(x - cx) + PAR_SQR(y - cy) > r2) { return false; } x = aabb[2]; y = aabb[3]; return PAR_SQR(x - cx) + PAR_SQR(y - cy) <= r2; } static bool par_bubbles__get_local(par_bubbles__t const* src, PARFLT* xform, PARINT parent, PARINT node); static bool par_bubbles_transform_parent(par_bubbles__t const* src, PARFLT* xform, PARINT node0) { PARINT node1 = src->graph_parents[node0]; xform[0] = 0; xform[1] = 0; xform[2] = 1; PARINT head = src->graph_heads[node1]; PARINT tail = src->graph_tails[node1]; for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = src->graph_children[cindex]; if (par_bubbles__get_local(src, xform, child, node0)) { return true; } } return false; } PARINT par_bubbles__find_local(par_bubbles__t const* src, PARFLT const* xform, PARFLT const* aabb, PARINT parent) { PARFLT const* xyr = src->xyr + parent * 3; PARFLT child_xform[3] = { xform[2] * xyr[0] + xform[0], xform[2] * xyr[1] + xform[1], xform[2] * xyr[2] }; xform = child_xform; if (!par_bubbles__disk_encloses_aabb(xform[0], xform[1], xform[2], aabb)) { return -1; } PARFLT maxrad = 0; PARINT head = src->graph_heads[parent]; PARINT tail = src->graph_tails[parent]; for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = src->graph_children[cindex]; PARFLT const* xyr = src->xyr + child * 3; maxrad = PAR_MAX(maxrad, xyr[2]); } PARFLT maxext = PAR_MAX(aabb[2] - aabb[0], aabb[3] - aabb[1]); if (2 * maxrad < maxext) { return parent; } for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = src->graph_children[cindex]; PARINT cresult = par_bubbles__find_local(src, xform, aabb, child); if (cresult > -1) { return cresult; } } return parent; } // This finds the deepest node that completely encloses the box. PARINT par_bubbles_find_local(par_bubbles_t const* bubbles, PARFLT const* aabb, PARINT root) { par_bubbles__t const* src = (par_bubbles__t const*) bubbles; // Since the aabb is expressed in the coordinate system of the given root, // we can do a trivial rejection right away, using the unit circle. if (!par_bubbles__disk_encloses_aabb(0, 0, 1, aabb)) { if (root == 0) { return -1; } PARFLT xform[3]; par_bubbles_transform_parent(src, xform, root); PARFLT width = aabb[2] - aabb[0]; PARFLT height = aabb[3] - aabb[1]; PARFLT cx = 0.5 * (aabb[0] + aabb[2]); PARFLT cy = 0.5 * (aabb[1] + aabb[3]); width *= xform[2]; height *= xform[2]; cx = cx * xform[2] + xform[0]; cy = cy * xform[2] + xform[1]; PARFLT new_aabb[4] = { cx - width * 0.5, cy - height * 0.5, cx + width * 0.5, cy + height * 0.5 }; PARINT parent = src->graph_parents[root]; return par_bubbles_find_local(bubbles, new_aabb, parent); } PARFLT maxrad = 0; PARINT head = src->graph_heads[root]; PARINT tail = src->graph_tails[root]; for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = src->graph_children[cindex]; PARFLT const* xyr = src->xyr + child * 3; maxrad = PAR_MAX(maxrad, xyr[2]); } PARFLT maxext = PAR_MAX(aabb[2] - aabb[0], aabb[3] - aabb[1]); if (2 * maxrad < maxext) { return root; } PARFLT xform[3] = {0, 0, 1}; for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = src->graph_children[cindex]; PARINT cresult = par_bubbles__find_local(src, xform, aabb, child); if (cresult > -1) { return cresult; } } return root; } // This could be implemented much more efficiently, but for now it simply // calls find_local with a zero-size AABB, then ensures that the result // has a radius that is greater than or equal to minradius. PARINT par_bubbles_pick_local(par_bubbles_t const* bubbles, PARFLT x, PARFLT y, PARINT root, PARFLT minradius) { par_bubbles__t const* src = (par_bubbles__t const*) bubbles; PARFLT aabb[] = { x, y, x, y }; PARINT result = par_bubbles_find_local(bubbles, aabb, root); if (result == -1) { return result; } PARINT depth = par_bubbles_get_depth(bubbles, result); PARINT* chain = PAR_MALLOC(PARINT, depth); PARINT node = result; for (PARINT i = depth - 1; i >= 0; i--) { chain[i] = node; node = src->graph_parents[node]; } PARFLT radius = 1; for (PARINT i = 1; i < depth; i++) { PARINT node = chain[i]; radius *= src->xyr[node * 3 + 2]; if (radius < minradius) { result = chain[i - 1]; break; } } PAR_FREE(chain); return result; } static bool par_bubbles__get_local(par_bubbles__t const* src, PARFLT* xform, PARINT parent, PARINT node) { PARFLT const* xyr = src->xyr + parent * 3; PARFLT child_xform[3] = { xform[2] * xyr[0] + xform[0], xform[2] * xyr[1] + xform[1], xform[2] * xyr[2] }; if (parent == node) { xform[0] = child_xform[0]; xform[1] = child_xform[1]; xform[2] = child_xform[2]; return true; } PARINT head = src->graph_heads[parent]; PARINT tail = src->graph_tails[parent]; for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = src->graph_children[cindex]; if (par_bubbles__get_local(src, child_xform, child, node)) { xform[0] = child_xform[0]; xform[1] = child_xform[1]; xform[2] = child_xform[2]; return true; } } return false; } // Obtains the scale and translation (which should be applied in that order) // that can move a point from the node0 coord system to the node1 coord system. // The "xform" argument should point to three floats, which will be populated // with: x translation, y translation, and scale. bool par_bubbles_transform_local(par_bubbles_t const* bubbles, PARFLT* xform, PARINT node0, PARINT node1) { par_bubbles__t const* src = (par_bubbles__t const*) bubbles; xform[0] = 0; xform[1] = 0; xform[2] = 1; if (node0 == node1) { return true; } if (node1 == src->graph_parents[node0]) { return par_bubbles_transform_parent(src, xform, node0); } // First try the case where node1 is a descendant of node0 PARINT head = src->graph_heads[node0]; PARINT tail = src->graph_tails[node0]; for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = src->graph_children[cindex]; if (par_bubbles__get_local(src, xform, child, node1)) { float tx = xform[0]; float ty = xform[1]; float s = xform[2]; xform[0] = -tx / s; xform[1] = -ty / s; xform[2] = 1.0 / s; return true; } } // Next, try the case where node0 is a descendant of node1 head = src->graph_heads[node1]; tail = src->graph_tails[node1]; for (PARINT cindex = head; cindex != tail; cindex++) { PARINT child = src->graph_children[cindex]; if (par_bubbles__get_local(src, xform, child, node0)) { return true; } } // If we reach here, then node0 is neither an ancestor nor a descendant, so // do something hacky and return false. It would be best to find the lowest // common ancestor, but let's just assume the lowest common ancestor is 0. PARFLT xform2[3] = {0, 0, 1}; par_bubbles_transform_local(bubbles, xform, node0, 0); par_bubbles_transform_local(bubbles, xform2, 0, node1); xform[0] *= xform2[2]; xform[1] *= xform2[2]; xform[2] *= xform2[2]; xform[0] += xform2[0]; xform[1] += xform2[1]; return false; } void par_bubbles_set_orientation(par_bubbles_orientation ostate) { par_bubbles__ostate = ostate; } #undef PARINT #undef PARFLT #endif // PAR_BUBBLES_IMPLEMENTATION #endif // PAR_BUBBLES_H // par_bubbles is distributed under the MIT license: // // Copyright (c) 2019 Philip Rideout // // 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.