How do I determine label offset so the label is always on the outside of a polygon? - algorithm

I'm have some vertices of a polygon with labels on them. I want to place the labels so that they are always on the outside of the polygon. So in the image above, all of the labels are fine except for #3 and #4, which I want to be on the bottom, outside of the polygon. So generally, how do I determine, for a particular vertex, how to offset it such that it's outside the polygon?

Since you do not show any code of your own, I will just state some ideas. If you want more details including code, show more effort of your own then ask.
I'll assume here that the polygon is assumed to be a simple polygon--one that does not intersect itself. If a polygon does intersect itself, the definition of its "inside" is not so straightforward and there are multiple definitions of the inside. I will not assume that the polygon is convex--all the interior angles are less than 180°. (That would allow an easier answer.) I'll also assume that you want the center of your label to be outside the polygon but will allow a corner or small part of the label to be inside.
First, traverse the polygon and find its "winding angle," the amount the direction angle changes during the traversal. If the polygon is simple, the angle will be either +180° or -180°. One of those means you traversed the polygon clockwise, the other one means counter-clockwise. (Which is which depends on your coordinate system: Cartesian or graphing or other.)
Then traverse the polygon again. Now that you know the direction of the polygon, at each vertex you can find whether the exterior angle goes clockwise or counterclockwise from the entering segment. Find that direction and the size of the angle, then move half that angle in that direction. Move a given distance from the vertex along that angle, and you have the position of the center of your label.
That should work well for the vast majority of polygons. In some edge cases for non-convex polygons, that location moved away from the polygon into another part of the polygon. You then reduce the distance the label is from its vertex until the label moves back into the polygon's outside.
I gave an answer to a related question: How do I efficiently determine if a polygon is convex, non-convex or complex?.

On every vertex, the incoming and outgoing edges form an angle that covers some sector. You can place the label at some distance along the bisector of this angle. You find the direction vector of the bisector by adding two unit vectors originating from the vertex in the directions of the edges.
Finding the correct bisector side requires some care. In the first place, you need to orient the polygon, i.e. check if it is clockwise or counterclockwise. This is simply done by computing the area with the shoelace formula and testing the sign.
Then, if I am right, you can test the area of triangle formed by the two edges and compare its sign to that of the whole polygon. This tells you if the angle is convex or reflex, and you know the proper side. For a convex polygon, the side is always negative for a vector computed as above.

