|
Background and Theory
Polygon is one of the most important objects we are dealing with when we
rendering 2D/3D graphics or processing computational geometry. As a polygon
could be very complicated, some restrictions may be applied on implementation.
For example, OpenGL does not directly support drawing a concave polygon. When
you present OpenGL with a no convex filled polygon, it might not draw as you
expect. In most cases, we need to break down a complex polygon as its composed
simpler shapes, such as a set of triangles to simplify the problem.
In 1975, Gary Meisters developed the Two-Ears Theorem that proved this
attempt is always feasible: Except for triangles, every simple polygon has at
least two non-over lapping ears (triangles)[1].
A simple polygon is a polygon with no two non-consecutive edges intersecting.
If a diagonal (Pi-1, Pi+1) that bridges Pi lies entirely in the polygon, then
the vertex Pi is called an ear. To partitioning the polygon by finding ears, the
following lemma is also useful: If a convex vertex Pi is not an ear, then the
triangle formed by Pi-1, Pi, Pi+1 contains a concave vertex [2].
Figure 1. Polygon and Ears [3]
The following code present here demonstrates an O(kn) time algorithm for
finding ears and partitioning a simple polygon by triangles.
Figure 2. Polygon
Figure 3. Triangulated Polygon
Program Structure and Sample Code:
Based on Gary Meisters' theory, we always can find an ear from a polygon. If
we cut the ear from this polygon, we get a new polygon with one vertex less and
a triangle. Repeat this process with the new polygon till the polygon has only
three vertices left. The flowchart is as following:
Figure 4. Polygon Triangulation Program Flowchart
This program is written in C#.NET and developed with MS Visual Studio 2003.
To use object oriented program technology and make the code re-useable, I
structured the program with following classes:
Figure 5. Program Class Diagram
PolygonCuttingEarInterface Namespace:
The frmCuttingEars is the user interface where to receive the user-selected
polygon vertices, generate an object of CPolygonShape and pass the
data to the object, then retrieving the calculated data and presenting a serious
of triangles to the user. public class frmCuttingEars : System.Windows.Forms.Form
{
…… ……
private System.Windows.Forms.Panel pnlDraw;
private ArrayList m_alSelectedPts=new ArrayList();
private void pnlDraw_MouseUp(object sender, MouseEventArgs e)
{
…… ……
Point clickedPt=new Point(e.X, e.Y);
m_alSelectedPts.Add(clickedPt);
…… ……
}
private void btnCut_Click(object sender, System.EventArgs e)
{
…… ……
CPolygonShape cutPolygon = new CPolygonShape(vertices);
cutPolygon.CutEar();
…… ……
}
public void DrawEarsPolygon(CPolygonShape cutPolygon)
{
…… ……
for (int i=0; i < cutPolygon.NumberOfPolygons; i++)
{
int nPoints=cutPolygon.Polygons(i).Length;
Point[] tempArray=new Point[nPoints];
for (int j=0; j < nPoints; j++)
{
tempArray[j].X= (int)cutPolygon.Polygons(i)[j].X;
tempArray[j].Y= (int)cutPolygon.Polygons(i)[j].Y;
}
Graphics gfx=pnlDraw.CreateGraphics();
int nBrush = i % 3;
gfx.FillPolygon(m_aBrushes[nBrush], tempArray);
…… ……
}
}
}
PolygonCuttingEar Namespace:
The CpolygonShape is the class that does all the calculation:
find a polygon's ear, cut the ear by deleting the vertex and make an updated
polygon. This process will be repeated till the updated polygon is a
triangle. public class CPolygonShape
{
…… ……
private CPoint2D[] m_aUpdatedPolygonVertices;
private CPoint2D[][] m_aPolygons;
public int NumberOfPolygons
{
get
{
return m_aPolygons.Length;
}
}
public CPoint2D[] Polygons(int index)
{
if (index < m_aPolygons.Length)
return m_aPolygons[index];
else
return null;
}
private bool IsEarOfUpdatedPolygon(CPoint2D vertex )
{
CPolygon polygon=new CPolygon(m_aUpdatedPolygonVertices);
……
bool bEar=true;
if (polygon.PolygonVertexType(vertex)=VertexType.ConvexPoint)
{
CPoint2D pi=vertex;
CPoint2D pj=polygon.PreviousPoint(vertex);
CPoint2D pk=polygon.NextPoint(vertex);
for (int i=m_aUpdatedPolygonVertices.GetLowerBound(0);
i < m_aUpdatedPolygonVertices.GetUpperBound(0); i++)
{
CPoint2D pt=m_aUpdatedPolygonVertices[i];
if ( !(pt.EqualsPoint(pi)||
pt.EqualsPoint(pj)||pt.EqualsPoint(pk)))
{
if (TriangleContainsPoint(new CPoint2D[] {pj, pi, pk}, pt))
bEar=false;
}
}
}
else
bEar=false;
return bEar;
}
public void CutEar()
{
CPolygon polygon=new CPolygon(m_aUpdatedPolygonVertices);
bool bFinish=false;
CPoint2D pt=new CPoint2D();
while (bFinish==false)
{
int i=0;
bool bNotFound=true;
while (bNotFound
&& (i < m_aUpdatedPolygonVertices.Length))
{
pt=m_aUpdatedPolygonVertices[i];
if (IsEarOfUpdatedPolygon(pt))
bNotFound=false;
else
i++;
}
if (pt !=null)
UpdatePolygonVertices(pt);
polygon=new CPolygon(m_aUpdatedPolygonVertices);
if (m_aUpdatedPolygonVertices.Length==3)
bFinish=true;
}
SetPolygons();
}
}
GeometryUtility Namespace:
This namespace includes many generic computational geometry classes and lots
of functions that could be re-used for other projects. public class CPoint2DD
{
private double m_dCoordinate_X;
private double m_dCoordinate_Y;
public bool InLine(CLineSegment lineSegment) {};
public bool InsidePolygon(CPoint2D[] polygonVertices);
public double DistanceTo(CPoint2D point) {};
…… ……
}
public class CLine
{
protected double a;
protected double b;
protected double c;
public CLine(Double angleInRad, CPoint2D point){};
public CLine(CPoint2D point1, CPoint2D point2){};
public CLine(CLine copiedLine){};
public bool Parallel(CLine line){};
public CPoint2D IntersecctionWith(CLine line){};
…… ……
}
public class CLineSegment : CLine
{
private CPoint2D m_startPoint;
private CPoint2D m_endPoint;
public double GetLineSegmentLength(){};
public int GetPointLocation(CPoint2D point){};
…… ……
}
public class CPolygon
{
private CPoint2D[] m_aVertices;
public double PolygonArea(){};
public VertexType PolygonVertexType(CPoint2D vertex){};
public PolygonType GetPolygonType(){};
public PolygonDirection VerticesDirection();
public bool PrincipalVertex(CPoint2D vertex){};
… …}
Run the Program
To see a demonstration, first you select the way to initialize polygon
vertices: you can use the build-in testing data or pick up vertices by clicking
the panel, then draw polygon outer lines by clicking Draw Polygon button; Click
the Cut Ear button will start to triangulate the polygon, the triangles composed
the polygon will be colored on the screen.
To reset program for next demonstration, click the Clean Screen button, you
will be able to start over again.
You can use button, main menu, context menu or tool bar button to run the
program as your convenient. For assistance or more details of this program, you
can use the Help menu or reference the tool tip of each control by moving mouse
over the control.
Figure 6. User Selected Polygon
Figure 7. Triangulated Polygon
Please feel free to explore and use the source code here. If you have any
suggestions, please let me know and I will keep the code updated.
Reference
- [1] Meisters, G.H. Polygons have ears. American Mathematical Monthly.
June/July 1975, 648-651.
- [2] Polygon, simple polygon and diagonal:
http://cgm.cs.mcgill.ca/~godfried/teaching/cg- projects/97/Ian/introduction.html
- [3] Ear Cutting for Simple Polygons:
http://www.personal.kent.edu/~rmuhamma/Compgeometry/MyCG/TwoEar/two-ear.htm
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 33 (Total in Forum: 33) (Refresh) | FirstPrevNext |
|
 |
