Vatti Polygon Clipping

advertisement
Vatti Polygon Clipping
Quite a few polygon clipping algorithms have been published. We have discussed several. The LiangBarsky and Maillot algorithms are better than the Sutherland-Hodgman algorithm, but these algorithm
only clip polygons against simple rectangles. This is adequate for many situations in graphics. On the
other hand, the Sutherland-Hodgman and Cyrus-Beck algorithms are more general and allow clipping
against any convex polygon. The restriction to convex polygons is caused by the fact that the algorithm
clips against a sequence of half-planes and therefore only applies to sets that are the intersection of halfplanes, in other words, convex (linear) polygons. There are situations however where the convexity
requirement is too restrictive. The Weiler algorithm is more general yet and works for non-convex
polygons. The final two algorithms we look at, the Vatti and Greiner-Hormann algorithms, are also
extremely general. Furthermore, they are the most efficient of these general algorithms. The polygons are
not constrained in any way now. They can be concave or convex. They can have self-intersections. In
fact, one can easily deal with lists of polygons. We begin with Vatti’s algorithm ([Vatt92]).
Call an edge of a polygon a left or right edge if the interior of the polygon is to the right or left,
respectively. Horizontal edges are considered to be both left and right edges. A key fact that is used by the
Vatti algorithm is that polygons can be represented via a set of left and right bounds which are connected
lists of left and right edges, respectively, that come in pairs. Each of these bounds starts at a local
minimum of the polygon and ends at a local maximum. Consider the "polygon" with vertices p0, p1, …,
p8 shown in Figure 1(a). The two left bounds have vertices p0, p8, p7, p6 and p4, p3, p2, respectively. The
two right bounds have vertices p0, p1, p2 and p4, p5, p6.
Note: In this section the y-axis will be pointing up (rather than down as usual for a viewport).
Here is an overview of the Vatti algorithm. The first step of the algorithm is to determine the left and
right bounds of the clip and subject polygons and to store this information in a local minima list (LML).
This list consists of a list of matching pairs of left-right bounds and is sorted in ascending order by the ycoordinate of the corresponding local minimum. It does not matter if initial horizontal edges are put into a
left or right bound. Figure 1(b) shows the LML for the polygon in Figure 1(a). The algorithm for
constructing the LML is a relatively straightforward programming exercise and will not be described
here. It can be done with a single pass of the clip and subject polygons.
LML = ( (B1, B2) , (B3, B4) )
where the bounds Bi are defined by
B1
B2
B3
B4
=
=
=
=
( p0p8 , p8p7 , p7p6 ) ,
( p0p1 , p1p2 ) ,
( p4p5 , p5p6 ) , and
( p4p3 , p3p2 ) .
(b)
Figure 1 Polygon bounds
The bounds on the LML were specified to have the property that their edges are either all left edges
or all right edges. However, it is convenient to have a more general notion of a left or right bound.
Therefore, from now on, a left or right bound will denote any connected sequence of edges only whose
first edge is required to be a left or right edge, respectively. We still assume that a bound starts at a local
minimum and ends at a local maximum. For example, we shall allow the polygon in Figure 1(a) to be
described by one left bound with vertices p0, p8, p7, p6, p5, p4, p3, p2 and one right bound with vertices p0,
p1, p2.
The clipped or output polygons we are after will be built in stages from sequences of "partial"
polygons, each of which is a "V-shaped" list of vertices with the vertices on the left side coming from a
left bound and those on the right side coming from a right bound and where there is one vertex in
common, namely, the one at the bottom of the "V" which is at a local minimum. Let us use the notation
P[p0p1…pn] to denote the partial polygon with vertices p0, p1, …, pn, where p0 is the first point and pn, the
last. The points p0 and pn are the top of the partial left and right bound, respectively. Some vertex pm will
be the vertex at a local minimum which connects the two bounds but since it will not be used for anything
there is no need to indicate this index m in the notation. For example, one way to represent the polygon in
Figure 1(a) would be as P[p6p7p8p0p1p2p3p4p5p6] (with m being 3 in this case). Notice how the edges in
the left and right bounds are not always to the right or left of the interior of the polygon here. In the case
of a "completed" polygon, p0 and pn will be the same vertex at a local maximum, but at all the other
intermediate stages in the construction of a polygon the vertices p0 and pn may not be equal. However, p0
and pn will always correspond to top vertices of the current left and right partial bounds, respectively. For
example, P[p7p8p0p1] (with m equal to 2) is a legitimate expression describing partial left and right
bounds for the polygon in Figure 1(a). A good way to implement these partial polygons is via a circularly
linked list, or cycle, and a pointer that points to the last element of the list.
The algorithm now computes the bounds of the output polygons from the LML by scanning the world
from the bottom to the top using what are called scanbeams. A scanbeam is a horizontal section between
two scan lines (not necessarily adjacent), so that each of these scan lines contains at least one vertex from
the polygons but there are no vertices in between them. Figure 1(a) shows the scanbeams and the scan
lines that determine them for that particular polygon. The scanbeams are the regions between the
horizontal lines. It should be noted here that the scan lines that determine the scanbeams are not computed
all at once but incrementally in a bottom-up fashion. The information about the scanbeams is kept in a
scanbeam list (SBL) which is an ordered list of the y-coordinates of all the scan lines that define the
scanbeams. This list of increasing values will be thought of as a stack. As we scan the world, we also
maintain an active edge list (AEL) which is an ordered list consisting of all the edges intersected by the
current scanbeam.
When we begin processing a scanbeam, the first thing we do is to check the LML to see if any of its
bound pairs start at the bottom of the scanbeam. These bounds correspond to local minima and may start a
new output polygon or break one into two depending on whether the local minimum starts with a leftright or right-left edge pair. After any new edges from the LML are added to the AEL we need to check
for intersections of edges within a scanbeam. These intersections affect the output polygons and are dealt
with separately first. Finally, we process the edges on the AEL. Algorithm 1 summarizes this overview of
the Vatti algorithm.
Now let us look at the algorithm in more detail. Data 1 shows a data structure for an edge. Each edge
has the x-value of its intersection with the bottom of the scanbeam associated to it and the edges of the
AEL are ordered by increasing values of these x-values with ties being broken using the dx value of the
edge. The x-values are updated as we move from scanbeam to scanbeam. The kind of an edge specifies
whether it belongs to the clip or subject polygon. We shall say that two edges are like edges if they have
the same kind value and unlike edges otherwise.
The UpdateLMLandSBL procedure shown in Algorithm 2 finds the bounds of a polygon, adds them
to LML, and also updates SBL. Finding a bound involves creating the edges which make them up. Each
edge has its bottomX, topY, dx, and kind fields defined here. The dx field for a horizontal edge is
simply the signed length of the edge with the value of dx negative if the edge is oriented to the left. The
{ Global variables }
real list
SBL; { an ordered list of distinct reals thought of as a stack}
bound pair list LML; { a list of pairs of matching polygon bounds }
edge list
AEL; { a list of nonhorizontal edges ordered by x-intercept
with the current scan line}
polygon list
PL;
{ the finished output polygons are stored here as algorithm
progresses }
polygon list function Vatti_Clip (polygon subjectP; polygon clipP)
{ The polygon subjectP is clipped against the polygon clipP.
The list of polygons which are the intersection of subjectP and clipP is returned to the
calling procedure. }
begin
real yb, yt;
Initialize LML, SBL to empty;
{ Define LML and the initial SBL }
UpdateLMLandSBL (subjectP,subject);
UpdateLMLandSBL (clipP,clip);
Initialize PL, AEL to empty;
yb := PopSBL ();
repeat
AddNewBoundPairs (yb);
yt := PopSBL ();
ProcessIntersections (yb,yt);
ProcessEdgesInAEL (yb,yt);
yb := yt;
until Empty (SBL);
{ bottom of current scanbeam }
{ modifies AEL and SBL }
{ top of current scan beam }
return (PL);
end;
Algorithm 1 The Vatti polygon clipping algorithm
side, contributing, and adjPolyPtr fields are determined in the InsertIntoAEL procedure described later.
The pointer adjPolyPtr in an edge record points to the polygon associated to the edge. This polygon will
also be referred to as the adjacent polygon of the edge.
Because horizontal edges complicate matters, in order to make dealing with horizontal edges easier,
we assume that the matching left and right bound pairs in the LML list are "normalized." A normalized
left and right bound pair satisfies the following properties:
(1) All consecutive horizontal edges are combined into one so that bounds do not have two horizontal
edges in a row.
(2) No left bound has a bottom horizontal edge (any such edges are shifted to the right bound).
edge = record
bottomX,
{ initially the x-coodinate of the bottom vertex, but once the
edge is on the AEL, then it is the x-intercept of the edge with
the line at the bottom of the current scanbeam }
topY,
{ y-coordinate of top vertex }
dx;
{ the reciprocal of the slope of the edge }
(clip,subject)
kind;
{ does edge belong to clip or subject polygon? }
(left,right)
side;
{ is it a left or right bound edge? }
boolean
contributing; { does edge contribute to output polygons? }
polygon pointer adjPolyPtr; { pointer to partial polygon associated to the edge }
end;
real
Data 1 The edge data structure
procedure UpdateLMLandSBL (closed polygonal curve P; (clip,subject) type)
{ Finds the bounds of P and adds them to LML. Only the edge fields bottomX, topY,
dx, and kind are defined here. The side and adjPolyPtr fields are defined later. Add
all local minima and the tops of the first nonhorizontal edge of every bound to SBL. }
begin
for each bound pair BP = (B1,B2) of P do
begin
Add BP to LML;
{ Add the minimum y-value of BP to SBL }
InsertIntoSBL (StartY (BP));
{ Add the top of the first nonhorizontal edges to SBL }
Let ei denote the first nonhorizontal edge of Bi;
InsertIntoSBL (TopY (e1));
InsertIntoSBL (TopY (e2));
end
end;
real function StartY (bound pair BP)
{ Returns the minimum y-value of the vertices in BP }
procedure InsertIntoSBL (real y)
{ If y is not yet in SBL, then insert it at the correct place in the ordered list SBL }
real function PopSBL ()
{ Delete the smallest (first) element of SBL and return its value }
edge function Succ (edge e)
{ The edge after e in the polygon bound to which e belongs }
Algorithm 2 Updating the LML and SBL
(3) No right bound has a top horizontal edge (any such edges are shifted to the left bound).
Let us introduce some more terminology. Some edges and vertices that one encounters or creates for
the output polygons will belong to the bounds of the clipped polygon, others will not. Let us call a vertex
or an edge a contributing or noncontributing vertex or edge depending on whether or not it belongs to the
output polygons. With regard to vertices, if a vertex is not a local minimum or maximum, then it will be
called a left or right intermediate vertex depending on whether it belongs to a left or right bound,
respectively. Because the overall algorithm proceeds by taking the appropriate action based on the
vertices that are encountered, we shall see that it therefore basically reduces to a careful analysis of the
three cases:
(1) The vertex is a local minimum.
(2) The vertex is a left or right intermediate vertex.
(3) The vertex is a local maximum.
Local minima are encountered when elements on the LML become active. Intermediate vertices and local
maxima are encountered when scanning the AEL. Intersections of edges also give rise to these three
cases.
Returning to Algorithm 1, the first thing that happens in the main loop is to check for new bound
pairs that start at the bottom of the current scanbeams. If any such pairs exist, then we have a case of two
bounds starting at a vertex which is a local minimum. We add their first nonhorizontal edges to the AEL
and the top y-values of these to the SBL. The edges are flagged as being a left or right edge using the
side field. We determine if the edges are contributing by a parity test. An edge of the subject polygon is
contributing if there are an odd number of edges from the clip polygon to its left in the AEL. Similarly, an
edge of the clip polygon is contributing if there are an odd number of edges from the subject polygon to
its left in the AEL. If the vertex is contributing, then we create a new partial polygon P[p] and make the
adjPolyPtr field in both edge records point to this polygon. If the vertex is noncontributing, then we set
their adjPolyPtr fields to nil. Note that to determine whether or not an edge is contributing or
noncontributing we actually have to look at the geometry only for the first nonhorizontal edge of each
bound. After that, we simply check the contributing field. Algorithm 3 describes the details.
The central task of the main loop in the Vatti algorithm is to process the edges on the AEL. If edges
intersect, we shall have to do some preprocessing (procedure ProcessIntersections), but right now let us
skip that and describe the actual processing, namely, procedure ProcessEdgesInAEL. Because horizontal
edges cause substantial complications, we describe two versions of the procedure. The first, shown in
Algorithm 4, assumes that the polygons have no horizontal edges. The general case, where we allow
horizontal edges is shown in Algorithm 6. We shall discuss the nonhorizontal version first.
If an edge does not end at the top of the current scanbeam, then we simply update its bottomX field
to the x-coordinate of the intersection of the edge with the scan line at the top of the scanbeam. If an edge
does end at the top of the scanbeam, then the action we take is determined by the type of the top end
vertex p. The vertex can either be an intermediate vertex or a local maximum. Issues associated to
horizontal edges are treated later.
If the vertex p is a left or right intermediate vertex, then the vertex is added at the beginning or end of
the vertex list of its adjacent polygon, depending on whether it is a left or right edge, respectively. The
edge is replaced by its successor edge and the polygon pointer and left/right flag are passed on to the new
edge.
If the vertex p is a local maximum of the original clip or subject polygons. In that case, a pair of
edges from two bounds meet in a point p. If p is a contributing vertex, then the two edges may belong
either to the same or different (partial) polygons. If they belong to the same polygon, then this polygon
will now be closed once the point p is added. If they belong to different polygons, say P and Q,
respectively, then we need to merge these polygons. Let e1 and e2 be the edges for P and f1 and f2, the
procedure AddNewBoundPairs (real y)
while not (Empty (LML)) and (first bound pair BP on LML starts at y) do
begin
AddEdgesToAEL (BP,y);
Delete BP from LML;
end;
procedure AddEdgesToAEL (bound pair (B1,B2), real yb)¦
{ Bound pair (B1,B2) starts at yb }
begin
{ Since bounds are normalized, only B2 can have a horizontal first edge }
Let (e1,e2) denote the first nonhorizontal edges of B1 and B2, respectively;
InsertIntoSBL (TopY (e1));
InsertIntoSBL (TopY (e2));
{ Now add edges to AEL and finish their definition }
InsertIntoAEL (e1,e2);
Let p be the vertex at which both e1 and e2 start;
if Contributing (p) then AddLocalMin (e1,e2,p);
end; { AddEdgesToAEL }
procedure InsertIntoAEL (ref edge e1, e2)
{ Add nonhorizontal edges e1 and e2 to active edge list maintaining increasing x order.
Also set side and contributing fields of e1 and e2 using a parity argument. }
procedure AddLocalMin (edge e1, e2; point p);
{ e1 and e2 are the first nonhorizontal edges of a local minimum. }
begin
Create a new adjacent polygon P[p] for e1;
Make the adjPolyPtr field in edge e1 and e2 point to this polygon;
end;
edge function Next (edge e)
{ The edge after e in the AEL. }
edge function Previous (edge e)
{ The edge which precedes e in the AEL. }
Algorithm 3 Adding new bound pairs
procedure ProcessEdgesInAEL (real yb, yt)
{ Assumption: Polygons have no horizontal edges. }
begin
real
dy;
boolean moreEdges;
if Empty (AEL) then return;
dy := yt − yb;
Let e denote the first edge of AEL;
moreEdges := true;
while moreEdges do
begin
if e terminates at level yt in a vertex p
then
case Type (p) of
left intermediate: begin
if Contributing (e) then AddLeft (e,p);
Replace e in AEL by Succ (e);
end;
right intermediate: begin
if Contributing (e) then AddRight (e,p);
Replace e in AEL by Succ (e);
end;
local maximum: begin
Let nexte denote the edge of AEL after e;
if Contributing (e) then AddLocalMax (e,nexte,p);
Delete e and nexte from AEL;
end;
end
else SetBottomX (e,TopX (e,dy)); { Update e's bottomX value }
Update e to denote the next edge in AEL or
set moreEdges to false if there is none;
end
end; { ProcessEdgesInAEL }
procedure AddLeft (edge e; point p);
If P[p0p1...pn] is the polygon associated to e, replace it by P[pp0p1...pn];
procedure AddRight (edge e; point p);
If P[p0p1...pn] is the polygon associated to e, replace it by P[p0p1...pnp];
procedure AddLocalMax (edge e1, e2; point p);
begin
if Side (e1) = left then AddLeft(e1,p)
else AddRight (e1,p);
if e1 and e2 have different output polygons
then AppendPolygon(e1,e2)
else Add polygon to PL;
end;
procedure AppendPolygon (edge e1, f1);
{ Let P1 = P[p0p1…pn] and P2 = P[q0q1…qs] be the polygons adjacent
to e1 and f1, respectively. Let e2 and f2 be the other top edges of P1 and P2,
respectively. }
if e1 is a left edge
then
begin
Add vertex list of P2 to the left of vertex list of P1, that is,
replace P1 by P[qsqs−1…q0p0p1…pn]
Make P1 the adjacent polygon of f2;
end
else
begin
Add vertex list of P1 to the right of vertex list of P2, that is,
replace P2 by P[q0q1…qspnpn−1…p0]
Make P2 the adjacent polygon of e2;
end;
real function TopX (edge e; real dy)
{ This function returns the x-coordinate of the intersection of the edge with
a scan line a height dy above the current one which intersects the edge
in a point with x-coordinate BottomX (e). }
begin
return (BottomX (e) + Dx (e)*dy);
end;
Algorithm 4 Algorithm for processing the AEL edges (no horizontal edges)
edges for Q, so that e1 and f1 meet in p with f1 the successor to e1 in the AEL. See Figure 2. Figure 2(a)
and (c) show specific examples and (b) and (d) generic cases. If e1 is a left edge of P (Figure 2(a) and (b)),
then we append the vertices of Q to the beginning of the vertex list of P. If e1 is a right edge of P (Figure
2(c) and (d)), then we append the vertices of P to the end of the vertex list of Q. Note that each of the
polygons has two top contributing edges. In either case, after combining the vertices of P and Q, the two
edges e1 and f1 become noncontributing. If e1 was a left edge, then f2 will be contributing to P. Therefore,
we will need to modify the polygon pointer of f2 to point to P. If e1 was a right edge, then e2 will be
contributing to Q. Therefore, we will need to modify the polygon pointer of e2 to point to Q.
When we find a local maximum we get two edges. If these belong to different polygons, then how do
we find the other two top edges for these polygons. There are two ways to handle this. One could
maintain pointers in the polygons to their current top edges, or one could do a search of the AEL. The first
method gives us our edges without a search, but one will have to maintain the pointers as we move from
one edge to the next. Which method is better depends on the number of edges versus the number of local
Figure 2 Merging polygons
maxima. Since there probably are relatively few local maxima, the second method is the recommended
one.
Finally, we look at how one deals with intersections of edges within a scanbeam. The way that these
intersections are handled depends on whether we have like or unlike edges. Like intersections need only
be considered if both edges are contributing and in that case the intersection point should be treated as
both a left and right intermediate vertex. (Note that in the case of like intersections, if one edge is
contributing, then the other one will be also.) Unlike intersections must always be handled. How their
intersection point is handled depends on their type, side, and relative position in the AEL.
It is possible to give some precise rules on how to classify intersection points. The rules are shown in
Table 1 in an encoded form. Edges have been specified using the following two-letter code: The first
letter indicates whether the edge is a left (L) or right (R) edge, and the second letter specifies whether it
belongs to the subject (S) or clip (C) polygon. The resulting vertex type is also specified by a two-letter
code: local minimum (MN), local maximum (MX), left intermediate (LI), and right intermediate (RI).
Edge codes are listed in the order in which their edges appear in the AEL!
For example, Rule 1 translates into the following: The intersection of a left clip edge and a left subject
edge, or the intersection of a left subject edge and a left clip edge, produces a left intermediate vertex.
Rules 1-4 are shown graphically in Figure 3(a). Figure 3(b) shows an example of how the rules apply to
some real polygon intersections.
As one moves from scanbeam to scanbeam, one updates the bottomX values of all the edges (unless
they end at the top of the scanbeam). Although the AEL is ordered as one enters a new scanbeam, if any
intersections are found in a scanbeam, the AEL will no longer be sorted after the bottomX values are
updated. The list must therefore be resorted, but this can be done in the process of dealing with the
intersections. Vatti used a temporary sorted edge list (SEL) and an intersection list (IL) to identify and
Unlike edges:
(1)
(2)
(3)
(4)
(LC
(RC
(LS
(RS
∩
∩
∩
∩
Like edges:
LS) or (LS ∩ LC)
RS) or (RS ∩ RC)
RC) or (LC ∩ RS)
LC) or (RC ∩ LS)
→
→
→
→
LI
RI
MX
MN
(5) (LC ∩ RC) or (RC ∩ LC) → LI and RI
(6) (LS ∩ RS) or (RS ∩ LS) → LI and RI
Table 1 Rules which classify the intersection point between edges
Figure 3 Intersection rules
store all the intersections in the current scanbeam. The SEL is ordered by the x-coordinate of the
intersection of the edge with the top of the scanbeam similarly to the way that the AEL is ordered by the
bottomX value of its edges. The IL is a list of nodes specifying the two intersecting edges and also the
intersection itself. It is sorted in an increasing order by the y coordinate of the intersection. The SEL is
initialized to empty. One then makes a pass over the AEL comparing the top x value of the current edge
with the top x values of the edges in the SEL starting at the right of the SEL. There will be an intersection
each time the AEL edge has a smaller top x value than the SEL edge. Note that the number of
intersections that are found is the same as the number of edge exchanges in the AEL it takes to bring the
edge into its correct place at the top of the scanbeam. Algorithm 5 has a more detailed description of this
process.
Intersection points of edges are basically treated as vertices. Such "vertices" will be classified in a
similar way as the regular vertices. If we get a local maximum, then there are two cases. If two unlike
edges intersect, then a contributing edge becomes a noncontributing edge and vice versa. This is
implemented by simply swapping the output polygon pointers. If two like edges intersect, then a left edge
becomes a right edge and a right edge becomes a left edge. One needs to swap the intersecting edges in
the AEL to maintain the x-sort.
This finishes our discussion of the Vatti algorithm in the case where there are no horizontal edges.
Now we address the more complicated general case which allows horizontal edges to exist. (However,
we never allow edges to overlap, that is, where they share a common segment.) The only changes we
have to make are in procedure ProcessEdgesInAEL. On an abstract level, it is easy to see how horizontal
edges should be handled. The classification of vertices described above should proceed as if such edges
were absent (had been shrunk to a point). Furthermore, if horizontal edges do not intersect any other
edge, then for all practical purposes they could be ignored. The problems arise when intersections exist.
Imagine that the polygons were rotated slightly so that there were no horizontal edges. The edges that
used to be horizontal would now be handled without any problem. This suggests how they should be
treated when they are horizontal. One should handle horizontal edges the same way that intersections are
handled. Note that horizontal edge intersections occur only at the bottom or top of a scanbeam. Horizontal
edges at local minima should be handled in the AddNewBoundPairs procedure. The others are handled as
special cases in that part of the algorithm which tests whether or not an edge ends in the current
scanbeam. At that point we also need to look for horizontal edges at the top of the current scanbeam and
the "Type(p)" classification should return three other cases for horizontal edges in a local maximum, left
intermediate, or right intermediate. The corresponding procedures need to continue scanning the AEL for
edges that intersect the horizontal edge until one gets past it. One final problem occurs with horizontal
edges that are oriented to the left. These would be detected too late, that is, by the time one finds the edge
intNode = record
edge e1, e2;
point intP;
end;
edge list
SEL;
intNode list IL;
procedure ProcessIntersections (real yb, yt)
begin
if Empty (AEL) then return;
BuildIL (yt − yb);
ProcessIL ();
end;
procedure BuildIL (real dy);
begin
real
topX1;
edge
e1;
boolean moreEdges;
point
p;
Initialize IL to empty;
SEL := { first edge of AEL };
for each edge e1 on AEL after the first do
begin
topX1 := TopX (e1);
{ top x value of e1 }
{ Starting with the rightmost node of SEL we shall now move from right
to left through the nodes of SEL checking for an intersection with e1 }
Let e2 denote the rightmost edge of SEL;
moreEdges := true;
while moreEdges and (topX1 < TopX (e2) do
begin
p := IntersectionOf (e1,e2);
Insert intNode (e1,e2,p) into IL;
if e2 is the left-most edge in SEL
then moreEdges := false;
else update e2 to denote edge to its left in SEL;
end;
{ Now insert e1 into SEL at the point where we quit the while loop.
If moreEdges is false then e2 had reached the left end of SEL. }
if moreEdges
then Insert e1 to the right of e2 in SEL
else Insert e1 at the left end of SEL;
end
end; { BuildIL }
procedure ProcessIL ()
begin
for each intNode (e1,e2,p) in IL do
begin
{ e1 precedes e2 in AEL and p is point of intersection }
case IntersectionType (p) of
like edge intersection : if Contributing (e1) then
begin
AddLeft (e1,p);
AddRight (e2,p);
Exchange side values of edges;
end;
local maximum
: AddLocalMax (e1,e2,p);
left intersection
: AddLeft (e2,p);
right intersection : AddRight (e1,p);
local minimum
: AddLocalMin (e1,e2,p);
end;
Swap e1 and e2 position in AEL;
Exchange adjPolyPtr pointers in edges;
end
end; { ProcessIL }
point function IntersectionOf (edge e1, e2)
{ This function returns the intersection of the two edges e1 and e2. }
Algorithm 5 An algorithm for finding intersecting edge pairs
to which they are the successor we would have already scanned past the AEL edges which intersected
them. To avoid this, the simplest solution probably to make an initial scan of the AEL for all such edges
before one checks events at the top of the scanbeam and put them into a special left-oriented horizontal
edge list (LHL) ordered by the x values of their left end points. Then as one scans the AEL one needs to
constantly compare the top x value of an edge for whether it lies inside one of these horizontal edges.
To deal with horizontal edges as just described, replace Algorithm 4 by Algorithm 6. Algorithm 6
contains three new procedures, LeftHoriz, RightHoriz, and HorizLocalMax. The first two handle the
cases where a horizontal edge follows a left or right intermediate vertex, respectively. The last deals with
a horizontal edge at the top of a local maximum. What we have to do is scan down the AEL for any edges
that intersect the current horizontal edge and deal with this intersection. The algorithms for these three
procedures are very similar and we shall only give more details in the case of the RightHoriz procedure.
See Algorithm 7. The function IntersectionType determines the intersection type as specified in Table 1.
One should keep in mind one simplification we made at the very beginning, namely, that bounds are
normalized so that we never have more than one horizontal edge in a row. The nastiest horizontal edges
procedure ProcessEdgesInAEL (real yb, yt)
{ This procedure deals with horizontal edges }
begin
edge list LHL;
{ thought of as queue }
dy, topX;
real
boolean moreEdges;
if Empty (AEL) then return;
{ Look for top left-directed horizontal intermediate edges and put them on LHL list }
Initialize LHL to empty;
for each edge e in AEL do¦
if (e ends at yt) and HasSucc (e) then
begin
Let succe denote Succ (e);
if Horiz (succe) and (Dx (succe) < 0) then Append (LHL,succe);
end;
dy := yt − yb;
Let e1 denote the first edge of AEL;
moreEdges := true;
while moreEdges do
begin
topX := TopX (e1,dy);
if not (Empty (LHL)) and (topX > OtherEndX (First element of LHL))
then
begin
he := Pop (LHL); LeftHoriz (yt,he,e1);
end
else if e terminates at level yt in vertex p
then
case Type (p) of
local maximum
: { same as in Algorithm 3.12 }
left intermediate : { same as in Algorithm 3.12 }
right intermediate : { same as in Algorithm 3.12 }
horizontal local maximum : begin
Let e2 denote the edge on AEL after e1;
if e1 and e2 come from matching bounds
then { no intersections in horizontal edge }
begin
if Contributing (e1) then
AddLocalMax (e1,e2,p);
Delete e1 and e2 from AEL;
end
else HorizLocalMax (e1,e2,p);
end;
horizontal right intermediate : RightHoriz (yt,e1,p);
end
else { Update e1's bottomX value }
SetBottomX (e1,topX);
{ Note that if we encountered intersections of horizontal edges, then we may
have scanned past a number of edges on the AEL after e1 at this point. }
Update e1 to denote the next "untouched" edge in the AEL or
set moreEdges to false if there are none;
end;
end;
real function OtherEndX (edge e)
{ The function returns the x-coordinate of the other end of the horizontal edge }
begin
return (BottomX (e) + Dx (e));
end;
Algorithm 6 Algorithm for processing the AEL edges (with horizontal edges)
procedure RightHoriz (real yt; edge e1; point p)
{ We assume that Succ (e1) is a right-oriented horizontal edge which is not a
local maximum. }
begin
real
hrx, nextTop;
boolean hasNext;
if Contributing (e1) then
begin
if Side (e1) = left then AddLeft (e1,yt)
else AddRight (e1,yt);
end;
Let he denote the horizontal edge Succ (e1);
Copy e1's side, contributing, and adjPolyPtr field values to he;
hrx := OtherEndX (he);
{ hrx is the right x value of the edge he }
hasNext := HasNextEdge (e1);
if hasNext then
begin
Let e2 denote the edge on AEL after e1;
nextTop := TopX (e2);
{ top x value for e2 }
{ loop through all edges on AEL after e which intersect he }
while hasNext and (nextTop ≤ hrx) do
begin
Let p be the point where he and e2 intersect;
case IntersectionType (he,e2) of
like edge intersection : if Contributing (he) then
begin
AddLeft (e2,p);
SetSide (he,Side (e2));
end;
local maximum :
begin
if Contributing (he) then AddLocalMax (he,e2,p);
SetContributing (he,false);
SetContributing (e2,false);
end;
left intermediate : if Contributing (he) then AddLeft (e2,yt);
right intermediate : if Contributing (he) then
begin
AddRight (he,p);
SetAdjPolyPtr (he,AdjPolyPtr (he));
end;
local minimum :
begin
if Contributing (he) then
begin
AddRight (he,p);
SetAdjPolyPtr (he,AdjPolyPtr (he));
end;
SetContributing (he,true);
SetContributing (e2,true);
end
end;
Exchange value of contributing fields for he and e1;
if TopY (e2) = yt
{ edge ends at top of scanbeam }
then Replace e2 in AEL by Succ (e2)
else SetBottomX (e2,nextTop);
hasNext := HasNextEdge (e2);
if hasNext then
begin
nextTop := TopX (e2); { top x value for next edge }
Update e2 to next edge on AEL;
end
end
end;
Delete e1 from AEL;
Insert Succ (he) into AEL;
InsertIntoSBL (TopY (Succ (he)));
end; { RightHoriz }
boolean function HasNextEdge (edge e)
{ Return true if there is an edge to the right of e on the AEL and false otherwise. }
Algorithm 7 Algorithm for dealing with right-oriented horizontal edges
are the left-oriented ones. These will always follow a left intermediate vertex. We cannot eliminate them,
but at least we have reduced their number.
This completes our description of the basic Vatti algorithm. The algorithm can be optimized in the
common case of rectangular clip bounds. Another optimization is possible if the clip polygon is fixed
(rectangular or not) by computing its bounds only once and initializing the LML to these bounds at the
beginning of a call to the clip algorithm.
An attractive feature of Vatti's algorithm is that it can easily be modified to generate trapezoids. This
is particularly convenient for scan line oriented rendering algorithms. A trapezoid can be represented by
a record as shown in Data 2. Each local minimum starts a trapezoid or breaks an existing one into two
depending on whether the local minimum starts with a left-right (contributing case) or right-left
(noncontributing case) edge pair. At a contributing local minimum we create a trapezoid setting all of its
fields except for topY. Trapezoids are output at local maxima and left or right intermediate vertices. A
noncontributing local minimum should output the trapezoid it is about to split and the trapezoid pointers
of the relevant edges updated to the two new trapezoids. The following simple loop fills a trapezoid:
for y:=bottomY to topY do
begin
DrawLine (leftX,y,rightX,y);
leftX := leftX + leftDx;
rightX := rightX + rightDx;
end;
Vatti compared the performance of the trapezoid version of his algorithm to the Sutherland-Hodgman
algorithm and found it to be roughly twice as fast for clipping (the more edges, the more the
trapezoid = record
real leftX,
leftDx,
rightX,
rightDx,
bottomY,
topY;
end;
{ the x-coordinate of the bottom left vertex }
{ the reciprocal of the slope of the left edge }
{ the x-coordinate of the bottom right vertex }
{ the reciprocal of the slope of the right edge }
{ the y-coordinate of the bottom edge }
{ the y-coordinate of the top edge }
Data 2 The trapezoid data structure
improvement) and substantially faster if one does both clipping and filling.
A special case of the trapezoid form of the Vatti algorithm will be very useful when we deal with
trimmed surfaces in Section 14.8. The reader can find more details on how to deal with trapezoids there.
Finally, we can also use the Vatti algorithm for other operations than just intersection. All we have to
do is replace the classification rules. For example, if we want to output the union of two polygons, use
the rules
(1)
(2)
(3)
(4)
(LC
(RC
(LS
(RS
∪
∪
∪
∪
LS)
RS)
RC)
LC)
or
or
or
or
(LS ∪ LC)
(RS ∪ RC)
(LC ∪ RS)
(RC ∪ LS)
→
→
→
→
LI
RI
MN
MX
Local minima of the subject polygon which lie outside the clip polygon and local minima of the clip
polygon which lie outside the subject polygon should be treated as contributing local minima.
For the difference of two polygons (subject polygon minus clip polygon) use the rules
(1)
(2)
(3)
(4)
(RC
(RS
(RS
(RC
−
−
−
−
LS)
LC)
RC)
RS)
or
or
or
or
(LS
(LC
(LC
(LS
−
−
−
−
RC)
RS)
LS)
LC)
→
→
→
→
LI
RI
MN
MX
Local minima of the subject polygon which lie outside the clip polygon should be treated as contributing
local minima.
Download