Click here to Skip to main content
Click here to Skip to main content

Polygon Triangulation in C#

By , 3 Oct 2004
 

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;        
 
 //To hold user selected points as polygon vertices:
  private ArrayList m_alSelectedPts=new ArrayList(); 

  /* To pick up the user selected points in the panel */
  private void pnlDraw_MouseUp(object sender, MouseEventArgs e)
  {    
    …… ……
    Point clickedPt=new Point(e.X, e.Y);
    m_alSelectedPts.Add(clickedPt);
    …… …… 
  }
  
  /* Pass data to an object of CpolygonShape and calculate ears: */
  private void btnCut_Click(object sender, System.EventArgs e)
  {            
    …… ……
    //Convert the received vertices to array of CPoint2D, then:
    CPolygonShape cutPolygon =  new CPolygonShape(vertices);
    cutPolygon.CutEar();
    …… ……
  }
  
  /*Receive results from the object and fill triangles: */
  public void DrawEarsPolygon(CPolygonShape cutPolygon)
  {
    …… ……
    //Use tempArray to hold the results:            
    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;  //Fill triangles in different color
      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;
  }

  /* To check whether the Vertex is an ear  */
  private bool IsEarOfUpdatedPolygon(CPoint2D vertex )        
  {
    CPolygon polygon=new CPolygon(m_aUpdatedPolygonVertices);
        ……
    bool bEar=true;
    if (polygon.PolygonVertexType(vertex)=VertexType.ConvexPoint)
    {
      CPoint2D pi=vertex;
      //previous vertex                    
      CPoint2D pj=polygon.PreviousPoint(vertex);
      //next 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  //concave point
      bEar=false; //not an ear
  
    return bEar;
  }
    
  /*To cut an ear */
  public void CutEar()
  {
    CPolygon polygon=new CPolygon(m_aUpdatedPolygonVertices);
    bool bFinish=false;            
    CPoint2D pt=new CPoint2D();
    while (bFinish==false) //UpdatedPolygon
    {
      int i=0;
      bool bNotFound=true;
      while (bNotFound 
        && (i < m_aUpdatedPolygonVertices.Length)) // till find an ear
      {
        pt=m_aUpdatedPolygonVertices[i];
    if (IsEarOfUpdatedPolygon(pt))
      bNotFound=false; //got one, pt is an ear
    else
      i++;
      } //bNotFount
      if (pt !=null)
    UpdatePolygonVertices(pt);
        
      polygon=new CPolygon(m_aUpdatedPolygonVertices);
      if (m_aUpdatedPolygonVertices.Length==3)
    bFinish=true;
    } //bFinish=false
    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
{
  //line: ax+by+c=0;
  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
{
  //line: ax+by+c=0, with start point and end point
  //direction from start point ->end point
  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){};
  //return Concave vertex or convex vertex

  public PolygonType GetPolygonType(){};
  //return Concave polygon, convex polygon

  public PolygonDirection VerticesDirection();
  //return clockwise or count clockwise

  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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

fgshen
Web Developer
Canada Canada
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralHolesmemberkaratecoyote17 Apr '08 - 5:17 
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?
GeneralRe: HolesmemberFiorentino18 May '08 - 6:12 
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.
GeneralRe: Holesmemberkaratecoyote19 May '08 - 9:24 
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
GeneralRe: HolesmemberFiorentino20 May '08 - 1:30 
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. Big Grin | :-D
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.
GeneralRe: Holesmemberhavana75 Jan '11 - 7:26 
Can someone tell me how I triangular holes? Does anyone have a code example?
QuestionReusememberkaratecoyote24 Mar '08 - 12:36 
what are the rules for reuse?
GeneralComplex polygonsmemberLeonnik24 Oct '07 - 10:07 
Can I triangulate complex polygons? (those who are composed by many rings)
 
LeonniK®

GeneralPointInsidePolygon bugsmemberjdiegel26 Aug '07 - 15:45 
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.
Questiona bugmembersbenhar18 Jul '07 - 3:15 
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 Confused | :confused: ...
 
shoham

GeneralWrong trianulation for simple figuremembernoi7612 Dec '06 - 6:58 
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
GeneralRe: Wrong trianulation for simple figuremembernoi7612 Dec '06 - 23:42 
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 Frown | :(
GeneralRe: Wrong trianulation for simple figurememberWizard_0130 Dec '06 - 7:21 
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

AnswerRe: Wrong trianulation for simple figurememberAnilkale110 Jan '10 - 21:58 
Below code is used to draw conditional shape used in the flow chart in rubber band effect.
 
Point[] ptArray = new Point[4];
ptArray[0] = new Point(m_ptStart.X + (m_ptOld.X - m_ptStart.X) / 2, m_ptStart.Y);
ptArray[1] = new Point(m_ptStart.X + (m_ptOld.X - m_ptStart.X), m_ptStart.Y + ((m_ptOld.Y - m_ptStart.Y) / 2));
ptArray[2] = new Point(m_ptStart.X + ((m_ptOld.X - m_ptStart.X) / 2), m_ptStart.Y + ((m_ptOld.Y - m_ptStart.Y)));
ptArray[3] = new Point(m_ptStart.X, m_ptStart.Y + ((m_ptOld.Y - m_ptStart.Y) / 2));
 
g.DrawPolygon(p, ptArray);
 
Regards,
anil Kale
GeneralBug? - CPoint2D.InLine(LineSegment lineSegment)memberrofu29 Sep '06 - 4:18 
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()))

GeneralBug with &quot;IntersectedWith&quot;memberwegnwa10 Mar '06 - 5:59 
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
AnswerRe: Bug with &quot;IntersectedWith&quot;membernoi7612 Dec '06 - 7:24 
I found no usage of this function.
GeneralVery skinny trianglesmemberBrent_Daignault14 Oct '05 - 10:54 
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.
GeneralRe: Very skinny trianglesmemberFiorentino21 May '08 - 5:32 
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.
GeneralOld techniquememberBrent_Daignault14 Oct '05 - 10:51 
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.
QuestionInline routinemembereko.pramono10 Oct '05 - 22:36 
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
GeneralA small bugmemberBobik27 Aug '05 - 21:35 
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.
QuestionA bug?memberPaul Selormey7 Aug '05 - 13:59 
In the CPolygon.Diagonal method, the parameter vertex2 is never used.
 
Best regards,
Paul.
 
Jesus Christ is LOVE! Please tell somebody.
GeneralPolygon DirectionsussDaniel Carvajal30 May '05 - 5:03 
I found this article really useful, but I have a little problem that your program is not able to solve. Really, I am not sure that there is a solution, but here it goes:
 
How to know if a concave polygon is CCW or CW when there are the same number of concave and convex vertices? Half the cross products are positive and half are negative.
 
Two thumbs up.
GeneralVery usefullmembertrixcomp4 May '05 - 12:37 
This algorithm will be very useful to me, thanks so much!
GeneralSmall bugmemberOmer van Kloeten23 Sep '04 - 7:02 
It looks great! Wonderful work!
 
However, there's a crash once you draw a simple triangle and try to find the ears.
I didn't mean to find it! Honest! :P
 
Programming is life; The rest is mere details.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 4 Oct 2004
Article Copyright 2004 by fgshen
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid