{ This unit draws polygons fast. It draws only polygons which are monotone vertical. That means only polygons which you can fill with continues horizontal lines. Fortunately that are the polygons which are mostly used in 3d graphics. } {*****************************************************************} {* UnitName : FASTPOLY.PAS *} {* Purpose : Draw monotone vertical polygons fast *} {* Version : 1.5 *} {* Author : Daan de Haas *} {* Date : 20/10/1993 *} {* Last update : 9/06/1994 *} {* Language : Borland Turbo Pascal 7.0 *} {* Fidonet : Daan de Haas (2:500/104.6141) *} {* Internet : Daan.de.Haas@p6141.f104.n500.z2.fidonet.org *} {*****************************************************************} {* VGA mode $13 and 386 processor *} {* Literatur : Dr Dobb's XSharp *} {$R-,S-,Q-,I-} UNIT FastPoly; {**************************} INTERFACE {**************************} TYPE PPoint = ^TPoint; TPoint = RECORD x,y:integer; END; PPolygon = ^TPolygon; PPointsList = ^TPointsList; TPointsList = ARRAY[0..9999] OF TPoint; TPolygon = RECORD length,color:word; PointPtr:PPointsList; END; PHLine = ^THLine; THLine = RECORD XStart,XEnd:word; END; PHLineArray = ^THLineArray; THLineArray = ARRAY[0..9999] OF THLine; THLineList = RECORD length,YStart:integer; HLinePtr : PHLineArray; END; PROCEDURE HLine(x1,y1,x2:word; color:word); PROCEDURE InitPoly(VAR p:TPolygon; len,col:word); PROCEDURE DonePoly(VAR p:TPolygon); PROCEDURE FillMonotoneVerticalPolygon(XOffset,YOffset:word; VertexList:TPolygon); CONST MaxX=320; MaxY=200; VidSegment=$A000; {************************} IMPLEMENTATION {***********************} PROCEDURE HLine; ASSEMBLER; ASM mov ax,x1 { x1 < x2 } cmp ax,x2 jl @@skip1 je @@lijnexit xchg ax,x2 mov x1,ax @@skip1: mov ax,maxX { calculate y1*maxX+x1 } mul y1 add ax,x1 @@1: mov di,ax { dx=segment, di=offset } mov ax,VidSegment @@skip2: cld { forward direction } mov cx,x2 sub cx,x1 inc cx { cx = number of pixels in line } mov dx,di add dx,cx mov es,ax { load segment register } mov ax,color { get color into 386 register eax } mov ah,al mov dx,ax db $66,$c1,$e0,$10 { shl eax,16 (386 code) } mov ax,dx test di,00000011b jz @@skip { test for doubleword border, if so jump } @@waitdd: mov es:[di],al { put one pixel } inc di { di:=next pixel address } test di,00000011b { doubleword border ? } loopnz @@waitdd { stop if cx=0 or zeroflag 1 } or cx,cx { cx=0 ? } jz @@lijnexit { if so, line is ready } cmp cx,4 { is a stosd possible ? } jl @@waitdd { no, then pixel after pixel } @@skip: mov dx,cx shr cx,2 db $f3,$66,$AB { rep stosd (386 code) } mov cx,dx and cx,00000011b { line finished ? } jnz @@waitdd @@lijnexit: END; PROCEDURE ScanEdge(x1,y1,x2,y2,SetXStart,SkipFirst:integer; VAR EdgePointPtr:PHLineArray); ASSEMBLER; { Scan converts an edge from (X1,Y1) to (X2,Y2), not including the point at (X2,Y2). If SkipFirst == 1, the point at (X1,Y1) isn't drawn; if SkipFirst == 0, it is. For each scan line, the pixel closest to the scanned edge without being to the left of the scanned edge is chosen. Uses an all-integer approach for speed & precision. Edges must not go bottom to top; that is, Y1 must be <= Y2. Updates the pointer pointed to by EdgePointPtr to point to the next free entry in the array of HLine structures. } VAR AdvanceAmt,Height:word; ASM les di,EdgePointPtr les di,es:[di] { point to the HLine array } cmp SetXStart,1 { set the XStart field of each HLine { struc? } jz @@HLinePtrSet { yes, DI points to the first XStart } add di,2 { no, point to the XEnd field of the } { first HLine struc } @@HLinePtrSet: mov bx,Y2 sub bx,Y1 { edge height } jle @@ToScanEdgeExit{ guard against 0-length & horz edges } mov Height,bx { Height = Y2 - Y1 } sub cx,cx { assume ErrorTerm starts at 0 (true if } { we're moving right as we draw) } mov dx,1 { assume AdvanceAmt = 1 (move right) } mov ax,X2 sub ax,X1 { DeltaX = X2 - X1 } jz @@IsVertical { it's a vertical edge--special case it } jns @@SetAdvanceAmt { DeltaX >= 0 } mov cx,1 { DeltaX < 0 (move left as we draw) } sub cx,bx { ErrorTerm = -Height + 1 } neg dx { AdvanceAmt = -1 (move left) } neg ax { Width = abs(DeltaX) } @@SetAdvanceAmt: mov AdvanceAmt,dx { Figure out whether the edge is diagonal, X-major (more horizontal), } { or Y-major (more vertical) and handle appropriately. } cmp ax,bx { if Width==Height, it's a diagonal edge } jz @@IsDiagonal { it's a diagonal edge--special case } jb @@YMajor { it's a Y-major (more vertical) edge } { it's an X-major (more horz) edge } sub dx,dx { prepare DX:AX (Width) for division } div bx { Width/Height } { DX = error term advance per scan line } mov si,ax { SI = minimum # of pixels to advance X } { on each scan line } test AdvanceAmt,8000h { move left or right? } jz @@XMajorAdvanceAmtSet { right, already set } neg si { left, negate the distance to advance } { on each scan line } @@XMajorAdvanceAmtSet: mov ax,X1 { starting X coordinate } cmp SkipFirst,1 { skip the first point? } jz @@XMajorSkipEntry { yes } @@XMajorLoop: mov es:[di],ax { store the current X value } add di,4 { point to the next HLine struc } @@XMajorSkipEntry: add ax,si { set X for the next scan line } add cx,dx { advance error term } jle @@XMajorNoAdvance { not time for X coord to advance one } { extra } add ax,AdvanceAmt { advance X coord one extra } sub cx,Height { adjust error term back } @@XMajorNoAdvance: dec bx { count off this scan line } jnz @@XMajorLoop jmp @@ScanEdgeDone @@ToScanEdgeExit: jmp @@ScanEdgeExit @@IsVertical: mov ax,X1 { starting (and only) X coordinate } sub bx,SkipFirst { loop count = Height - SkipFirst } jz @@ScanEdgeExit { no scan lines left after skipping 1st } @@VerticalLoop: mov es:[di],ax { store the current X value } add di,4 { point to the next HLine struc } dec bx { count off this scan line } jnz @@VerticalLoop jmp @@ScanEdgeDone @@IsDiagonal: mov ax,X1 { starting X coordinate } cmp SkipFirst,1 { skip the first point? } jz @@DiagonalSkipEntry { yes } @@DiagonalLoop: mov es:[di],ax { store the current X value } add di,4 { point to the next HLine struc } @@DiagonalSkipEntry: add ax,dx { advance the X coordinate } dec bx { count off this scan line } jnz @@DiagonalLoop jmp @@ScanEdgeDone @@YMajor: push bp { preserve stack frame pointer } mov si,X1 { starting X coordinate } cmp SkipFirst,1 { skip the first point? } mov bp,bx { put Height in BP for error term calcs } jz @@YMajorSkipEntry { yes, skip the first point } @@YMajorLoop: mov es:[di],si { store the current X value } add di,4 { point to the next HLine struc } @@YMajorSkipEntry: add cx,ax { advance the error term } jle @@YMajorNoAdvance { not time for X coord to advance } add si,dx { advance the X coordinate } sub cx,bp { adjust error term back } @@YMajorNoAdvance: dec bx { count off this scan line } jnz @@YMajorLoop pop bp { restore stack frame pointer } @@ScanEdgeDone: cmp SetXStart,1 { were we working with XStart field? } jz @@UpdateHLinePtr { yes, DI points to the next XStart } sub di,2 { no, point back to the XStart field } @@UpdateHLinePtr: mov bx,word ptr EdgePointPtr { point to pointer to HLine array } mov ss:[bx],di { update caller's HLine array pointer } @@ScanEdgeExit: END; PROCEDURE DrawHorizontalLineList(VAR list:THLineList; color:word); ASSEMBLER; ASM les si,list mov cx,es:[si] { cx = number of lines } mov ax,es:[si+2] { ax = startY } les si,es:[si+4] { es:si points to pointlist } @@loop: mov bx,es:[si] { get startX } mov dx,es:[si+2] { get endX } push cx { save registers } push ax push si push es push bx { draw horizontal line } push ax push dx mov dx,color { get color } push dx call HLine pop es { restore registers } pop si pop ax pop cx inc ax { y:=y+1 } add si,4 { next points } loop @@loop { if length=0 then stop } END; PROCEDURE FillMonotoneVerticalPolygon; VAR i,MinIndex,MaxIndex,MinPoint_y,MaxPoint_y,NextIndex, CurrentIndex,PreviousIndex:integer; WorkingHLineList:THLineList; EdgePointPtr:PHLineArray; VertexPtr:PPointsList; BEGIN IF VertexList.Length=0 THEN Exit; VertexPtr:=VertexList.PointPtr; MaxPoint_y:=VertexPtr^[0].y; MinPoint_y:=MaxPoint_y; MinIndex:=0; MaxIndex:=0; FOR i:=1 TO VertexList.Length-1 DO WITH VerTexPtr^[i] DO IF yMaxPoint_y THEN BEGIN MaxPoint_y:=y; MaxIndex:=i; END; WITH WorkingHLineList DO BEGIN length:=MaxPoint_y-MinPoint_y; IF length<=0 THEN Exit; YStart:=YOffset+MinPoint_y; GetMem(HLinePtr,SizeOf(THLine)*length); EdgePointPtr:=HLinePtr; END; CurrentIndex:=MinIndex; PreviousIndex:=MinIndex; REPEAT CurrentIndex:=(CurrentIndex+VertexList.length-1) MOD VertexList.length; ScanEdge(VertexPtr^[PreviousIndex].x+XOffset, VertexPtr^[PreviousIndex].y, VertexPtr^[CurrentIndex].x+XOffset, VertexPtr^[CurrentIndex].y, 1,0,EdgePointPtr); PreviousIndex:=CurrentIndex; UNTIL CurrentIndex=MaxIndex; EdgePointPtr:=WorkingHLineList.HLinePtr; CurrentIndex:=MinIndex; PreviousIndex:=MinIndex; REPEAT CurrentIndex:=(CurrentIndex+1) MOD VertexList.length; ScanEdge(VertexPtr^[PreviousIndex].x+XOffset, VertexPtr^[PreviousIndex].y, VertexPtr^[CurrentIndex].x+XOffset, VertexPtr^[CurrentIndex].y, 0,0,EdgePointPtr); PreviousIndex:=CurrentIndex; UNTIL CurrentIndex=MaxIndex; DrawHorizontalLineList(WorkingHLineList,VertexList.color); WITH WorkingHLineList DO FreeMem(HLinePtr,SizeOf(THLine)*length); END; PROCEDURE InitPoly; BEGIN WITH p DO BEGIN length:=len; color:=col; { No Error checking !} GetMem(PointPtr,len*SizeOf(TPoint)); END; END; PROCEDURE DonePoly; BEGIN WITH p DO BEGIN IF PointPtr<>NIL THEN FreeMem(PointPtr,length*SizeOf(TPoint)); PointPtr:=NIL; END; END; END. {*****************************************************************} {* ProgramName : FASTPOL.PAS *} {* Purpose : Demonstration of unit FastPoly *} {* Version : 1.0 *} {* Author : Daan de Haas *} {* Date : 9 jun 1994 *} {* Last update : 9 jun 1994 *} {* Language : Borland Pascal 7.0 *} {* Fidonet : Daan de Haas (2:500/104.6141) *} {* Internet : Daan.de.Haas@p6141.f104.n500.z2.fidonet.org *} {*****************************************************************} {$R-,I-,Q-,S-} USES Crt, FastPoly; PROCEDURE SetVideo(m:word); ASSEMBLER; ASM mov ax,m int $10 END; PROCEDURE Polydemo; VAR p1,p2:TPolygon; BEGIN InitPoly(p1,6,YELLOW); p1.PointPtr^[0].X:=10; p1.PointPtr^[0].Y:=0; p1.PointPtr^[1].X:=20; p1.PointPtr^[1].Y:=0; p1.PointPtr^[2].X:=30; p1.PointPtr^[2].Y:=10; p1.PointPtr^[3].X:=20; p1.PointPtr^[3].Y:=20; p1.PointPtr^[4].X:=10; p1.PointPtr^[4].Y:=20; p1.PointPtr^[5].X:=0; p1.PointPtr^[5].Y:=10; InitPoly(p2,6,BLUE); p2.PointPtr^[0].X:=10; p2.PointPtr^[0].Y:=0; p2.PointPtr^[1].X:=20; p2.PointPtr^[1].Y:=0; p2.PointPtr^[2].X:=30; p2.PointPtr^[2].Y:=10; p2.PointPtr^[3].X:=20; p2.PointPtr^[3].Y:=20; p2.PointPtr^[4].X:=10; p2.PointPtr^[4].Y:=20; p2.PointPtr^[5].X:=0; p2.PointPtr^[5].Y:=10; REPEAT FillMonotoneVerticalPolygon(Random(MaxX-35),Random(MaxY-25),p1); FillMonotoneVerticalPolygon(Random(MaxX-35),Random(MaxY-25),p2); UNTIL KeyPressed; ReadKey; DonePoly(p1); DonePoly(p2); END; BEGIN ClrScr; Randomize; SetVideo($13); PolyDemo; SetVideo(3); END.