|
|
I'm getting help from your codes here. Nice job! I'm wondering if I can get help with this problem. I'm trying to draw a simple concave polygon from a set of points(not sequential). Thanks in advance...
modified on Wednesday, November 19, 2008 11:57 PM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
foreach(ShapeRecord record1 in m_ShapeTileArgs.ShapeRecords)//over 10000 { ..... CPoint2D[] vertices = new CPoint2D[record1.Polygon.Points.Length]; for (int i = 0; i <record1.polygon.points.length;>{ vertices[i] = new CPoint2D(record1.Polygon.Points[i].X, record1.Polygon.Points[i].Y); } CPolygonShape cutPolygon = new CPolygonShape(vertices); cutPolygon.CutEar(); ..... }
And my program stopped here,never got out. I just don't know what is happenning. Does it spend much time to caculate? but it just stopped there.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Modifying the original default data it can be seen that the triangulation fails. The below code draws a simple "I" shape.
m_aPolygon=new Point[12]; m_aPolygon[0]=new Point(0,0); m_aPolygon[1]=new Point(0,50); m_aPolygon[2]=new Point(50,50); m_aPolygon[3]=new Point(50,100); m_aPolygon[4]=new Point(0,100); m_aPolygon[5]=new Point(0,150); m_aPolygon[6]=new Point(150,150); m_aPolygon[7]=new Point(150,100); m_aPolygon[8]=new Point(100,100); m_aPolygon[9]=new Point(100,50); m_aPolygon[10]=new Point(150,50); m_aPolygon[11]=new Point(150,0);
I just realized that this is a similar bug that was happening to mario a few years ago. Nevertheless I decided to post my data for testing purposes. I am guessing not to hold my breath on a fix.
modified on Wednesday, July 2, 2008 2:13 PM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I saw the same kind of errors as you discovered when I tried this code. Unfortunately fgshen has misunderstood the concept of convex vertex. The biggest error in this code therefore is in the line:
if (polygon.PolygonVertexType(vertex)=VertexType.ConvexPoint)
in the class Polygonshape.
The check that is done is only considering the area of the points around the vertex point, which definitely is not enough to determine if a vertex is convex or not. What you have to check is if the polygon turns right or left with respect to where its interior is. I also think that the PointInsidePolygon routine contains some errors, but did not analyse this further. What I did was however to recode the Ear-Cutting algorithm from start since this code contains a lot of stuff that is not necessary for Ear-Cutting and ended up with a working code (at least I think it is working). If you are interested in having that, you are free to get it, just drop me a message. Did not create a new codeproject entry since that would mean writing a big description of the code and the problem. This page really explains the Ear-Cutting in a good way, just that the code contains some basic errors that makes the code unusable .
|
| Sign In·View Thread·PermaLink | 3.80/5 (3 votes) |
|
|
|
 |
