Trimmed Surfaces Most algorithms that triangulate trimmed surfaces obtain the triangulation by triangulating the region bounded by the trimming curves in parameter space. This means that the trimmed surface problem has as part of it a problem that is interesting on its own, namely, the triangulation problem for such planar regions. This is a well-known problem in computational geometry and Section 17.6 discusses algorithms for triangulating planar polygons without holes and their complexity. On the other hand, since we are interested in the rendering of trimmed surfaces, we do not necessarily need triangles. We shall now describe an implementation of a relatively simple algorithm that solves the following general problem: Trapezoidation Problem: Given a closed planar region bounded by k closed polygonal curves, k ≥ 1 , decompose it into a collection of trapezoids. Of course, once a region has been divided into trapezoids, it is obviously easy to get a triangulation, so that we will also have solved the corresponding triangulation problem. To use our algorithm to triangulate trimmed surfaces one would first have to use some other algorithm to represent the trimming curves in parameter space as a suitable collection of closed polygonal curves. Additional criteria would have to be used to determine if the triangulation is fine enough. It should also be noted beforehand that since our trapezoidation algorithm does not concern itself with the parametrization of the trimmed surface, the induced triangulation of the surface may contain very thin triangles, so that extra work would have to be done to avoid that. Nevertheless it leads to a quick-and-dirty algorithm for displaying trimmed surfaces and we shall describe the algorithm with that application in mind. (We should mention the algorithm in [ŽalC99] that seems to be based on similar ideas; however, the author has used the algorithm we are about to describe for a number of years even though it was never published until now.) Our trapezoidation algorithm is based on an idea from Vatti’s clipping algorithm ([Vatt92]) that we described in Section 3.10 and the document VattiClip.doc. Recall that the central idea of his algorithm is to define polygons in terms of left and right bounds and then to obtain the final clipped regions using a fairly standard scan line approach with active edge lists. This idea of polygon bounds can also be used effectively to generate a trapezoidal decomposition of a polygonal region. The domain can be quite general for our algorithm. It may have holes or consist of more than one component. The main task will be to show how to subdivide a given polygonal region into trapezoids using the left and right bounds of the bounding curves for the polygons of the region. (Trapezoids in this discussion will include the “degenerate” case of triangles.) Because of the application to trimmed surfaces, we also give an algorithm for subdividing a trapezoid appropriately for a wireframe display of the parametric surface defined on it. This would also apply to a z-buffer type display algorithm. The complete details for an algorithm that displays trimmed parametrized surfaces (above and beyond what is discussed here) depend on the type of display which is desired. As an example of one approach for shaded surfaces using a z-buffer algorithm see [RoHD89] but replace the regions that are used there with the trapezoids from here. The discussion here will depend heavily on what was done in the document VattiClip.doc, but a simplified version of the algorithm described in that document will suffice for our regions. Major simplifications arise from the fact that although the region may consist of more than one polygon, none intersect here. On the other hand, we shall need to worry about “knots”, so that the data structures will be modified slightly to fit our current needs. We shall concentrate the discussion on those aspects that are different from what was done in the document VattiClip.doc. Given our planar region, first determine the left and right bounds of the polygons making up the region and store this information in a local minima list (LML) as before. Next, compute the trapezoidal decomposition of the region from the LML by scanning the region from the bottom to the top using scanbeams whose y-values are kept in a scanbeam list (SBL). Recall that the values for this list are not generated all at once. We start with the list of all the local y-minima of the polygon boundaries and the yvalues of the top end point of the edges that start at the local minima and then add to the list incrementally on an edge by edge basis. As we scan the world one scanbeam at a time, an active edge list (AEL) lists all the edges intersected by the current scanbeam just like in the document VattiClip.doc. When we begin processing a scanbeam, before we look at any of the edges of the AEL, we check the LML to see if any of its bound pairs start at this level. These bounds correspond to local minima. Each of these will start a trapezoid or break an existing one into two depending on whether the local minimum starts with a left-right or right-left edge pair. The first nonhorizontal edge from each bound of a local minimum is added to the AEL. Note that horizontal edges are never put on the AEL. Intermediate horizontal edges basically are skipped but they do create “knots” as will be described later. After any new edges from the LML are added to the AEL, we process the edges of the AEL one at a time starting from the left. See Algorithm 1 for a more precise description of the top-level algorithm described so far. { Global variables } real list SBL; bound pair list LML; edge list AEL; trapezoid list TRAPS; { an ordered list of distinct reals thought of as a stack} { a list of pairs of matching polygon bounds } { a list of nonhorizontal edges ordered by x-intercept with the current scan line} { the trapezoids are stored here as algorithm progresses } trapezoid list function ConvertToTrapezoids (curve list PL) { The list PL represents a polygon with holes. The first curve in the list is the outer boundary of the polygon and the others, if any, are the holes. The procedure partitions the polygon into trapezoids. The list of these trapezoids is returned to the calling procedure. } begin real yb, yt; Initialize LML, SBL to empty; { Define LML and the initial SBL } for each curve P in PL do UpdateLMLandSBL (P); Initialize TRAPS, AEL to empty; yb := PopSBL (); repeat AddNewBoundPairs (yb); yt := PopSBL (); ProcessEdgesInAEL (yb,yt); yb := yt; until Empty (SBL); { bottom of current scanbeam } { top of current scan beam } return (TRAPS); end; Algorithm 1 The trapezoid creation algorithm edge = record real bottomX, { initially the x-coodinate of 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 } boolean horiz; { whether edge is horizontal or not } edge pointer succPtr; { pointer to successor edge (denoted by succ) in the left or right bound to which it belongs } (left,right) side; { is it a left or right bound edge? } trapezoid pointer adjTrapPtr; { pointer to trapezoid (denoted by adjTrap) associated to the edge } { The next two fields are used to link the edges into the doubly-linked list AEL } edge pointer nextPtr; { pointer to next edge (denoted by next) in AEL } prevPtr; { pointer to previous edge (denoted by prev) in AEL } end; Data 1 The edge data structure Continuing on to the next level of detail, the data structure associated to an edge is shown in Data 1. 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 that define it. Each edge has all of its fields defined here except for the side, adjTrapPtr, nextPtr, and prevPtr fields. The latter are defined in the InsertIntoAEL or ReplaceEdgeBySucc procedures described later. Since every edge will belong to one or more trapezoids in the final decomposition, the pointer adjTrapPtr in an edge record points to the “current” trapezoid associated to the edge. This trapezoid will also be referred to as the adjacent trapezoid of the edge. It may change as we move from one scanbeam to the next. 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). (3) No right bound has a top horizontal edge (any such edges are shifted to the left bound). Next, consider the AddNewBoundPairs procedure. It and all of its auxiliary procedures are shown in Algorithm 3. The task of the AddNewBoundPairs procedure is to add those bounds that start at the current scan line into the AEL list. This is not as straightforward as it may seem. Some of the complicating possibilities are shown in Figure 1 which shows some of the events that can occur in the processing of the scanbeam between the two scan lines at yb and yt. As we deal with local minima at the points p1 and p2, we have to worry about potential “knots” for either new or old trapezoids. By a knot for the top or bottom edge of a trapezoid we mean the x-coordinate of a vertex of a polygon which must be included in any subdivision of the trapezoid. Knots are needed because adjacent trapezoids might have only part of an edge in common. Without keeping track of the knots, subdivisions of these adjacent trapezoids might subdivide the part of the edge that they have in common in different ways which would lead to gaps in the surface defined over these trapezoids. In Figure 1, when we process the local minimum at p1 we have to add a top knot to trapezoid tz1. When we process p2, we must add two top procedure UpdateLMLandSBL (closed polygonal curve P) { Finds the bounds of P and adds them to LML. Only the edge fields bottomX, topY, dx, horiz, and succPtr are defined here. The other 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; procedure InsertIntoSBL (real y) { If y is not yet in SBL, then insert it into SBL in sorted order } real function PopSBL () { Delete the smallest element of SBL and return its value } Algorithm 2 Updating the LML and SBL knots corresponding to p2 and p3 to tz1. The knots corresponding to points p4, p5, and p6 in the bottom edge of trapezoid tz6 would have been specified when the scanbeam below the current one was processed and the local maxima were encountered. Only knots interior to an edge are kept track of in the trapezoid data structure. Although the end points of the top and bottom edges of a trapezoid clearly fall under the label of knots, we do not include them in the knot arrays, because that would be redundant data. The final data structure for a trapezoid is shown in Data 2. Figure 1 Complications for the AddNewBoundPairs procedure procedure AddNewBoundPairs (real yb) while not (Empty (LML)) and (first bound pair BP on LML starts at yb) do begin AddEdgesToAEL (BP,yb); Delete BP from LML; end; procedure AddEdgesToAEL (bound pair (B1,B2); real yb) { Bound pair (B1,B2) starts at yb } begin trapezoid pointer tzPtr; { 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); SetSideFields (e1,e2); { use a parity argument to determine the side of the edges } case Side (e1) of left: begin tzPtr := NewTrap (BottomX (e1),Dx (e1),BottomX (e2),Dx (e2),yb); SetAdjTrapPtr (e1,tzPtr); SetAdjTrapPtr (e2,tzPtr); end; right: begin Let preve denote the edge of AEL to left of e1; if ClosableAdjTrap (preve) then CloseAndMakeNewAdjTrap (preve,BottomX (e1),Dx (e1),yb); else AdjustTrapezoidWidth (AdjTrap (preve),e1); SetAdjTrapPtr (e1,AdjTrapPtr (preve)); Let nexte denote the edge of AEL to right of e2; tzPtr := NewTrap (BottomX (e2),Dx (e2),BottomX (nexte),Dx (nexte),yb); SetAdjTrapPtr (nexte,tzPtr); Check BotKnots (AdjTrap (preve)) and shift any bottom knots bigger than BottomX (e1) to botKnots of trapezoid pointed to by tzPtr; UpdateTopKnots (belowTrap,e1,e2); end end 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 fields of e1 and e2 using a parity argument. Set the nextPtr and prevPtr fields. } trapezoid pointer function NewTrap (real lx, ldx, rx, rdx, y) { Create a new trapezoid whose leftX, leftDx, rightX, rightDx, topY, and bottomY fields are set to lx, ldx, rx, rdx, y, and y, respectively. The botKnots and topKnots arrays are set to empty. Add the trapezoid to the end of TRAPS and return a pointer to it. } procedure CloseAndMakeNewAdjTrap (ref edge e; real rx, rdx, yt) { e is a left edge } begin real ldx, lx; Let adjTz denote the trapezoid adjacent to e; CloseTrapezoid (adjTz,yt); ldx := LeftDx (adjTz); lx := LeftX (adjTz) + ldx*(yt − BottomY (adjTz)); SetAdjTrapPtr (e,NewTrap (lx,ldx,rx,rdx,yt)); end; { CloseAndMakeNewAdjTrap } procedure CloseTrapezoid (ref trapezoid tz; real yt) SetTopY (tz,yt); procedure UpdateTopKnots (ref trapezoid tz; edge e1, e2) { Update top knots of tz. At least one knot will be added. } begin Add BottomX (e1) to TopKnots (tz); if BottomX (e1) < BottomX (e2) then { a horizontal edge connects e1 and e2 } Add BottomX (e2) to TopKnots (tz); end; { UpdateTopKnots } procedure AdjustTrapezoidWidth (ref trapezoid tz; edge e) begin SetRightX (tz,TopRightX (e)); SetRightDx (tz,Dx (e)); end; { WidenTrapezoid } Algorithm 3 The AddNewBoundPairs procedure trapezoid = record real leftX, leftDx, rightX, rightDx, bottomY, topY; real array topKnots, botKnots; 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 } { the interior knots for the top edge } { the interior knots for the bottom edge } Data 2 The trapezoid data structure The heart of the AddNewBoundPairs procedure is the AddEdgesToAEL procedure. In general, at a left-right edge pair minimum, we (1) create a new trapezoid T with all fields defined, except that at this point it is degenerate because the topY and bottomY fields are temporarily set to the same value. (2) make the adjTrapPtr of the two edges point to T. A local minimum at a right-left edge pair is more complicated. Basically, the trapezoid below us is about to split. First of all, let us introduce some notation. Call a trapezoid T closable if (1) its topY and bottomY fields have the same value, and (2) the common value of these two fields is less than the y-coordinate of the bottom of the current scanbeam. The process of setting the topY field to a value larger than the value of the bottomY field (thereby making the trapezoid “nondegenerate”) will be referred to as closing the trapezoid. Sometimes when a trapezoid is closed, we need to update its rightX and rightDx fields. A trapezoid whose topY and bottomY fields have distinct values is said to be closed. Closed trapezoids may still have incomplete knot arrays however. Dealing with a right-left edge pair (e1,e2) therefore involves three steps. Let prevE be the edge to the left of e1 and let belowTrap be the trapezoid below this one which is splitting. (1) If the trapezoid adjacent to prevE is still open, then close it and create a new trapezoid T1 for e1 and prevE. For example, in Figure 2(a), when we get to p1, tz1 would be adjacent to e1. We would close it and create tz2 for e1 and e3. On the other hand, when we get to p2, we do not need to close tz3. (2) Create a new open trapezoid T2 and make it the adjacent trapezoid for e2 and Next(e2). Any knots from the bottom of prevE’s trapezoid bigger than BottomX(e2) will have to be moved to T2. For example, in Figure 2(b), tz4 would already have bottom knots corresponding to p4, p5, and p6 as we start processing the scanbeam. Then at p1 (and p2) the knots p4, p5, and p6 would be shifted. (a) (b) Figure 2 Closing and splitting trapezoids (3) The new local minimum at BottomX(e1) creates a knot in the top of belowTrap. See point p1 in Figure 2. If BottomX(e2) is larger than BottomX(e1), then we get a second knot. See edge p2p3 in Figure 2(b). These knots must be added to the top knots of belowTrap. By the way, keeping track of the trapezoid below the current position in the scan line is not hard, because trapezoids are added to TRAPS in a bottom-up left-to-right manner, so that we merely need to keep moving a pointer to the right appropriately. One determines whether an edge is a left or right edge at the time that it is put on the AEL. This is done by a parity type argument. If there are an even number of edges on the AEL to the left of the new edge, then this edge is a left edge, otherwise, it is a right edge. Figure 2(b) and Table 1 should clarify how the AddNewBoundPairs and AddEdgesToAEL procedures work. We use the notation shown in Figure 2(b) and the following abbreviations: xpi is the x-coordinate of point pi dxi is the value of the dx field in edge ei. xib is the x-coordinate of the point where edge ei meets scan line at yb. xit is the x-coordinate of the point where edge ei meets scan line at yt. Because the trapezoids tz2 and tz3 are already defined and do not change, we do not include them in the table. Next we describe the AEL processing procedure. See Algorithm 4. After adding any bottom edges from local minima to the AEL, we process the edges of the AEL one at a time starting from the left. If an edge extends past the current scanbeam, then we simply update the bottomX field of the edge. If the edge ends at the top of the current scanbeam, then one or two trapezoids are closed and the edge is processed in one of two ways depending on whether the top vertex is an intermediate vertex or a local maximum. If the vertex is an intermediate vertex, then the edge is replaced in the AEL by its successor edge and the left/right flag is passed on to the new edge. In the case of a left intermediate vertex, the adjacent trapezoid is closed. In the case of a right intermediate vertex we close the trapezoid to its left if it is still open. If the top vertex is a local maximum, then two bounds meet which may belong either to the same or to different trapezoids. If the bounds belong to the same trapezoid, that is, the edge e was a left edge, then the trapezoid adjacent to e is closed. If the bounds belonged to different trapezoids, then two at beginning after (e7,e8) pair AEL Trapezoid values { e1,e6 } tz4 (x1b,dx1,x6b,dx6,yb,yb,{},{ xp4,xp5,xp6 }) tz5, tz6, and tz7 not yet defined topKnots of tz1: {} { e1,e7,e8,e6 } tz4 (x1b,dx1,xp1,dx7,yb,yb,{},{}) tz5 (xp1,dx8,x6b,dx6,yb,yb,{},{ xp4,xp5,xp6 }) tz6 and tz7 not yet defined topKnots of tz1: { xp1 } after (e9,e10) pair { e1,e7,e8,e9,e10,e6 } tz4 (x1b,dx1,xp1,dx7,yb,yb,{},{}) tz5 (xp1,dx8,xp2,dx9,yb,yb,{},{}) tz6 (xp3,dx10,x6b,dx6,yb,yb,{},{ xp4,xp5,xp6 }) tz7 not yet defined topKnots of tz1: { xp1,xp2,xp3 } Table 1 Stages of the AddNewBoundPairs procedure trapezoids (one to the left of e and the other to the right of the second bound) must be closed and a new merged trapezoid T started. Knots are a complicating factor in this last case. The top end point of e will contribute a knot to the botKnots array of trapezoid T. We must also check if our local maximum has a top horizontal edge because then a second knot will be added to the same botKnots array. Table 2 gives an example of these steps using Figure 2(b) again. Figures 3(a) and 4(a) show some sample polygons and their final trapezoidal decompositions. ptClassType = (locMax,leftInt,rightInt); procedure ProcessEdgesInAEL (real yb, yt) begin edge pointer e1Ptr; { e1 will denote the edge to which e1Ptr points } if Empty (AEL) then return; e1Ptr := pointer to first edge of AEL; { e1Ptr will move from left to right through AEL } repeat if TopY (e1) = yt { does edge end at top of scanbeam? } then case EndPtClass (e1) of locMax : begin OutputLocalMax (e1,yt); Let e2 denote the edge to the right of e1 in AEL; Delete both e1 and e2 from AEL; e1Ptr := NextPtr (e2); end; leftInt : begin OutputLeft (e1,yt); ReplaceEdgeBySucc (e1); e1Ptr := NextPtr (e1); end; rightInt : begin OutputRight (e1,yt); ReplaceEdgeBySucc (e1); e1Ptr := NextPtr (e1); end end { case EndPtClass } else { Update bottomX of e1 } SetBottomX (e1,BottomX (e1) + (yt − yb)*Dx (e1)); until e1Ptr = nil end; { ProcessEdgesInAEL } ptClassType function EndPtClass (edge e) if IsLastNonHorizontalEdgeOfBound (e) then return (locMax) else case Side (e) of left : return (leftInt); right : return (rightInt; end; procedure OutputLocalMax (edge e1; real yt) begin real knt1, knt2; case Side (e1) of left : CloseTrapezoid (AdjTrap (e1),yt); right : begin Let preve denote edge to left of e1 in AEL; if Open (preve) then CloseAndMakeNewAdjTrap (preve,BottomX (e1),Dx (e1),yt); Let e2 denote edge to right of e1 in AEL; CloseTrapezoid (AdjTrap (e2),yt); Let prevTz denote trapezoid adjacent to preve; knt1 := top right value of trapezoid AdjTrap (e1); { a new knot } Add knt1 to BotKnots (prevTz); knt2 := TopLeftX (AdjTrap (e2)); if knt1 < knt2 then Add knt2 to BotKnots (prevTz); AdjustTrapezoidWidth (prevTz,Next (e2)); end end end; { OutputLocalMax } procedure OutputLeft (edge e; real yt) begin Let nexte denote edge to right of e in AEL; CloseAndMakeNewAdjTrap (e,BottomX (nexte),Dx (nexte),yt); if HasRightHorizontalSucc (e) then begin Let adjTz denote trapezoid adjacent to i; Add (BottomX (Succ (e)) + Dx (Succ (e))) to topKnots of adjTz; if (TopY (nexte) = yt) and HasLeftHorizontalSucc (nexte) then Add (BottomX (Succ (nexte)) + Dx (Succ (nexte))) to topKnots of adjTz; end end; { OutputLeft } procedure OutputRight (edge e; real yt) begin real knt; Let prevTz denote trapezoid adjacent to Prev (e); { check if trapezoid still open } if AdjTrap (e) = prevTz then CloseAndMakeNewAdjTrap (Prev (e),BottomX (e),Dx (e),yt); Let nexte denote edge Succ (e); if HasLeftHorizontalSucc (e) then begin knt := BottomX (nexte) + Dx (nexte); Add knot knt into TopKnots (AdjTrap (e)); SetRightX (prevTz,knt); nexte := Succ (nexte); end; SetRightDx (prevTz,RightDx (nexte)); SetAdjTrapPtr (e,AdjTrapPtr (Prev (e))); end; { OutputRight } procedure ReplaceEdgeBySucc (edge e) begin Let succe denote edge Succ (e); if Horizontal (succe) then Let succe denote edge Succ (succe); Replace e with succe in AEL; SetSide (succe,Side (e)); InsertIntoSBL (TopY (succe)); SetAdjTrapPtr (succe,AdjTrapPtr (e)); end; { ReplaceEdgeBySucc } boolean function HasRightHorizontalSucc (edge e) { Return true if e has a right oriented (dx > 0) horizontal successor and false otherwise } boolean function HasLeftHorizontalSucc (edge e) { Return true if e has a left oriented (dx < 0) horizontal successor and false otherwise } Algorithm 4 Processing the edges on the AEL AEL at beginning Trapezoid values Edges (topY,topKnots) (bottomX,adjacent trapezoid) { e1,e7,e8,e9,e10,e6 } tz4 (yb,{}) e1 (x1b,tz4) tz5 (yb,{}) tz6 (yb,{}) after e1 { e1,e7,e8,e9,e10,e6 } after e8 { e1,e11,e9,e10,e6 } after e10 { e1,e11,e10,e6 } no change tz4 (yt,{ xp7 }) tz7 (yt,{}) no change e1 (x1t,tz4) e1 (x1t,tz7) e10 (x10t,tz6) Table 2 Stages of the AEL processing procedure (a) (b) Figure 3 Trapezoidal decompositions of polygons (a) (b) Figure 4 More trapezoidal decompositions of polygons Next, we describe an algorithm for a wireframe display of a parametric surface with domain a trapezoid. This is again a scan line type algorithm for use also in the context of a z-buffer or related type display. The main issue therefore is to come up with a convenient way to subdivide the trapezoidal u-v domain appropriately. Assume that du and dv are the maximum u and v step sizes, respectively, that we are to use. Because of the scan line nature it is convenient to use four arrays botKnts, topKnts, botPts, and topPts. We also assume that trapezoids have a doBottom field which specifies whether or not the bottom edge of the trapezoid is to be drawn (we always draw the top edge). The reason for this is that if two trapezoids are adjacent, then we might not want to draw their common edge twice. Note that because of the way that trapezoids are generated they never have common side edges. Incorporating this doBottom field in the trapezoid generation algorithm is easy. It is set to true for all trapezoids whose bottom edge come from a local minimum horizontal edge or which meet a local maximum horizontal edge. Given a trapezoid we first want to subdivide it into horizontal slices that are at most dv units high and then to subdivide each of these slices into rectangular or triangular patches whose top and bottom edges are at most du units wide. Furthermore, any knots that the trapezoid may have must appear as vertices for the patches. Getting the horizontal slices is easy. The tricky part is to divide those slices in a way that will include the knots in the division. The algorithm deals with slices based on a case by case basis. The generic cases are shown in Figure 5, however, the existence of knots can complicate the correct implementation somewhat. Nevertheless, drawing a slice is reduced to drawing a sequence of subdivided triangles with a single bottom vertex, subdivided triangles with a single top vertex, and/or subdivided parallelogram type regions. Algorithm 5 outlines the trapezoid display algorithm described above. The variables leftU and rightU in the algorithm hold the u value of left and right bottom corners of the current trapezoid slice. The variables leftU1 and rightU1 hold the u value of the left and right top corners. We shall use the variable m for indices into bottom arrays and k for indices into top arrays. Sample outputs are shown in Figures 3(b) and 4(b). Figure 5 Cases when decomposing horizontal trapezoid slices { Global variables: botKnts/botPts [0..botN] , topKnts/topPts [0..topN] } real array botKnts, topKnts; point array botPts, topPts; integer botN, topN; boolean firstSpan; real leftU, rightU, leftU1, rightU1; procedure DisplayTrapezoid (trapezoid tz; parametrization f; real du, dv) { This procedure gives a wireframe display of a surface with parametrization function f defined over the u-v domain specified by the trapezoid tz using increments du and dv. } begin real v; DetKnots (LeftX (tz),RightX (tz),du,botKnots,botKnts,botN); Evaluate f at botKnts[0..botN] and store in botPts[0..botN]. { Set flag determining if we need to draw bottom edge of the trapezoid. } firstSpan := DoBottom (tz); leftU := LeftX (tz); rightU := RightX (tz); v := min (BottomY (tz) + dv,TopY (tz)); while true do begin leftU1 := leftU + dv*LeftDx (tz); rightU1 := rightU + dv*RightDx (tz); DetKnots (leftU1,rightU1,du,nil,topKnts,topN); Evaluate f at topKnts[0..topN] and store in topPts[0..topN]; if topN = 0 then DownTriangle (0,botN,0) else if botN = 0 then UpTriangle (0,1,topN) else OneSpan (); end; if v = TopY (tz) then return; botN := topN; leftU := leftU1; rightU := rightU1; Exchange the arrays topKnts, botKnts and topPts, botPts; v := max (v + dv,TopY (tz)); firstSpan := false; { while } end { DisplayTrapezoid } procedure DetKnots (real u0, u1, du; real array knts; ref real array actKnts; ref integer n) { The knots, if any, in the array knts are assumed to lie in (u0,u1). They partition the interval [u0,u1] and this partition gets further subdivided into segments of length at most du. The resulting partition is returned in actKnts[0..n]. } procedure UpTriangle (integer m, k0, k1) { Triangle has a single bottom vertex and k0 > 0 . } begin integer k; for k:=k0 to k1 do if triangle (botPts[m],topPts[k−1],topPts[k]) is front-facing then Draw segments [botPts[m],topPts[k−1]] and [topPts[k−1],topPts[k]]; if (m = botN) and (k1 = topN) and (last drawn triangle was front-facing) then Draw segment [botPts[m],topPts[k1]]; end; { UpTriangle } procedure DownTriangle (integer m0, m1, k) { Triangle has a single top vertex. We draw vertical edges only unless this is the first horizontal trapezoid slice and the bottom edge was to be drawn. m0 < m1 . } begin integer m; for m:=(m0+1) to m1 do if triangle (topPts[k],botPts[m−1],botPts[m]) is front-facing then begin Draw segment [topPts[k],botPts[m−1]]; if firstSpan then Draw segment [botPts[m−1],botPts[m]]; end; if (m1 = botN) and (k = topN) and (last drawn triangle was front-facing) then Draw segment [topPts[k],botPts[m1]]; end; { DownTriangle } procedure CenterSection (integer m, k0; ref integer lastm, lastk) { 0 ≤ k0 < topN , 0 ≤ m . This procedure basically draws a sequence of parallelograms. At the end, either lastm = botN or lastk = topN. } begin lastk := k0 + 1; lastm := m; while (lastk ≤ topN) and (lastm < botN) do begin if either triangle (botPts[lastm],topPts[lastk−1],topPts[lastk]) or triangle (botPts[lastm],topPts[lastk],botPts[lastm+1]) is front-facing then begin Draw segment [topPts[lastk],topPts[lastk−1]]; Draw segment [topPts[lastk−1],botPts[lastm]]; if firstSpan then Draw segment [botPts[lastm],botPts[lastm+1]]; end; lastk := lastk + 1; end; lastm := lastm + 1; lastk := lastk − 1; if (one of the last two triangles was front-facing) and (lastk = topN) and (botN < lastm) then Draw segment [topPts[topN],botPts[botN]]; if lastm > botN then lastm := botN; end; { CenterSection } procedure OneSpan () { topN > 0 . } begin if leftU1 < leftU then Case1 () else if leftU1 < rightU then Case2 () else Case3 (); end; { OneSpan } procedure Case1 () { leftU1 < leftU } begin integer lastm, lastk; if rightU1 ≤ leftU then { Case 1.1 } begin UpTriangle (0,1,topN); DownTriangle (0,botN,topN); return; end; { leftU < rightU1 } lastm := 0; lastk := LastIndex (0,leftU,topKnts); if lastk > 0 then UpTriangle (0,1,lastk); if rightU1 < rightU { Case 1.2 } then begin if lastk < topN then CenterSection (0,lastk,lastm,lastk); if lastk < topN then UpTriangle (botN,lastk+1,topN) else DownTriangle (lastm,botN,topN) end else { Case 1.3 } begin CenterSection (0,lastk,lastm,lastk); if lastm < botN then DownTriangle (lastm,botN,topN) else if lastk < topN then UpTriangle (botN,lastk+1,topN); end; end { Case1 } procedure Case2 () { leftU ≤ leftU1 < rightU } begin integer lastm, lastk; if leftU < leftU1 then begin lastm := LastIndex (0,leftU1,botKnts); if lastm > 0 then DownTriangle (0,lastm,0); end else lastm := 0; { Case 2.1 } if rightU1 < rightU then begin CenterSection (lastm,0,lastm,lastk); if lastk < topN then UpTriangle (botN,lastk,topN); if lastm < botN then DownTriangle (lastm,botN,topN); return; end; { rightU ≤ rightU1 - Case 2.2 } lastk := 0; if lastm < botN then CenterSection (lastm,0,lastm,lastk); if lastm < botN then DownTriangle (lastm,botN,topN) else if lastk < topN then UpTriangle (botN,lastk+1,topN); end; { Case2 } procedure Case3 () begin DownTriangle (0,botN,0); UpTriangle (botN,1,topN); end; { Case3 } integer function LastIndex (integer k; real u; real array knts) { This function returns index i so that knts[i] < u < knts[i+1] } Algorithm 5 The trapezoid display algorithm Figure 6 Trapezoidal decomposition of trimmed Bezier patch Finally, consider Figure 6. This shows the triangles and parallelograms that are generated for a trimmed Bezier surface patch where the bounding curves are cubic B-splines. Notice that parts of the domain were subdivided more than others. This may not be desirable. The “feature” has two causes: (1) All trapezoids are drawn no matter how thin they are. (2) The vertical height of trapezoids is determined by scanbeams and these are usually thinner than the resolution of the curves would suggest. One can mitigate these causes by using a smaller resolution for the bounding curves, expecting the trapezoids to be smaller anyway, or one could get fancier with the algorithms described above and change the subdivision of the bounding curves in a dynamic way from one scanbeam to the next. Alternatively, one could do some post-processing of the trapezoids. Actually, the problem of thin triangles or trapezoids is only part of the problem. As we pointed out in Section 14.3, how the domain of a parametrization is polygonized has, in principle, little bearing on the polygonization of the surface that this polygonization induces. If getting a uniform polygonization for a trimmed surface is important, then an approach like in [ChPP98] could be considered.