Largest circle inside a non-convex polygon - algorithm

How can I find the largest circle that can fit inside a concave polygon?
A brute force algorithm is OK as long as it can handle polygons with ~50 vertices in real-time.

The key to solving this problem is first making an observation: the center of the largest circle that will fit inside an arbitrary polygon is the point that is:
Inside the polygon; and
Furthest from any point on the edges of the polygon.
Why? Because every point on the edge of a circle is equidistant from that center. By definition, the largest circle will have the largest radius and will touch the polygon on at least two points so if you find the point inside furthest from the polygon you've found the center of the circle.
This problem appears in geography and is solved iteratively to any arbitrary precision. Its called the Poles of Inaccessibility problem. See Poles of Inaccessibility: A Calculation Algorithm for the Remotest Places on Earth.
The basic algorithm works like this:
Define R as a rectilinear region from (xmin,ymin) to (xmax,ymax);
Divide R into an arbitrary number of points. The paper uses 21 as a heuristic (meaning divide the height and width by 20);
Clip any points that are outside the polygon;
For the remainder find the point that is furthest from any point on the edge;
From that point define a new R with smaller intervals and bounds and repeat from step 2 to get to any arbitrary precision answer. The paper reduces R by a factor of the square root of 2.
One note, How to test if a point is inside the polygon or not: The simplest solution to this part of the problem is to cast a ray to the right of the point. If it crosses an odd number of edges, it's within the polygon. If it's an even number, it's outside.
Also, as far as testing the distance to any edge there are two cases you need to consider:
The point is perpendicular to a point on that edge (within the bounds of the two vertices); or
It isn't.
(2) is easy. The distance to the edge is the minimum of the distances to the two vertices. For (1), the closest point on that edge will be the point that intersects the edge at a 90 degree angle starting from the point you're testing. See Distance of a Point to a Ray or Segment.

In case anyone is looking for a practical implementation, I designed a faster algorithm that solves this problem for a given precision and made it a JavaScript library. It's similar to the iterative grid algorithm described by #cletus, but it's guaranteed to obtain global optimum, and is also 20-40 times faster in practice.
Check it out: https://github.com/mapbox/polylabel

An O(n log(n)) algorithm:
Construct the Voronoi Diagram of the edges in P. This can be done with, for example, Fortunes algorithm.
For Voronoi nodes (points equidistant to three or more edges) inside P;
Find the node with the maximum distance to edges in P. This node is the centre of the maximum inscribed circle.

Summary: In theory, this can be done in O(n) time. In practice you can do it in O(n log n) time.
Generalized Voronoi diagrams.
If you consider the vertices and edges of the polygon as a set of sites and tessellate the interior into the "nearest neighbor cells" then you get the so-called (generalized) Voronoi diagram. The Voronoi diagram consists of nodes and edges connecting them. The clearance of a node is the distance to its defining polygon faces.
(Here the polygon even has holes; the principle still works.)
The key observation now is that the center of the maximum inscribed circle touches three faces (vertices or edges) of the polygon, and no other face can be closer. So the center has to lie on a Voronoi node, i.e, the node with the largest clearance.
In the example above the node that marks the center of the maximum inscribed circle touches two edges and a vertex of the polygon.
The medial axis, by the way, is the Voronoi diagram with those Voronoi edges removed that emanate from reflex vertices. Hence, the center of the maximum inscribed circle also lies on the medial axis.
Source: A blog article of mine that deals with generalizations of maximum inscribed circles at some point. There you can find more on Voronoi diagrams and their relation to maximum inscribed circles.
Algorithms & implementations.
You could actually compute the Voronoi diagram. A worst-case O(n log n) algorithm for points and segments is given by Fortune, A sweepline algorithm for Voronoi diagrams, SoCG'86. Held published the software package Vroni with an expected O(n log n) time complexity, which actually computes the maximum inscribed circle, too. And there seems to be an implementation in boost, too.
For simple polygons (i.e., without holes) a time-optimal algorithm that runs in O(n) time is due to Chin et al., Finding the Medial Axis of a Simple Polygon in Linear Time, 1999.
Brute force.
However, as you stated that you are fine with a brute-force algorithm: What about simply trying out all triplets of sites (vertices and edges). For each triplet you find candidate Voronoi nodes, i.e., equidistant loci to the three sites and check whether any other site would intersect the candidate maximum inscribed circle. If there is an intersection you dismiss the candidate. Take the greatest you can find over all triplets.
See chapter 3 in my Master thesis about more details on computing equidistant loci for three sites.