|
|
How difficult would it be to modify this algorithm to handle holes? Would it be worth trying to change the existing code or would it be better to start over conceptually from scratch?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Holes are theoretically easy: Let the "hole" be a polygon with reverse winding inside of the containing polygon, and merge them into a single polygon by joining a vertex (actually a start and an end vertex with the same positions) from each polygon with two edges. Essentially you have one polygon with an infitesimal small gap, you can visualize it like: _________ / ___ \ | |___>---| \_________/ (You'll have to copy and paste this ASCII *art* into some text editor with fixed-width font support, doesn't work with kerned fonts) Where the dashes represent the connecting seam, picture the outer polygon to be winded clockwise and the inner one counter clockwise.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Fiorentino, appreciate the response. I am trying to think of ways this could be implemented. I believe the original source code reversed the poly point hull if the points were deemed clockwise. So, I am not sure how this will play into having an outer poly point hull as clockwise. Would the point list be composed of one continuous point list (outer hull clockwise-inner hull anticlock)? I can try to play with some small samples to see the effect. Thanks
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
To be honest, I haven't read the original source code, but was eager to try to help you. Now, based on what you said about the source code ensuring the winding direction of the polygons, I assume this is because it makes the ear-clipping approach so much easier. If you encapsulate the polygon in a structure with a flag that says whether it is additive or subtractive (or hole or not hole or whatever lingo you prefer), you can then ensure the opposing winding directions of polygons that are supposed to be additive or subtractive (holes) in the method that ensures the proper winding directions. Then before the triangulation step, you map out what subtractive polygons are completely contained in what additive polygons (scrap overlapping polygons to make it easier, you can deal with edge intersections and clipping later on). You can do this by starting with a test on whether a vertex of the subtractive polygon is inside the additive polygon, if it is, then check that none of its edges intersect with the edges of the additive one. Of course, self-intersection would be tested for before this step to rule out all malshaped polygons. After this make the program find the closest vertices of the outer polygon and the hole polygon, and merge them there (this takes a bit of index magic, but is easy to do with a bit of thinking). Now, this should theoretically work for polygons with single holes. If there are multiple holes, and they do not overlap, inside of a polygon, you must take steps to ensure that the gluing edges do not cross any existing edges, so it's a bit more tricky. If done right, the newly constructed polygons from the holes should be consistent with the winding direction of the outer polygon, so the actual triangulation algorithm should need to be touched. Hope it works out for you. I've found that it isn't really possible to model a polygon with hole, without separating the two or joining them with a glue edge.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
I found two bugs in the PointInsidePolygon method of CPoint2D.
1. This for loop: for (int i= 1; i<nPoints; i++) needs to loop until i<=nPoints, that is: for (int i= 1; i<=nPoints; i++)
2. Points that are on the border will return inconsistent results. Since you are using the method of checking a line from the point and counting the number of intersections of polygon sides (even = outside, odd = inside). If the point is already on the a border of the polygon the code counts that as one intersection. depending on which side of the polygon the point is on and which way your testing line is drawn you may head into the poylgon, out the other side and cross another line resulting in 2 intersections (not in polygon). Or the testing line would be drawn away from the inner polygon resulting in just the one intersection (in the polygon). To fix this you can simply check if the point is on a border line and if it is return true, else continue with the test you now have.
|
| Sign In·View Thread·PermaLink | 1.50/5 (2 votes) |
|
|
|
 |
