I have implemented a polygon triangulation algorithm into my program. The algorithm first takes a simple polygon, described as 2D points/vertices, and splits it into y-monotone pieces.
After this is done, the algorithm then takes each monotone polygon piece and splits it into triangular pieces.
The input to the algorithm I need help with is an array of vertices outlining a y-monotonic polygon in clockwise or counter-clockwise order. The output is a collection of all edges, both from the original polygon but also the new ones added by the triangulation algorithm in order to split this y-monotonic piece into triangular pieces. This works great if I only want to paint the result as it's possible to just draw each edge.
However, there is no particular order to this collection of edges, and I need the output to be an array of type triangle strip or an array where each triangle is simply described by it's 3 vertices (Example: [a1,a2,a3,b1,b2,b3] as we have triangle a and triangle b).
Is there any conventional algorithm or any other way which can help me solve this? Speed is not of extreme importance but I'd prefer a fast solution.
Here is a pseudocode example of the program used in order to give a better understanding of my question:
class Vertex {
var point;
var index;
init(x,y,index){
point = (x,y)
self.index = index
}
}
class Edge {
var from; // of type Vertex
var to; // of type Vertex
init(from, to){
self.from = from
self.to = to
}
}
A call to a function getMonotonePiecesOfPolygon(polygon) could generate one of many monotonic polygon pieces that would have these vertices and edges:
var vertex0 = Vertex(x0,y0,0)
var vertex1 = Vertex(x1,y1,1)
var vertex2 = Vertex(x2,y2,2)
var vertex3 = Vertex(x3,y3,3)
var edge0 = Edge(vertex0, vertex1)
var edge1 = Edge(vertex1, vertex2)
var edge2 = Edge(vertex2, vertex3)
var edge3 = Edge(vertex3, vertex4)
This could correspond to a rectangle like this:
edge0
0------1
| |
edge3 | | edge1
| |
3------2
edge2
Then, we can supply these edges in an array and divide the polygon into triangles with the a triangulation method 'makeMonotonePolygonIntoTrianglePieces()'
var edgesArray = [edge0,edge1,edge2,edge3]
var edgesArray = makePolygonIntoTrianglePieces(edgesArray)
Now the edgesArray could look like this:
edgesArray == [edge0,edge1,edge2,edge3,newEdge1]
where
newEdge1.from == vertex0
newEdge1.to == vertex2
which would correspond to a polygon like this if we were to draw each edge:
edge0
0------1
|\ |
edge3 | \ | edge1
| \ |
3------2
edge2
Now here is the part of what my question is seeking to solve. I need an array which would look like this:
var triangles = [
vertex0,vertex3,vertex2,
vertex0,vertex2,vertex1
]
or using triangle strips (each triangle described by one new vertex and two vertices from the previous triangle):
var triangles = [
vertex3, vertex2, vertex0,
vertex1
]
In Chapter 2 of the book you refer to in one of the comments, they present a data structure called Doubly-Connected Edge List (DCEL). DCEL:s are used to store planar subdivisions such as triangulations. From what you write, I think it could be what you are looking for.
https://en.wikipedia.org/wiki/Doubly_connected_edge_list
Related
What I want to do is replicate a data-structure produced by processing the output from d3-voronoi, but by using d3-delaunay instead.
The data-structure in question is the one produced by this makeMesh function:
function makeMesh(pts, extent) {
extent = extent || defaultExtent;
var vor = voronoi(pts, extent);
var vxs = [];
var vxids = {};
var adj = [];
var edges = [];
var tris = [];
for (var i = 0; i < vor.edges.length; i++) {
var e = vor.edges[i];
if (e == undefined) continue;
var e0 = vxids[e[0]];
var e1 = vxids[e[1]];
if (e0 == undefined) {
e0 = vxs.length;
vxids[e[0]] = e0;
vxs.push(e[0]);
}
if (e1 == undefined) {
e1 = vxs.length;
vxids[e[1]] = e1;
vxs.push(e[1]);
}
adj[e0] = adj[e0] || [];
adj[e0].push(e1);
adj[e1] = adj[e1] || [];
adj[e1].push(e0);
edges.push([e0, e1, e.left, e.right]);
tris[e0] = tris[e0] || [];
if (!tris[e0].includes(e.left)) tris[e0].push(e.left);
if (e.right && !tris[e0].includes(e.right)) tris[e0].push(e.right);
tris[e1] = tris[e1] || [];
if (!tris[e1].includes(e.left)) tris[e1].push(e.left);
if (e.right && !tris[e1].includes(e.right)) tris[e1].push(e.right);
}
var mesh = {
pts: pts,
vxs: vxs,
adj: adj,
tris: tris,
edges: edges,
extent: extent
}
mesh.map = function (f) {
var mapped = vxs.map(f);
mapped.mesh = mesh;
return mapped;
}
return mesh;
}
I've been trying to solve this for a while now and have finally made some progress here on observablehq:
https://observablehq.com/#folcon/original-code-by-martin-oleary-mewo2
I'm assessing how well it works by comparing the rendered images:
I want to produce these smooth colour transitions, which requires a correct mapping between vertices, heightmap and triangles.
Attempt 1:
Well I got something rendered, if my approach below doesn't work, it might be instructive to come back to this.
Attempt 2:
This one I feel is a lot better, I don't appear to be getting extra triangles (no black ones visible), but the issue appears to be I'm rendering in the wrong order. I was attempting to go from the leftmost point and then walk around the cell edges, this seems like the right idea, but the ordering is wrong...
I've dug into the delunator's guide to datastructures and at this point I feel like I'm pretty close, but missing something obvious.
Useful Notes / Assumptions:
The mesh uses the edges of the cells, not the triangles for "edges", if you look at the adj (adjacencies) it's never higher than 3, which is in keeping with using the cells as each vertex of the cell has no more than 3 neighbours.
Given that an edge is an edge of a cell, the left and the right of the edge should therefore be the two cells that edge sits on.
Hopefully that's clear.
To answer #thclark:
The Output Data Structure:
{
pts: pts,
vxs: vxs,
adj: adj,
tris: tris,
edges: edges,
extent: extent
}
pts is the original points array, which is a list of pairs of [x, y] coordinates.
vxs is the vertices of the cell as pairs of [x, y] coordinates. The points here are unique, and their index in the array is the authoritative id for that vertex.
I will use one of redblobgame's images to clarify:
The red dots in this image are the original points, the blue one's are the vertices of the cell.
edges is are comprised of 4 values, the first two are the indexes of the vertices that make up the edges of a cell, which in the image above are in white. The second two are the circumcenter's corresponding left and right cells which I've illustrated in an image above, but hopefully this will be clearer:
adj is a mapping from a vertex index to the other vertex index's which are connected to it, because we're using the cells, no entry in adj should have more than 3 indexes. As you can see in the image below, each vertex (red), can only have 3 neighbours (green).
tris is an array of triangles, the original data-structure does not always have complete triangles, but they indexed by the vertex index, to the circumcenter's corresponding left and right cells.
You can see in the above image, that by combining the left and right circumcenter's of three edges, it describes a triangle.
The Input Data Structure:
Delaunator's data-structures guide has a lot more detail, but a quick overview is this:
Delaunator takes an array of [x, y] coordinates of length N and makes a points array of length 2N where each coordinate in the original [x, y] array now sits at it's original index * 2 if it was the x coord, and * 2 + 1 if it was the y.
For example, the coords [[1, 2], [3, 4], [5, 6]] would become: [1, 2, 3, 4, 5, 6].
It then builds a delaunay triangulation, where each triangle edge is comprised of two halfedge's.
A halfedge takes a bi-directional edge and splits it into two directional edges.
So a triangle made up of 3 edges, now has 6 halfedges like so:
It also constructs two arrays:
delaunay.triangles which takes a halfedge index and returns the point id (an index into the points array described previously) where the halfedge begins.
delaunay.halfedges which takes a halfedge index and returns the opposite halfedge in the adjacent triangle:
Hopefully that's sufficient detail?
I've tried to make the setup runnable, so if someone wants to poke around with it to test out a quick hypothesis, they can do so easily, just edit the notebook or fork it.
I've also added at the bottom a more complete example that's purely focused on the "physical" things the map derives from the mesh+heightmap, which is basically the coastline and rivers.
I have a list of vertices that define a polygon, a list of perimeter edges that connect those vertices and define the outline of the polygon, and a list of inner edges connecting vertices, effectively splitting the polygon. None of the edges intersect each other (they only meet at the start and end points).
I want to partition the bigger polygon into its smaller components by splitting it at the inner edges. Basically I need to know which sets of vertices are part of a polygon that has no intersecting edges.
Basically, this is the information I have:
The vertices [0, 1, 3, 5, 7, 9, 11, 13, 15] define the outside perimeter of my polygon and the blue edge (5, 13) is an inner edge splitting that perimeter into two polygons. (Disregard the horizontal purple lines, they are the result of the trapezoidalization of the polygon to find the edge (5, 13). They have no further meaning)
This is what I want to get to:
One polygon is defined by the vertices [0, 1, 3, 5, 13, 15] and the other is defined by [5, 7, 9, 11, 13].
I need a solution that works for any number of splitting inner edges.
In the end, I would like to be able to partition a polygon like the following:
The sub-polygons are not necessarily convex. That might be of importance for any algorithm depending on it. The red sub-polygon has a concave vertex (13) for example.
My idea initially was to traverse the inside of each sub-polygon in a clockwise or counter-clockwise direction, keeping track of the vertices I encounter and their order. However I am having trouble finding a starting edge/vertex and guaranteeing that the next cw or ccw point is in fact on the inside of that sub-polygon I want to extract.
I tried to google for solutions but this is a field of mathematics I am too unfamiliar with to know what to search for. An idea/algorithm of how to tackle this problem would be much appreciated!
(I don't need any actual code, just an explanation of how to do this or pseudocode would totally suffice)
Now, unfortunately I don't have some code to show as I need a concept to try out first. I don't know how to tackle this problem and thus can't code anything that could accomplish what I need it to do.
EDIT:
This is just one step in what I am trying to do ultimately, which is polygon triangulation. I have read numerous solutions to that problem and wanted to implement it via trapezoidalization to get monotone polygons and ultimately triangulate those. This is basically the last step of (or I guess the next step after) the trapezoidalization, which is never explained in any resources on the topic that i could find.
The end result of the trapezoidalization are the inner edges which define the split into (in my case vertically monotone) polygons. I just need to split the polygons along those edges "datastructurally" so I can work on the subpolygons individually. I hope that helps to clarify things.
The key of the algorithm that you need, is to know how edges can be ordered:
Finding out which is the next edge in (counter)clockwise order
You can calculate the absolute angle, of an edge from node i to node j, with the following formula:
atan2(jy-iy, jx-ix)
See atan2
The relative angle between an edge (i, j) and (j, k) is then given by:
atan2(ky-jy, kx-jx) - atan2(jy-iy, jx-ix)
This expression may yield angles outside of the [-𝜋, 𝜋] range, so you should map the result back into that range by adding or subtracting 2𝜋.
So when you have traversed an edge (i, j) and need to choose the next edge (j, k), you can then pick the edge that has the smallest relative angle.
The Algorithm
The partitioning algorithm does not really need to know upfront which edges are internal edges, so I'll assume you just have an edge list. The algorithm could look like this:
Create an adjacency list, so you have a list of neighboring vertices for every vertex.
Add every edge to this adjacency list in both directions, so actually adding two directed edges for each original edge
Pick a directed edge (i, j) from the adjacency list, and remove it from there.
Define a new polygon and add vertex i as its first vertex.
Repeat until you get back to the first vertex in the polygon that's being constructed:
add vertex j to the polygon
find vertex k among the neighbors of vertex j that is not vertex i, and which minimizes the relative angle with the above given formula.
add this angle to a sum
Delete this directed edge from the neighbors of vertex j, so it will never be visited again
let i = j, j = k
If the sum of angles is positive (it will be 2𝜋) then add the polygon to the list of "positive" polygons, otherwise (it will be -2𝜋) add it to an alternative list.
Keep repeating from step 2 until there are no more directed edges in the adjacency list.
Finally you'll have two lists of polygons. One list will only have one polygon. This will be the original, outer polygon, and can be ignored. The other list will have the partitioning.
As a demo, here is some code in a runnable JavaScript snippet. It uses one of the examples you pictured in your question (but will consecutive vertex numbering), finds the partitioning according to this algorithm, and shows the result by coloring the polygons that were identified:
function partition(nodes, edges) {
// Create an adjacency list
let adj = [];
for (let i = 0; i < nodes.length; i++) {
adj[i] = []; // initialise the list for each node as an empty one
}
for (let i = 0; i < edges.length; i++) {
let a = edges[i][0]; // Get the two nodes (a, b) that this edge connects
let b = edges[i][1];
adj[a].push(b); // Add as directed edge in both directions
adj[b].push(a);
}
// Traverse the graph to identify polygons, until none are to be found
let polygons = [[], []]; // two lists of polygons, one per "winding" (clockwise or ccw)
let more = true;
while (more) {
more = false;
for (let i = 0; i < nodes.length; i++) {
if (adj[i].length) { // we have unvisited directed edge(s) here
let start = i;
let polygon = [i]; // collect the vertices on a new polygon
let sumAngle = 0;
// Take one neighbor out of this node's neighbor list and follow a path
for (let j = adj[i].pop(), next; j !== start; i = j, j = next) {
polygon.push(j);
// Get coordinates of the current edge's end-points
let ix = nodes[i][0];
let iy = nodes[i][1];
let jx = nodes[j][0];
let jy = nodes[j][1];
let startAngle = Math.atan2(jy-iy, jx-ix);
// In the adjacency list of node j, find the next neighboring vertex in counterclockwise order
// relative to node i where we came from.
let minAngle = 10; // Larger than any normalised angle
for (let neighborIndex = 0; neighborIndex < adj[j].length; neighborIndex++) {
let k = adj[j][neighborIndex];
if (k === i) continue; // ignore the reverse of the edge we came from
let kx = nodes[k][0];
let ky = nodes[k][1];
let relAngle = Math.atan2(ky-jy, kx-jx) - startAngle; // The "magic"
// Normalise the relative angle to the range [-PI, +PI)
if (relAngle < -Math.PI) relAngle += 2*Math.PI;
if (relAngle >= Math.PI) relAngle -= 2*Math.PI;
if (relAngle < minAngle) { // this one comes earlier in counterclockwise order
minAngle = relAngle;
nextNeighborIndex = neighborIndex;
}
}
sumAngle += minAngle; // track the sum of all the angles in the polygon
next = adj[j][nextNeighborIndex];
// delete the chosen directed edge (so it cannot be visited again)
adj[j].splice(nextNeighborIndex, 1);
}
let winding = sumAngle > 0 ? 1 : 0; // sumAngle will be 2*PI or -2*PI. Clockwise or ccw.
polygons[winding].push(polygon);
more = true;
}
}
}
// return the largest list of polygons, so to exclude the whole polygon,
// which will be the only one with a winding that's different from all the others.
return polygons[0].length > polygons[1].length ? polygons[0] : polygons[1];
}
// Sample input:
let nodes = [[59,25],[26,27],[9,59],[3,99],[30,114],[77,116],[89,102],[102,136],[105,154],[146,157],[181,151],[201,125],[194,83],[155,72],[174,47],[182,24],[153,6],[117,2],[89,9],[97,45]];
let internalEdges = [[6, 13], [13, 19], [19, 6]];
// Join outer edges with inner edges to an overall list of edges:
let edges = nodes.map((a, i) => [i, (i+1)%nodes.length]).concat(internalEdges);
// Apply algorithm
let polygons = partition(nodes, edges);
// Report on results
document.querySelector("div").innerHTML =
"input polygon has these points, numbered 0..n<br>" +
JSON.stringify(nodes) + "<br>" +
"resulting polygons, by vertex numbers<br>" +
JSON.stringify(polygons)
// Graphics handling
let io = {
ctx: document.querySelector("canvas").getContext("2d"),
drawEdges(edges) {
for (let [a, b] of edges) {
this.ctx.moveTo(...a);
this.ctx.lineTo(...b);
this.ctx.stroke();
}
},
colorPolygon(polygon, color) {
this.ctx.beginPath();
this.ctx.moveTo(...polygon[0]);
for (let p of polygon.slice(1)) {
this.ctx.lineTo(...p);
}
this.ctx.closePath();
this.ctx.fillStyle = color;
this.ctx.fill();
}
};
// Display original graph
io.drawEdges(edges.map(([a,b]) => [nodes[a], nodes[b]]));
// Color the polygons that the algorithm identified
let colors = ["red", "blue", "silver", "purple", "green", "brown", "orange", "cyan"];
for (let polygon of polygons) {
io.colorPolygon(polygon.map(i => nodes[i]), colors.pop());
}
<canvas width="400" height="180"></canvas>
<div></div>
I would like to draw a d3 voronoi diagram and clip it like d3 hull borders do bound a collection of nodes or any similar clipping.
The red line in this Screenshot shows what i would like to achieve.
How can it be done ?
The d3.geom.hull function will find a polygon that contains all your nodes tightly, with no extra spacing. That of course would cancel out much of the purpose of the Voronoi regions, which are intended to add some active space around the nodes. So what you need to calculate is a polygon that is a certain padding distance larger than the convex hull polygon on all sides.
My recommended algorithm:
Use d3.geom.hull(nodes) to calculate the array of vertices that define the tight boundary of your nodes.
Use those vertices to create a d3 polygon object.
Calculate the center of that polygon with .centroid().
For each vertex in your convex hull, calculate a point that is padding distance farther away from the center of the polygon.
Use this expanded polygon to clip all the polygons in the array returned by Voronoi function.
Sample code:
var hullFunction = d3.geom.hull()
.x(/*x accessor function*/)
.y(/*y accessor function*/);
var tightHull = hullFunction(nodes); //returns an array of vertices
var centerPoint = d3.geom.polygon(tightHullArray).centroid();
var expandedHull = tightHullArray.map( function(vertex) {
//Create a new array of vertices, each of which is the result
//of running this function on the corresponding vertex of the
//original hull.
//Each vertex is of the form [x,y]
var vector = [vertex[0] - centerPoint[0],
vertex[1] - centerPoint[1] ];
//the vector representing the line from center to this point
var vectorLength = Math.sqrt(vector[0]*vector[0]
+ vector[1]*vector[1]);
//Pythagorus' theorem to get the length of the line
var normalizedVector = [vector[0] / vectorLength,
vector[1] / vectorLength];
//the vector scaled down to length 1, but with the same angle
//as the original vector
return [vertex[0] + normalizedVector[0]*padding,
vertex[1] + normalizedVector[1]*padding ];
//use the normalized vector to adjust the vertex point away from
//the center point by a distance of `padding`
});
var clippedVoronoi = voronoiPolygons.map(function(voronoi) {
//voronoiPolygons would be the array returned by the voronoi function
return expandedHull.clip(voronoi);
//I think this is correct; if you get weird results, try
// return voronoi.clip(expandedHull);
});
I recently made an example to illustrate to myself how polygon clipping works:
http://tributary.io/inlet/8263747
You can see the clipping code in the update function, and the rendering code in the process function. drag the points around to see how the clipping will be affected.
A couple things to watch out for:
the order of the points in the "hull" (or clipping path in my example) matters. Your polygon has to be counter-clockwise as well as convex (no caves). If these conditions aren't met there is no error, you just get back an empty array.
the polygon operations manipulate your point arrays in place, if you don't want your geometry itself clipped, but want a copy you need to make a copy first.
First of all let me clear .. I am not asking about 2D mesh, to determine the winding order of 2D mesh its very easy with normal-z direction.
Second is, I am not asking any optimized algorithm, I do not worry about the time or speed, I just want to do it with my mesh.
When I triangulate a 3D object using Greedy Projection Triangulation algorithm, This problem happens.
check the attached images.
If I apply 2D approaches to this model using "Calculate Signed Area" or "Cross production of AB and BC vectors of a triangle", it only solves the 2D mesh but how about a 3D mesh?
First we need to check that which triangles are in wrong winding direction in 3D mesh, then we only consider those triangles, so the issue is, how can we check that which triangles are in wrong winding direction in 3D? We can not just do with 2D approach I have tested it and but no success.
For example in case of a sphere, we can not apply 2D approach to sphere.
So is there any way to solve this issue ?
Thanks.
Update # 1:
Below is the algorithm to check which edge has the same winding. It doesn't work well, I don't know why. Theoretically it should correct all the triangles but it is not correcting. For example in case of a sphere check in the attached figure. Something is wrong with it.
void GLReversedEdge(int i, int j, GLFace *temp)
{
//i'th triangle
int V1 = temp[i].v1;
int V2 = temp[i].v2;
int V3 = temp[i].v3;
//i'th triangle edges
int E1[] ={V1, V2};
int E2[] ={V2, V3};
int E3[] ={V3, V1};
//adjacent triangle
int jV1 = temp[j].v1;
int jV2 = temp[j].v2;
int jV3 = temp[j].v3;
//adjacent edges
int jE1[] ={jV1, jV2};
int jE2[] ={jV2, jV3};
int jE3[] ={jV3, jV1};
// 1st edge of adjacent triangle is checking with all edges of ith triangle
if((jE1[0] == E1[0] && jE1[1] == E1[1]) ||
(jE1[0] == E2[0] && jE1[1] == E2[1]) ||
(jE1[0] == E3[0] && jE1[1] == E3[1]))
{
temp[j].set(jV2, jV1, jV3); // 1st edges orientation is same, so reverse/swap it
}
// 2nd edge of adjacent triangle is checking with all edges of ith triangle
else if((jE2[0] == E1[0] && jE2[1] == E1[1]) ||
(jE2[0] == E2[0] && jE2[1] == E2[1]) ||
(jE2[0] == E3[0] && jE2[1] == E3[1]))
{
temp[j].set(jV1, jV3, jV2); // 2nd edges orientation is same, so reverse/swap it
}
// 3rd edge of adjacent triangle is checking with all edges of ith triangle
else if((jE3[0] == E1[0] && jE3[1] == E1[1]) ||
(jE3[0] == E2[0] && jE3[1] == E2[1]) ||
(jE3[0] == E3[0] && jE3[1] == E3[1]))
{
temp[j].set(jV3, jV2, jV1); // 3rd edges orientation is same, so reverse/swap it
}
}
void GetCorrectWindingOfMesh()
{
for(int i=0; i<nbF; i++)
{
int j1 = AdjacentTriangleToEdgeV1V2;
if(j1 >= 0) GLReversedEdge(i, j1, temp);
int j2 = AdjacentTriangleToEdgeV2V3;
if(j2 >= 0) GLReversedEdge(i, j2, temp);
int j3 = AdjacentTriangleToEdgeV3V1;
if(j3 >= 0) GLReversedEdge(i, j3, temp);
}
}
To retrieve neighboring information lets assume we have method that returns neighbor of triangle on given edge neighbor_on_egde( next_tria, edge ).
That method can be implemented with information for each vertex in which triangles it is used. That is dictionary structure that maps vertex index to list of triangle indices. It is easily created by passing through list of triangles and setting for each triangle vertex index of triangle in right dictionary element.
Traversal is done by storing which triangles to check for orientation and which triangles are already checked. While there are triangles to check, make check on it and add it's neighbors to be checked if they weren't checked. Pseudo code looks like:
to_process = set of pairs triangle and orientation edge
initial state is one good oriented triangle with any edge on it
processed = set of processed triangles; initial empty
while to_process is not empty:
next_tria, orientation_edge = to_process.pop()
add next_tria in processed
if next_tria is not opposite oriented than orientation_edge:
change next_tria (ABC) orientation (B<->C)
for each edge (AB) in next_tria:
neighbor_tria = neighbor_on_egde( next_tria, edge )
if neighbor_tria exists and neighbor_tria not in processed:
to_process add (neighbor_tria, edge opposite oriented (BA))
Does your mesh include edge adjacency information? i.e. each triangle T contains three vertices A,B,C and three edges AB, BC and CA, where AB is a link to the triangle T1 which shares common vertices A,B and includes a new vertex D. Something like
struct Vertex
{
double x,y,z;
};
struct Triangle
{
int vertices[3],edges[3];
};
struct TriangleMesh
{
Vertex Vertices[];
Triangle Triangles[];
};
If this is the case, for any triangle T = {{VA,VB,VC},{TAB,TBC,TCA}} with neighbour TE = &TAB at edge AB, A and B must appear in the reverse order for T and TE to have the same winding. e.g. TAB = {{VB,VA,VD},{TBA,TAD,TDA}} where TBA = &T. This can be used to give all the triangles the same winding.
How would I "inflate" a polygon? That is, I want to do something similar to this:
The requirement is that the new (inflated) polygon's edges/points are all at the same constant distance from the old (original) polygon's (on the example picture they are not, since then it would have to use arcs for inflated vertices, but let's forget about that for now ;) ).
The mathematical term for what I'm looking for is actually inward/outward polygon offseting. +1 to balint for pointing this out. The alternative naming is polygon buffering.
Results of my search:
Here are some links:
A Survey of Polygon Offseting Strategies
Polygon offset, PROBLEM
Buffering Polygon Data
I thought I might briefly mention my own polygon clipping and offsetting library - Clipper.
While Clipper is primarily designed for polygon clipping operations, it does polygon offsetting too. The library is open source freeware written in Delphi, C++ and C#. It has a very unencumbered Boost license allowing it to be used in both freeware and commercial applications without charge.
Polygon offsetting can be performed using one of three offset styles - squared, round and mitered.
August 2022:
Clipper2 has now been formally released and it supercedes Clipper (aka Clipper1).
The polygon you are looking for is called inward/outward offset polygon in computational geometry and it is closely related to the straight skeleton.
These are several offset polygons for a complicated polygon:
And this is the straight skeleton for another polygon:
As pointed out in other comments, as well, depending on how far you plan to "inflate/deflate" your polygon you can end up with different connectivity for the output.
From computation point of view: once you have the straight skeleton one should be able to construct the offset polygons relatively easily. The open source and (free for non-commercial) CGAL library has a package implementing these structures. See this code example to compute offset polygons using CGAL.
The package manual should give you a good starting point on how to construct these structures even if you are not going to use CGAL, and contains references to the papers with the mathematical definitions and properties:
CGAL manual: 2D Straight Skeleton and Polygon Offsetting
For these types of things I usually use JTS. For demonstration purposes I created this jsFiddle that uses JSTS (JavaScript port of JTS). You just need to convert the coordinates you have to JSTS coordinates:
function vectorCoordinates2JTS (polygon) {
var coordinates = [];
for (var i = 0; i < polygon.length; i++) {
coordinates.push(new jsts.geom.Coordinate(polygon[i].x, polygon[i].y));
}
return coordinates;
}
The result is something like this:
Additional info: I usually use this type of inflating/deflating (a little modified for my purposes) for setting boundaries with radius on polygons that are drawn on a map (with Leaflet or Google maps). You just convert (lat,lng) pairs to JSTS coordinates and everything else is the same. Example:
Sounds to me like what you want is:
Starting at a vertex, face anti-clockwise along an adjacent edge.
Replace the edge with a new, parallel edge placed at distance d to the "left" of the old one.
Repeat for all edges.
Find the intersections of the new edges to get the new vertices.
Detect if you've become a crossed polygon and decide what to do about it. Probably add a new vertex at the crossing-point and get rid of some old ones. I'm not sure whether there's a better way to detect this than just to compare every pair of non-adjacent edges to see if their intersection lies between both pairs of vertices.
The resulting polygon lies at the required distance from the old polygon "far enough" from the vertices. Near a vertex, the set of points at distance d from the old polygon is, as you say, not a polygon, so the requirement as stated cannot be fulfilled.
I don't know if this algorithm has a name, example code on the web, or a fiendish optimisation, but I think it describes what you want.
In the GIS world one uses negative buffering for this task:
http://www-users.cs.umn.edu/~npramod/enc_pdf.pdf
The JTS library should do this for you. See the documentation for the buffer operation: http://tsusiatsoftware.net/jts/javadoc/com/vividsolutions/jts/operation/buffer/package-summary.html
For a rough overview see also the Developer Guide:
http://www.vividsolutions.com/jts/bin/JTS%20Developer%20Guide.pdf
Each line should split the plane to "inside" and "outline"; you can find this out using the usual inner-product method.
Move all lines outward by some distance.
Consider all pair of neighbor lines (lines, not line segment), find the intersection. These are the new vertex.
Cleanup the new vertex by removing any intersecting parts. -- we have a few case here
(a) Case 1:
0--7 4--3
| | | |
| 6--5 |
| |
1--------2
if you expend it by one, you got this:
0----a----3
| | |
| | |
| b |
| |
| |
1---------2
7 and 4 overlap.. if you see this, you remove this point and all points in between.
(b) case 2
0--7 4--3
| | | |
| 6--5 |
| |
1--------2
if you expend it by two, you got this:
0----47----3
| || |
| || |
| || |
| 56 |
| |
| |
| |
1----------2
to resolve this, for each segment of line, you have to check if it overlap with latter segments.
(c) case 3
4--3
0--X9 | |
| 78 | |
| 6--5 |
| |
1--------2
expend by 1. this is a more general case for case 1.
(d) case 4
same as case3, but expend by two.
Actually, if you can handle case 4. All other cases are just special case of it with some line or vertex overlapping.
To do case 4, you keep a stack of vertex.. you push when you find lines overlapping with latter line, pop it when you get the latter line. -- just like what you do in convex-hull.
Here is an alternative solution, see if you like this better.
Do a triangulation, it don't have to be delaunay -- any triangulation would do.
Inflate each triangle -- this should be trivial. if you store the triangle in the anti-clockwise order, just move the lines to right-hand-side and do intersection.
Merge them using a modified Weiler-Atherton clipping algorithm
Big thanks to Angus Johnson for his clipper library.
There are good code samples for doing the clipping stuff at the clipper homepage at http://www.angusj.com/delphi/clipper.php#code
but I did not see an example for polygon offsetting. So I thought that maybe it is of use for someone if I post my code:
public static List<Point> GetOffsetPolygon(List<Point> originalPath, double offset)
{
List<Point> resultOffsetPath = new List<Point>();
List<ClipperLib.IntPoint> polygon = new List<ClipperLib.IntPoint>();
foreach (var point in originalPath)
{
polygon.Add(new ClipperLib.IntPoint(point.X, point.Y));
}
ClipperLib.ClipperOffset co = new ClipperLib.ClipperOffset();
co.AddPath(polygon, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon);
List<List<ClipperLib.IntPoint>> solution = new List<List<ClipperLib.IntPoint>>();
co.Execute(ref solution, offset);
foreach (var offsetPath in solution)
{
foreach (var offsetPathPoint in offsetPath)
{
resultOffsetPath.Add(new Point(Convert.ToInt32(offsetPathPoint.X), Convert.ToInt32(offsetPathPoint.Y)));
}
}
return resultOffsetPath;
}
One further option is to use boost::polygon - the documentation is somewhat lacking, but you should find that the methods resize and bloat, and also the overloaded += operator, which actually implement buffering. So for example increasing the size of a polygon (or a set of polygons) by some value can be as simple as:
poly += 2; // buffer polygon by 2
Based on advice from #JoshO'Brian, it appears the rGeos package in the R language implements this algorithm. See rGeos::gBuffer .
I use simple geometry: Vectors and/or Trigonometry
At each corner find the mid vector, and mid angle. Mid vector is the arithmetic average of the two unit vectors defined by the edges of the corner. Mid Angle is the half of the angle defined by the edges.
If you need to expand (or contract) your polygon by the amount of d from each edge; you should go out (in) by the amount d/sin(midAngle) to get the new corner point.
Repeat this for all the corners
*** Be careful about your direction. Make CounterClockWise Test using the three points defining the corner; to find out which way is out, or in.
There are a couple of libraries one can use (Also usable for 3D data sets).
https://github.com/otherlab/openmesh
https://github.com/alecjacobson/nested_cages
http://homepage.tudelft.nl/h05k3/Projects/MeshThickeningProj.htm
One can also find corresponding publications for these libraries to understand the algorithms in more detail.
The last one has the least dependencies and is self-contained and can read in .obj files.
This is a C# implementation of the algorithm explained in here . It is using Unity math library and collection package as well.
public static NativeArray<float2> ExpandPoly(NativeArray<float2> oldPoints, float offset, int outer_ccw = 1)
{
int num_points = oldPoints.Length;
NativeArray<float2> newPoints = new NativeArray<float2>(num_points, Allocator.Temp);
for (int curr = 0; curr < num_points; curr++)
{
int prev = (curr + num_points - 1) % num_points;
int next = (curr + 1) % num_points;
float2 vn = oldPoints[next] - oldPoints[curr];
float2 vnn = math.normalize(vn);
float nnnX = vnn.y;
float nnnY = -vnn.x;
float2 vp = oldPoints[curr] - oldPoints[prev];
float2 vpn = math.normalize(vp);
float npnX = vpn.y * outer_ccw;
float npnY = -vpn.x * outer_ccw;
float bisX = (nnnX + npnX) * outer_ccw;
float bisY = (nnnY + npnY) * outer_ccw;
float2 bisn = math.normalize(new float2(bisX, bisY));
float bislen = offset / math.sqrt((1 + nnnX * npnX + nnnY * npnY) / 2);
newPoints[curr] = new float2(oldPoints[curr].x + bislen * bisn.x, oldPoints[curr].y + bislen * bisn.y);
}
return newPoints;
}
Thanks for the help in this topic, here's the code in C++ if anyone interested. Tested it, its working. If you give offset = -1.5, it will shrink the polygon.
typedef struct {
double x;
double y;
} Point2D;
double Hypot(double x, double y)
{
return std::sqrt(x * x + y * y);
}
Point2D NormalizeVector(const Point2D& p)
{
double h = Hypot(p.x, p.y);
if (h < 0.0001)
return Point2D({ 0.0, 0.0 });
double inverseHypot = 1 / h;
return Point2D({ (double)p.x * inverseHypot, (double)p.y * inverseHypot });
}
void offsetPolygon(std::vector<Point2D>& polyCoords, std::vector<Point2D>& newPolyCoords, double offset, int outer_ccw)
{
if (offset == 0.0 || polyCoords.size() < 3)
return;
Point2D vnn, vpn, bisn;
double vnX, vnY, vpX, vpY;
double nnnX, nnnY;
double npnX, npnY;
double bisX, bisY, bisLen;
unsigned int nVerts = polyCoords.size() - 1;
for (unsigned int curr = 0; curr < polyCoords.size(); curr++)
{
int prev = (curr + nVerts - 1) % nVerts;
int next = (curr + 1) % nVerts;
vnX = polyCoords[next].x - polyCoords[curr].x;
vnY = polyCoords[next].y - polyCoords[curr].y;
vnn = NormalizeVector({ vnX, vnY });
nnnX = vnn.y;
nnnY = -vnn.x;
vpX = polyCoords[curr].x - polyCoords[prev].x;
vpY = polyCoords[curr].y - polyCoords[prev].y;
vpn = NormalizeVector({ vpX, vpY });
npnX = vpn.y * outer_ccw;
npnY = -vpn.x * outer_ccw;
bisX = (nnnX + npnX) * outer_ccw;
bisY = (nnnY + npnY) * outer_ccw;
bisn = NormalizeVector({ bisX, bisY });
bisLen = offset / std::sqrt((1 + nnnX * npnX + nnnY * npnY) / 2);
newPolyCoords.push_back({ polyCoords[curr].x + bisLen * bisn.x, polyCoords[curr].y + bisLen * bisn.y });
}
}
To inflate a polygon, one can implement the algorithm from "Polygon Offsetting by Computing Winding Numbers" article.
The steps of the algorithm are as follows:
Construct outer offset curve by taking every edge from input polygon and shifting it outside, then connecting shifted edged with circular arches in convex vertices of input polygon and two line segments in concave vertices of input polygon.
An example. Here input polygon is dashed blue, and red on the left - shifted edges, on the right - after connecting them in continuous self-intersecting curve:
The curve divides the plane on a number of connected components, and one has to compute the winding number in each of them, then take the boundary of all connected components with positive winding numbers:
The article proofs that the algorithm is very fast compared to competitors and robust at the same time.
To avoid implementing winding number computation, one can pass self-intersecting offset curve to OpenGL Utility library (GLU) tessellator and activate the settings GLU_TESS_BOUNDARY_ONLY=GL_TRUE (to skip triangulation) and GLU_TESS_WINDING_RULE=GLU_TESS_WINDING_POSITIVE (to output the boundary of positive winding number components).