Trimmed Surfaces

advertisement
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.
Download