|
|
hellow
I enlarged the MainForm of the demo to 1600X1200 to fit the following input:
m_aPolygon=new Point[26];
m_aPolygon[0] = new Point(487,450);
m_aPolygon[1] = new Point(498 ,482);
m_aPolygon[2] = new Point(708, 767);
m_aPolygon[3] = new Point(704, 927);
m_aPolygon[4] = new Point(638,1054);
m_aPolygon[5] = new Point(528,1130);
m_aPolygon[6] = new Point(400 ,1157);
m_aPolygon[7] = new Point(272 ,1130);
m_aPolygon[8] = new Point(162 ,1054);
m_aPolygon[9] = new Point(96 ,927);
m_aPolygon[10] = new Point(92,767);
m_aPolygon[11] = new Point(302, 482);
m_aPolygon[12] = new Point(313, 450);
m_aPolygon[13] = new Point(374, 415);
m_aPolygon[14] = new Point(370, 425);
m_aPolygon[15] = new Point(367, 440);
m_aPolygon[16] = new Point(368 ,456);
m_aPolygon[17] = new Point(376, 467);
m_aPolygon[18] = new Point(387, 479);
m_aPolygon[19] = new Point(400, 489);
m_aPolygon[20] = new Point(413, 479);
m_aPolygon[21] = new Point(424, 467);
m_aPolygon[22] = new Point(432, 456);
m_aPolygon[23] = new Point(433, 440);
m_aPolygon[24] = new Point(430, 425);
m_aPolygon[25] = new Point(426 ,415);
and the triangulation is not working!!!
please help me ...
shoham
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello, I use other test data:
m_aPolygon=new Point[12]; m_aPolygon[0]=new Point(50,50); m_aPolygon[1]=new Point(190,50); m_aPolygon[2]=new Point(190,55); m_aPolygon[3]=new Point(75,55); m_aPolygon[4]=new Point(75,195); m_aPolygon[5]=new Point(190,195); m_aPolygon[6]=new Point(190,200); m_aPolygon[7]=new Point(50,200); m_aPolygon[8]=new Point(50,195); m_aPolygon[9]=new Point(70,195); m_aPolygon[10]=new Point(70,55); m_aPolygon[11]=new Point(50,55);
This is a polygon like a big I, all only 90° angles. Triangulation for this case not works. Can anybody say what’s wrong?
Mario
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Another problem:
m_aPolygon=new Point[8]; m_aPolygon[0]=new Point(50,70); m_aPolygon[1]=new Point(150,70); m_aPolygon[2]=new Point(150,170); m_aPolygon[3]=new Point(100,170); m_aPolygon[4]=new Point(120,120); m_aPolygon[5]=new Point(80,120); m_aPolygon[6]=new Point(100,170); m_aPolygon[7]=new Point(50,170);
Cutting ears never return
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I found that the problem is with point(100,170), propably you should not allow 2 same points ... try this:
m_aPolygon=new Point[8];
m_aPolygon[0]=new Point(50,70); m_aPolygon[1]=new Point(150,70); m_aPolygon[2]=new Point(150,170); m_aPolygon[3]=new Point(100,170); m_aPolygon[4]=new Point(120,120); m_aPolygon[5]=new Point(80,120); m_aPolygon[6]=new Point(100,171); m_aPolygon[7]=new Point(50,170);
it works well.
Wizard_01
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The InLine method does not work properly.
After my change the InLine method seems to work better.
Before my change:
else if ((Cx < lineSegment.GetXmax()) && (Cx > lineSegment.GetXmin()) && (Cy < lineSegment.GetYmax()) && (Cy > lineSegment.GetYmin()))
After my change:
else if ((Cx <= lineSegment.GetXmax()) && (Cx >= lineSegment.GetXmin()) && (Cy <= lineSegment.GetYmax()) && (Cy >= lineSegment.GetYmin()))
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The "IntersectedWith" method does not function properly.
First of all ... I believe that:
if (Math.Abs(de - 0) < ConstantValue.SmallValue)
Should be:
if (Math.Abs(de - 0) > ConstantValue.SmallValue)
Additionally,
if ((ub > 0) && (ub < 1)) return true;
Seems to return true, even if the lines don't actually intersect. I think this should rather be:
if ((ub > 0) && (ub < 1)) if ((ua > 0) && (ua < 1)) return true; else return false;
This seems to make it function properly.
Anyone else come across this?
-- modified at 12:00 Friday 10th March, 2006
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi, One aspect that you may not be aware of is that this algorithm does not always choose triangles with shorter sides when there is a choice. A simple example is where you have a 5 point star and the center is a pentagon. If the star is not symmetric the pentagon will have 2 unequal length diagonals. The diagonals must share a common vertex. You can choose this common vertex to be any one of the 5 in the pentagon.
Does it matter? The question depends on what you are using the triangles for. For many modeling uses it is best that the triangles chosen are as close to equilateral, or have the largest internal angles as possible. Triangles with long sides and very small angles do not model surfaces very well and the trig functions usually suffer with small angles.
Your second example with the S shape polygon shows how long and pointy some of the triangles were made. There is another set of triangles that keep them all about the same size and shape.
I once wrote a program that followed Donald Knuth's algorithm that he describes in his paper "Axioms and Hulls". This is slightly different in that you have a field of randomly placed points and you want to triangulate them all to form a mesh of triangles. The outside perimeter is the "hull", but what points will be used for the hull. That's a big computational geometry question and the solution is not trivial. The key point here is that the triangles have to be formed using a set of rules that define what is called a "Delaney Triangle". Simply stated, the triangle is formed from nearest neighbours, but the algorithm is complex. Knuth has a very good solution.
|
| Sign In·View Thread·PermaLink | 5.00/5 (2 votes) |
|
|
|
 |