Maybe follow something like this: First, make sure the polygon does not self-intersect, see the previous answers. Then, let the polygon be counter-clockwise oriented and represented by an array (a 2 by n+2 matrix) of its vertices (the vertices are traversed in counter-clockwise order)
double P[n+2][2] = {{pxn, pyn}, {px1, py1}, {px2, py2}, ..., {pxn, pyn}, {px1, py1}};
double Label_position[n][2];
void generate_labels(const double (&P)[n+2][2], double (&Label_position)[n][2]){
double v1[2];
double v2[2];
for(j = 1, j <= n, j = j+1) {
v1[0] = P[j][0] - P[j-1][0];
v1[1] = P[j][1] - P[j-1][1];
v2[0] = P[j+1][0] - P[j][0];
v2[1] = P[j+1][1] - P[j][1];
v1 = normalize(v1);
v2 = normalize(v2);
t = add_vectors(v1, v2);
t = normalize(t);
Label_position[j][0] = t[1] + P[j][0];
Label_position[j][1] = - t[0] + P[j][1];
}
}
This function generates the coordinates of the points at the tips of the unit angle bisector vectors pointing in the exterior of the polygon (see Yves Daoust's answer and the picture he has generated).

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:

Shifting Polygon inside Polygon

I'm having troubles figuring out an algorithm to solve the following problem:
I want the user to be able to snap the rectangle (Could be any type of polygon) to the 4 corners of the polygon such that it's as far inside the polygon as it can be.
What I'm trying so far:
Allow user to get the object.
Find the nearest vertice on the polygon to the rectangle.
Find the furthest vertice on the rectangle to the polygon's nearest vertice.
Use a plane to find the first intersection point with the furthest rectangle's point to the polygon's point.
Shift up or down using the corresponding x/y plane based on whether the furthest point is further in the x/y coordinate.
Keep repeating the steps above until everything is inside.
As long as the enclosing polygon is convex, you can write this as a linear programming problem and then apply https://en.wikipedia.org/wiki/Simplex_algorithm to find the answer. The smaller polygon you are putting inside can be as complicated as you want.
Your inequalities are all of the conditions to make sure that each vertex of the smaller polygon is inside of the larger. You don't have to be clever here, there is no cost to extra inequalities that don't come into play.
The function to optimize is constructed as follows. Look at the interior angle of the vertex you are trying to get close to. Draw a coordinate system at that point with one axis pointing directly into the polygon (call that axis y) and the other at right angles to the first (call that axis x). You want to minimize the y value of the nearest vertex on the polygon you are putting in. (Just put the polygon you are putting in in the middle, and search for the nearest vertex. Use that.
The solution that you find will be the one that puts the two vertices as close together as possible subject to the condition that the smaller polygon has to be inside of the larger.

GJK algorithm in case of circle and polygon

I wonder if it's possible to modify GJK so I can use it to detect collision between circle and n points polygon.
I implemented for polygon and polygon before, and I tried modifying the support function (to get furthest point of a circle), but apparently, this results in infinite loop.
Is this possible to do? do I need to change other thing than the support function? (because I don't see any)
Here's what I use to get furthest point in a circle btw
maxPointCircle = new Vector2(circle.center).add(new Vector2(direction).mul(circle.radius));
GJK algorithm actually tells you the minimal distance between two polygons (and when the distance is 0 it's a collision).
So for a polygon and a circle, you can use GJK to find the minimal distance between that polygon, and the center point of the circle, then compare that distance with the radius of the circle. If the distance is less than or equal to the radius, then it's a collision.
The algorithm looks like:
collision(polygon1, circle):
polygon2 = dummy polygon of only one point, which is the center of the circle
distance = GJK(polygon1, polygon2)
if distance <= circle.radius:
return true
else:
return false

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.

Arduino convex hull algorithm

I am working on a project using an Arduino that needs to calculate the area of a polygon made up of many points. I use surveyor's theorem,
But the points are in random order, not (counter)clockwise. Some make lines that cross, and they make polygons like a bow-tie or hourglass, which don't work for the surveyor's theorem, so I need to sort them in (counter)clockwise order. what is the easiest way to do this?
You don't need to find the convex hull. Just use the area formula from a bunch of points ordered counterclockwise:
http://en.wikipedia.org/wiki/Polygon#Area_and_centroid
float totalArea = 0.0;
for(i=0; i<N; i++) {
float parallelogramArea = (point[i].x*point[i+1].y - point[i+1].x*point[i].y)
float triangleArea = parallelogramArea / 2.0;
totalArea += triangleArea;
}
// or divide by 2 out here for efficiency
The area formula comes from taking each edge AB, and calculating the (signed) area between the edge and the origin (triangle ABO) by taking the cross-product (which gives you the area of a parallelogram) and cutting it in half (factor of 1/2). As one wraps around the polygon, these positive and negative triangles will overlap, and the area between the origin and the polygon will be cancelled out and sum to 0, while only the area inside remains. This is why the formula is called the Surveyor's Formula, since the "surveyor" is at the origin; if going counterclockwise, positive area is added when going left->right and negative area is added when going right->left, from the perspective of the origin.
The mathematical formula is given below, but does not provide the intuition behind it (as given above):
edit (after question has been changed)
There is absolutely no way to "get their order" without additional assumptions, e.g. "the polygon is convex".
If the polygon is concave, it becomes nearly impossible in the general case without lots of extra assumptions (proof: consider a point which lies within the convex hull, but whose neighbors do not; there are many possible valid polygons you can construct using that point, its neighbors, and their neighbors).
If the polygon is convex, all you need to do is sort by the angle from some arbitrary point inside the polygon (e.g. centroid of three arbitrary points).
You could find the center of gravity (cx,cy) of the points and then calculate the angles of the points relative to (cx,cy).
angle[i] = atan2(y[i]-cy, x[i]-cx) ;
Then sort the points by angle.
Just beware that a random set of points does not describe a single unique polygon. So this method will just give you one of the possible polygons, and not necessarily the polygon you would have obtained if you had manually connected the dots.

Resources