I implemented a piece of python code based on cv2 to get the maximum/largest inscribed circle inside mask/polygon/contours. It supports non-convex/hollow shape.
import cv2
import numpy as np
def get_test_mask():
# Create an image
r = 100
mask = np.zeros((4 * r, 4 * r), dtype=np.uint8)
# Create a sequence of points to make a contour
vert = [None] * 6
vert[0] = (3 * r // 2, int(1.34 * r))
vert[1] = (1 * r, 2 * r)
vert[2] = (3 * r // 2, int(2.866 * r))
vert[3] = (5 * r // 2, int(2.866 * r))
vert[4] = (3 * r, 2 * r)
vert[5] = (5 * r // 2, int(1.34 * r))
# Draw it in mask
for i in range(6):
cv2.line(mask, vert[i], vert[(i + 1) % 6], (255), 63)
return mask
mask = get_test_mask()
"""
Get the maximum/largest inscribed circle inside mask/polygon/contours.
Support non-convex/hollow shape
"""
dist_map = cv2.distanceTransform(mask, cv2.DIST_L2, cv2.DIST_MASK_PRECISE)
_, radius, _, center = cv2.minMaxLoc(dist_map)
result = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
cv2.circle(result, tuple(center), int(radius), (0, 0, 255), 2, cv2.LINE_8, 0)
# minEnclosingCircle directly by cv2
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[-2:]
center2, radius2 = cv2.minEnclosingCircle(np.concatenate(contours, 0))
cv2.circle(result, (int(center2[0]), int(center2[1])), int(radius2), (0, 255, 0,), 2)
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
Red circle is max inscribed circle
Source: https://gist.github.com/DIYer22/f82dc329b27c2766b21bec4a563703cc

I used Straight Skeletons to place an image inside a polygon with three steps:
Find the straight skeleton using the Straight Skeleton algorithm (pic 1)
Base on the straight skeleton, find the largest circle (pic 2)
Draw the image inside that circle (pic 3)
Try it at: https://smartdiagram.com/simple-infographics-3d-charts-2/

An O(n log X) algorithm, where X depends on the precision you want.
Binary search for largest radius R for a circle:
At each iteration, for a given radius r, push each edge E, "inward" by R, to get E'. For each edge E', define half-plane H as the set of all points "inside" the the polygon (using E' as the boundary). Now, compute the intersection of all these half-planes E', which could be done in O(n) time. If the intersection is non-empty, then if you draw a circle with radius r using any point in the intersection as the center, it will be inside the given polygon.

Related

Fast 2D signed distance

I need a way to compute the distance beetween a point and the bounding edge of a polygon.
If the point is outside the polygon, the distance will be posivite
If the point is inside the polygon, the distance will be negative
This is called SDF for Signed Distance Field/Function
The polygon itself is composed of multiple paths, can be concave, with holes, but not self intersecting, and with a lot of clockwise ordered points (10000+).
I've found some existing solutions, but they require to test the point against each polygon edge, which is not efficient enough.
Here is the visual result produced (green is positive, red is negative):
So I've tried the following:
Put the polygon edges in a quadtree
To compute the distance, find the closest edge to the point and change the sign depending on which side of the edge the point is.
Sadly, it doesn't works when the point is at the same distance of multiple edges, such as corners.
I've tried adding condition so a point is outside the polygon if it's on the exterior side of all the edges, but it doesn't solve the interior problem, and the other way around.
Can't wrap my head around it...
If anyone curious, the idea is to later use some shader to produce images like this :
EDIT
To clarify, here is a close up of the problem arising at corners :
For all the points in area A, the closest segment is S1, so no problem
For all the points in area E, the closest segment is S2, so no problem either
All points in area B, C and D are at the same distance of S1 and S2
Points in area B are on the exterior side of S1 and interior side of S2
Points in area D are on the interior side of S1 and exterior side of S2
Points in area C are on the exterior side of both segments
One might think that a point has to be on the interior side of both segments in order to be considered "in". It solves the problem for angles < 180°, but the problem is mirrored for angles > 180°
Worst, two or more corners can share the same position (like the four way corner in the low part of first image)...
I hope this solves your problem.
This is implemented in Python.
First, we use imageio to import the image as an array. We need to use a modified version of your image (I filled the interior region in white).
Then, we transform the RGBA matrix into a binary matrix with a 0 contour defining your interface (phi in the code snippet)
Here's phi from the snippet below (interior region value = +0.5, exterior region value = -0.5):
import imageio
import numpy as np
import matplotlib.pyplot as plt
import skfmm
# Load image
im = imageio.imread("0WmVI_filled.png")
ima = np.array(im)
# Differentiate the inside / outside region
phi = np.int64(np.any(ima[:, :, :3], axis = 2))
# The array will go from - 1 to 0. Add 0.5(arbitrary) so there 's a 0 contour.
phi = np.where(phi, 0, -1) + 0.5
# Show phi
plt.imshow(phi)
plt.xticks([])
plt.yticks([])
plt.colorbar()
plt.show()
# Compute signed distance
# dx = cell(pixel) size
sd = skfmm.distance(phi, dx = 1)
# Plot results
plt.imshow(sd)
plt.colorbar()
plt.show()
Finally, we use the scikit-fmm module to compute the signed distance.
Here's the resulting signed distance field:
For closed, non-intersecting and well oriented polygons, you can speed up the calculation of a signed distance field by limiting the work to feature extrusions based on this paper.
The closest point to a line lies within the edge extrusion as you have shown in your image (regions A and E). The closest feature for the points in B, C and D are not the edges but the vertex.
The algorithm is:
for each edge and vertex construct negative and positive extrusions
for each point, determine which extrusions they are in and find the smallest magnitude distance of the correct sign.
The work is reduced to a point-in-polygon test and distance calculations to lines and points. For reduced work, you can consider limited extrusions of the same size to define a thin shell where distance values are calculated. This seems the desired functionality for your halo shading example.
While you still have to iterate over all features, both extrusion types are always convex so you can have early exits from quad trees, half-plane tests and other optimisations, especially with limited extrusion distances.
The extrusions of edges are rectangles in the surface normal direction (negative normal for interior distances).
From 1:
The extrusions for vertices are wedges whose sides are the normals of the edges that meet at that vertex. Vertices have either positive or negative extrusions depending on the angle between the edges. Flat vertices will have no extrusions as the space is covered by the edge extrusions.
From 1:

Robust polygon normal calculation

is there a good robust algorithm to calculate normal vector of a convex polygon (in 3D, of course)? For triangles, it is easy: one takes two of the triangle's edges and calculates the cross product:
vec3 u = point[0] - point[1], v = point[0] - point[2];
vec3 n = normalize(cross(u, v));
But this approach does not really extend to polygons very well. Some edges of the polygon can be nearly or "exactly" collinear (this will happen often in meshes where T-Junction removal took place), therefore it is necessary to choose a pair of edges, giving a "strong" normal (both edges are "long enough" and they hold "almost perpendicular" angle).
This approach will still not work for all polygons, though. Imagine a polygon in the shape of a disc. If it is very finely subdivided, all the edges will be very short and all of the consecutive edges will be almost collinear, regardless of the radius of the disc. At the same time, the normal is very well defined.
One solution could be to find the largest inscribed triangle and to calculate the normal of that. However, finding it will have complexity of O(n^2), which seems prohibitive.
A better solution could be to use SVD or Eigenvalue decomposition to calculate the normal, given all the polygon points, not just three or four.
Is there a standard algorithm for this? Anyone has a good way of doing it?
If you factorize the formula for a triangle, you'll get the following:
n ~ (p1 - p0) x (p2 - p0)
= p0 x p1 + p1 x p2 + p2 x p0
You can generalize this formula for arbitrary polygons:
n ~ p0 x p1 + p1 x p2 + ... + pn x p0
So sum the cross product of consecutive edges. This is a robust algorithm and works for non-planar polygons.
If you can be sure that the polygon is planar, I would do the following (to save computation time):
Repeat k times
Pick 3 random polygon vertices
Calculate the normal of the according triangle
Choose the longest normal as the polygon's normal.
You might discard any normal that has a length <= epsilon.
start from an arbitrary vertex(lets call it vertex A), and move to the next vertex in the list(call it vertex B). calculate the perpendicular vector(call it vector P) to the AB vector. Then continue iterating in the vertex list to find the vertex that is perpendicularly the most distant from vector AB. So at each iteration take the dot product of the current element(take vertex B as the origin) with the vector P and take the one that has the greatest result in magnitude(take absolute value) and call it C. calculate the cross product of A B C vectors.
if the poly is convex you can stop iterating untill the perpendicular distances starts to
get smaller in magnitude.
I came up with this idea, i do not know how efficient this method would be since I do not know any other algorithm to compare with.
You can calculate the covariance matrix for all the points of the polygon (which will be a 3x3 matrix for 3D space). The normal to the polygon will be the Eigen vector corresponding to the smallest Eigen value.

Best dynamic data structure for 2d circle nearest neighbor

The title is most of the problem. I have a set of circles, each given by a center C and radius r. The distance between two circles is the Euclidean distance between their centers minus both their radii. For circles a and b,
d_ab = |C_a - C_b| - r_a - r_b.
Note this can be negative if the circles overlap.
Then what is the quickest data structure for finding the nearest (minimum distance) neighbor of a given circle in the set?
Adding and deleting circles with "find nearest" queries interleaved in arbitrary order must be supported. Nothing is known about the set's geometric distribution in advance.
This will be the heart of a system where a typical number of circles is 50,000 and 10's of thousands of queries, inserts, and deletes will be needed, ideally at user-interaction speed (a second or less) on a high end tablet device.
The point nearest neighbor has been studied to death, but this version with circles seems somewhat harder.
I have looked at kd-trees, quad trees, r-trees, and some variations of these. Both advice on which of these might be the best to try and also new suggestions would be a terrific help.
Cover trees are another possibility for a proximity structure. They don't support deletes (?), but you can soft delete and rebuild in the background to keep the garbage from piling up, which may be a useful technique for the other structures.
There's a reduction from the 2D circle problem to the 3D point problem with a funky metric that goes like this. (The proximity structures that you named should be adaptable.) Map a circle centered at (x, y) with radius r to the point (x, y, r). Define the length of a vector (dx, dy, dz) to be sqrt(dx**2 + dy**2) + abs(dz). This induces a metric. To find the circle nearest to a center (x, y) (the radius of the query circle is not relevant), do a proximity search at (x, y, R), where R is greater than or equal to the maximum radius of a circle (it may be possible to modify your proximity structure so that it's not necessary to track R).
From my experience implementing kd-trees and Voronoi diagrams on points, it will be significantly easier to implement kd-trees from scratch. Even if you reuse someone else's robust geometric primitives (and please do, to save your sanity if you go this route), the degenerate edge cases of Voronoi/point location take time to get right.
I propose the following heuristic using a KD-Tree or something else that allows for O(log N) nearest neighbor. Instead of using a single point and a radius to represented a circle. Use k equidistant points on a circle itself, plus the center of the circle, otherwise you might have issues with a small circle inside of a large circle. It's the same the same idea as using a regular polygon of k vertices to represent a circle. It is then possible to take one vertex and find it's nearest neighbor (ignoring the vertices on the same circle) to find an approximation as to what is the closest circle based on the closest regular polygon.
The performances are as followed:
Create the KD-Tree: O(kN log kN)
Remove/add Circle to KD-Tree: O(k log kN)
-Add or remove all k points of a circle within the KD-Tree
Nearest Circle query (Circle): O(k log kN)
-This is done by first removing all k points of the circle (O(k log kN)) as it's not terribly useful to find out that the nearest neighbor of a circle is unsurprisingly, the circle itself. Then for each k points in the circle, find the nearest neighbor (O(k log kN)). Once the nearest neighbors are found, the actual nearest (to within some error) is the the one with the smallest distance (after calculating the true distance based on point and radius) (O(1)).
I'd suggest either using k = log(N) if you prefer it to be fast or k = sqrt(N) if you prefer it to be accurate.
Also, it's possible I haven't considered some special case that causes issues, so watch out for them.
If there is a guarantee that circles don't have large radius, at least that maximum radius (R) is significantly smaller than area where circles are positioned, than I think it can be covered with standard space partitioning and nearest neighbour search.
When searching for circle in a set that has minimal distance to given circle, than radius of given circle doesn't matter (distance definition.) Because of that it is same if only center (point) is compared to set of circles.
With that it is enough to store set of circles in space partition structure only by there centers (set of points.) Circle adding and deleting is done in standard way for points. Finding of nearest circle to given point can be done in two step:
find nearest center to given point P. Say circle C with center c and radius r.
center of circle that is closer to P, by your distance, can be only in ring around P with inner radius r and outer radius R-d(P,c). It is enough to search partitions that intersect that ring for candidates.
It is possible to optimize search by combining these two steps. In first step, some of partitions of interest are already visited. By storing which partitions are visited, and circle with minimal distance found in these partitions, search in second step can be reduced.
Thanks to #David Eisenstadt for the idea of a 3d search structure. This is part of the best answer, though his strange metric is not needed.
The key is to look in detail at how nearest neighbor search works. I'll show this for quadrees. Kd-trees with k=3 are similar. Here is pseudocode:
# Let nearest_info be a record containing the current nearest neighbor (or nil
# if none yet) and the distance from point to that nearest neighbor.
def find_nearest_neighbor(node, target, nearest_info)
if node is leaf
update nearest_info using target and the points found in this leaf
else
for each subdivision S of node
if S contains any point P where dist(P,T) < nearest_info.distance,
find_neareast(S, target, nearest_info)
end
end
end
end
When this is done, nearest_info contains the nearest neighbor and its distance.
The key is if S contains any point P where dist(P,T) < nearest_info.distance. In a 3d space, of (x,y,r) triples that describe circles, we have
def dist(P,T)
return sqrt( (P.x - T.x)^2 + (P.y - T.y)^2 ) - P.r - T.r
end
Here P is an arbitrary point in an octant of an octree cuboid. How to consider all points in the cuboid? Note all components of T are effectively fixed for a given search, so it's clearer if we write the target as a constant point (a, b, c):
def dist(P)
return sqrt( (P.x - a)^2 + (P.y - b)^2 ) - P.r
end
Where we have left out c = T.r completely because it can be subtracted out of the minimum distance after the algorithm is complete. In other words, the radius of the target does not affect the result.
With this it is pretty easy to see that the P we need to obtain minimum dist to the cuboid is Euclidean closest to the target with respect to x and y and with the max represented radius. This is very easy and quick to compute: a 2d point-rectangle distance and a 1d max operation.
In hindsight all this is obvious, but it took a while to see it from the right point of view. Thanks for the ideas.

Compute the size of Voronoi regions from Delaunay triangulation?

I would like to compute the mean and standard deviation of the areas of a set of Voronoi regions in 2D (if the region extends to infinity, I'll just clip it to the unit square).
However if possible I would like to do this computation from the Delaunay Triangulation without explicitly computing the Voronoi regions? Is this even possible, or is it better to just compute the Voronoi diagram explicitly?
In order to calculate the voronoi region of a vertex you need to iterate the 1-ring around it. Then the area of the region is defined as:
A = 1/8 * (sum for every adjacent vertex p_i) { (cot alpha_i + cot beta_i) * (p_i - c).Length² }
In the image you can see the whole voronoi region in light red. A part of it is shown in dark red. This is one of the parts accumulated by the sum. alpha and beta are the angles as visible in the image. c is the center vertex position. p_i is the opposite vertex_position. alpha, beta and p_i change while iterating. c keeps its value.
If you calculate those parts for every adjacent vertex, you get 8 times the area of the voronoi region.

Find a set of points of a circle draped on a 3D height map

I have a height map of NxN values.
I would like to find, given a point A (the red dot), whose x and y coordinates are given (and z is known from the data, so A is a vertex of the surface) a set of points that lie on the circumference of the circle with center in A and radius R that are a good approximation of a circular "cloth" (in grey) draped on the imaginary surface described by the data points.
The sampling, the reciprocal distances between the set of points that I am trying to find, doesn't need to be uniform, but still I would like to find at least all the points that are an intersection of the edges of the mesh with the circle at distance R from A.
How to find this set of points?
Is this a known problem?
(source: keplero.com)
-- edit
The assumption that Jan is using is right: the samples form a regular rectangular or square grid (in the X-Y plane) aligned with [0,0]. But I would like to take the displacement in the Z direction into account to compute the distance. you can see the height map as a terrain, and the algorithm I am looking for as the instructions to give to an explorer that, traveling just on paths of given latitude or longitude, mark the points that are at distance R from A. Walking distance, that is taking into account all the Z displacements done so far. The explorer climbs and go down in the valleys too.
The trivial algorithm for this would be something like this. We know that given R, the maximum displacement on the x and y axis corresponds to a completely flat surface. If there is no slope, the x,y points will all be in the bounding square Ax-R < x < Ax+r and Ay-R
At this point, it would start traveling to the close cells, since if the perimeter enters the edge of one cell of the grid, it also have to exit that cell.
I reckon this is going to be quite difficult to solve in an exact fashion, so I would suggest trying the straightforward approach of simulating the paths that your explorers would take on the surface.
Given your starting point A and a travel distance d, calculate a circle of points P on the XY plane that are d from A.
For each of the points p in P, intersect the line segment A-p with your grid so that you end up with a sequence of points where the explorer crosses from one grid square to the next, in the order that this would happen if the explorer were travelling from A. These points should then be given a z-coordinate by interpolation from your grid data.
You can thus advance through this point sequence and keep track of the distance travelled so far. Eventually the target distance will be reached - adjust p to be at this point.
P now contains the perimeter that you're looking for. Adjust the sample fidelity (size of P) according to your needs.
Just to clarify - You have a triangulated surface in 3d and, for a given starting vertex Vi in the mesh you would like to find the set of vertices U that are reachable via paths along the surface (i.e. geodesics) with length Li <= R.
One approach would be to transform this to a graph-based problem:
Form the weighted, undirected graph G(V,E), where V is the set of vertices in the triangulated surface mesh and E is the set of edges in this mesh. The edge weight should be the Euclidean (3d) length of each edge. This graph is a discrete distance map - the distance "along the surface" between each adjacent vertex in the mesh.
Run a variant of Dijkstra's algorithm from the starting vertex Vi, only expanding paths with length Li that satisfy the constraint Li <= R. The set of vertices visited U, will be those that can be reached by the shortest (geodesic) path with Li <= R.
The accuracy of this approach should be related to the resolution of the surface mesh - as long as the surface curvature within each element is not too high the Euclidean edge length should be a good approximation to the actual geodesic distance, if not, the surface mesh should be refined in that area.
Hope this helps.

Resources