different faces of a polygon

In pcb-rnd polygons have three possible appearance:

The as-drawn version

The as-drawn version is a list of points (corners), as drawn by the user. The points are stored in an array of x;y coordinates. There is a single, continous, dynamically allocated array for all points, including the outer contour and the holes.

The points are stored in the Points[] field. The HoleIndex[] field lists the starting index of each hole. For example a rectangular polygon with 2 triangle holes is stored as:

index 0. 1. 2. 3. 4. 5. 6. 7. 8. 9.
Points[] value c1 c2 c3 c4 h1.1 h1.2 h1.3 h2.1 h2.2 h2.3
part of outer contour hole 1 hole 2

The array length of Points[] is 10, the length of the correspoding HoleIndex[] is 2. The values of HoleIndex[] is {4, 7}, indicating that the corners of first hole starts at 4 while the corners of the second hole starts at 7.

If HoldeIndex[] is NULL, there are no holes and all points are part of the outer contour. The Outer contour must contain at least 3 points.

The as-drawn version always contains the contour of a single island and 0 or more holes - none of them can intersect or touch any contour or hole of the same polygon. This also means a contour or hole can not be self-intersecting.

The as-drawn version is stored to make editing possible. It is not affected by other objects (e.g. clearances) or flags of the polygon. The as-drawn points exist and are stored even if they are not visible (clipped). The UI maniulates the as-drawn version.

The clipped version

The polygon is clipped by an object if both the polygon and the object have the correspoding 'clear' flag set. A clipped polygon typically has a much more complex shape than the as-drawn polygon, due to the clearance cutouts (not to be confused with user drawn holes). The clipped polygon is the actual "in-copper" shape of the polygon.

In some cases the clearance cutouts slice the polygon in multiple components or in other word islands. If the polygon is full (set by a polygon flag), all islands are kept, else only the largest island is kept. (Note: the find code handles all islands of a full polygon as one polygon: it indicates connection between them even if there is no connection, which makes full polygons dangerous on copper layers.)

The clipped polygon is stored in the polyarea field Clipped. It is a doubly linked circular list (link fields are .f for forward and .b for backward). Each item on the list is an island of the polygon. If Clipped is NULL, the polygon clipping is not yet compiled for the given polygon; call pcb_poly_init_clip().

Each island is a polyarea, which consists of an outer contour and 0 or more holes - all correspoding to the actual cutouts created by user-drawn polygon holes and/or clearance cutouts. These contours are stored in a singly linked list of plines. The first element of the list is the pline for the outer contour and always exists. The next 0 or more plines, using the .next field of the pline, are the contours of the cutouts (holes).

A pline consists of a circular, doubly linked list of points; traversing using the .next field, the points are ordered in counter-clockwise.

The clipped polygon shall be updated by any code that changes:

When some code forgets to update clippig, the clipped polygon doesn't match the clearances dictated by other objects; a reload of the board "fixes" the problem by forcing the clip. (Native save files contain the as-drawn poly only, not the (outdated) clipped poly).

The no-holes version

Some export plugins (and/or export formats) don't support holes in polygons. The no-hole version of the polygon is stored in the .NoHoles filed and is a list of islands, each specified with an outer countour that are crafted to include the holes. This is done by slicing an island at each hole. TODO: check how it looks, include image

Further comments by Ben Jackson

As extracted from the original code comments:
The first pcb_polyarea_t in pcb_polygon_t.Clipped is what is used for the vast
majority of Polygon related tests.  The basic logic for an
intersection is "is the target shape inside pcb_polyarea_t.contours and NOT
fully enclosed in any of pcb_polyarea_t.contours.next... (the holes)".

The polygon dicer (NoHolesPolygonDicer and r_NoHolesPolygonDicer)
emits a series of "simple" pcb_pline_t shapes.  That is, the pcb_pline_t isn't
linked to any other "holes" outlines).  That's the meaning of the first
test in r_NoHolesPolygonDicer.  It is testing to see if the pcb_pline_t
contour (the first, making it a solid outline) has a valid next
pointer (which would point to one or more holes).  The dicer works by
recursively chopping the polygon in half through the first hole it
sees (which is guaranteed to eliminate at least that one hole).  The
dicer output is used for HIDs which cannot render things with holes
(which would require erasure).

How to code with polygons

Iterators: looping on clipped polygon geometry

The following code prints all contours and holes of a polygon, using loops iterators. The same iterator holds all states, so that it points to:
void print_poly(pcb_polygon_t *polygon)
{
	pcb_poly_it_t it;
	rnd_polyarea_t *pa;

	/* first, iterate over all islands of a polygon */
	for(pa = pcb_poly_island_first(polygon, &it); pa != NULL; pa = pcb_poly_island_next(&it)) {
		rnd_coord_t x, y;
		rnd_pline_t *pl;
		int go;

		printf(" island\n");
		/* check if we have a contour for the given island */
		pl = pcb_poly_contour(&it);
		if (pl != NULL) {
			printf("  contour:\n");
			/* iterate over the vectors of the contour */
			for(go = pcb_poly_vect_first(&it, &x, &y); go; go = pcb_poly_vect_next(&it, &x, &y)) {
				pcb_printf("   %mm %mm\n", x, y);
			}
			
			/* iterate over all holes within this island */
			for(pl = pcb_poly_hole_first(&it); pl != NULL; pl = pcb_poly_hole_next(&it)) {
				printf("  hole:\n");
				/* iterate over the vectors of the given hole */
				for(go = pcb_poly_vect_first(&it, &x, &y); go; go = pcb_poly_vect_next(&it, &x, &y)) {
					rnd_printf("   %mm %mm\n", x, y);
				}
			}
		}
	}
}