|
|
I suspect you can somewhat achieve better control on the shape of the triangles by clipping off convex polygons instead of just ears first. Then you are free to sort out the triangles in any fashion, i.e. triangle fans.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, One aspect that you may not be aware of is that this algorithm does not always choose triangles with shorter sides when there is a choice. A simple example is where you have a 5 point star and the center is a pentagon. If the star is not symmetric the pentagon will have 2 unequal length diagonals. The diagonals must share a common vertex. You can choose this common vertex to be any one of the 5 in the pentagon.
Does it matter? The question depends on what you are using the triangles for. For many modeling uses it is best that the triangles chosen are as close to equilateral, or have the largest internal angles as possible. Triangles with long sides and very small angles do not model surfaces very well and the trig functions usually suffer with small angles.
Your second example with the S shape polygon shows how long and pointy some of the triangles were made. There is another set of triangles that keep them all about the same size and shape.
I once wrote a program that followed Donald Knuth's algorithm that he describes in his paper "Axioms and Hulls". This is slightly different in that you have a field of randomly placed points and you want to triangulate them all to form a mesh of triangles. The outside perimeter is the "hull", but what points will be used for the hull. That's a big computational geometry question and the solution is not trivial. The key point here is that the triangles have to be formed using a set of rules that define what is called a "Delaney Triangle". Simply stated, the triangle is formed from nearest neighbours, but the algorithm is complex. Knuth has a very good solution.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, what if the inline function is omitted? would there be an error?
I see the polygon would still be triangulated.
Cause in my case, I actually need all the points, so even if it's inline, it should be triangulated as well.
thanks
-- modified at 20:33 Tuesday 11th October, 2005
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
This is a GREAT example, but I found a small problem at the demo application. When you drawing a poly like in test data but opposite way, there is only one triangle. x \ x \ x \ x / x / / Thanks. Boris.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
In the CPolygon.Diagonal method, the parameter vertex2 is never used.
Best regards, Paul.
Jesus Christ is LOVE! Please tell somebody.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
| |