home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
drdobbs
/
1991
/
12
/
grphprog.asc
< prev
next >
Wrap
Text File
|
1991-11-11
|
17KB
|
446 lines
_GRAPHICS PROGRAMMING COLUMN_
by Michael Abrash
[LISTING ONE]
/* Returns 1 if polygon described by passed-in vertex list is monotone with
respect to a vertical line, 0 otherwise. Doesn't matter if polygon is simple
(non-self-intersecting) or not. Tested with Borland C++ 2 in small model. */
#include "polygon.h"
#define SIGNUM(a) ((a>0)?1:((a<0)?-1:0))
int PolygonIsMonotoneVertical(struct PointListHeader * VertexList)
{
int i, Length, DeltaYSign, PreviousDeltaYSign;
int NumYReversals = 0;
struct Point *VertexPtr = VertexList->PointPtr;
/* Three or fewer points can't make a non-vertical-monotone polygon */
if ((Length=VertexList->Length) < 4) return(1);
/* Scan to the first non-horizontal edge */
PreviousDeltaYSign = SIGNUM(VertexPtr[Length-1].Y - VertexPtr[0].Y);
i = 0;
while ((PreviousDeltaYSign == 0) && (i < (Length-1))) {
PreviousDeltaYSign = SIGNUM(VertexPtr[i].Y - VertexPtr[i+1].Y);
i++;
}
if (i == (Length-1)) return(1); /* polygon is a flat line */
/* Now count Y reversals. Might miss one reversal, at the last vertex, but
because reversal counts must be even, being off by one isn't a problem */
do {
if ((DeltaYSign = SIGNUM(VertexPtr[i].Y - VertexPtr[i+1].Y))
!= 0) {
if (DeltaYSign != PreviousDeltaYSign) {
/* Switched Y direction; not vertical-monotone if
reversed Y direction as many as three times */
if (++NumYReversals > 2) return(0);
PreviousDeltaYSign = DeltaYSign;
}
}
} while (i++ < (Length-1));
return(1); /* it's a vertical-monotone polygon */
}
[LISTING TWO]
/* Color-fills a convex polygon. All vertices are offset by (XOffset, YOffset).
"Convex" means "monotone with respect to a vertical line"; that is, every
horizontal line drawn through the polygon at any point would cross exactly two
active edges (neither horizontal lines nor zero-length edges count as active
edges; both are acceptable anywhere in the polygon). Right & left edges may
cross (polygons may be nonsimple). Polygons that are not convex according to
this definition won't be drawn properly. (Yes, "convex" is a lousy name for
this type of polygon, but it's convenient; use "monotone-vertical" if it makes
you happier!)
*******************************************************************
NOTE: the low-level drawing routine, DrawHorizontalLineList, must be able to
reverse the edges, if necessary to make the correct edge left edge. It must
also expect right edge to be specified in +1 format (the X coordinate is 1 past
highest coordinate to draw). In both respects, this differs from low-level
drawing routines presented in earlier columns; changes are necessary to make it
possible to draw nonsimple monotone-vertical polygons; that in turn makes it
possible to use Jim Kent's test for monotone-vertical polygons.
*******************************************************************
Returns 1 for success, 0 if memory allocation failed */
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include "polygon.h"
/* Advances the index by one vertex forward through the vertex list,
wrapping at the end of the list */
#define INDEX_FORWARD(Index) \
Index = (Index + 1) % VertexList->Length;
/* Advances the index by one vertex backward through the vertex list,
wrapping at the start of the list */
#define INDEX_BACKWARD(Index) \
Index = (Index - 1 + VertexList->Length) % VertexList->Length;
/* Advances the index by one vertex either forward or backward through
the vertex list, wrapping at either end of the list */
#define INDEX_MOVE(Index,Direction) \
if (Direction > 0) \
Index = (Index + 1) % VertexList->Length; \
else \
Index = (Index - 1 + VertexList->Length) % VertexList->Length;
extern void ScanEdge(int, int, int, int, int, int, struct HLine **);
extern void DrawHorizontalLineList(struct HLineList *, int);
int FillMonotoneVerticalPolygon(struct PointListHeader * VertexList,
int Color, int XOffset, int YOffset)
{
int i, MinIndex, MaxIndex, MinPoint_Y, MaxPoint_Y;
int NextIndex, CurrentIndex, PreviousIndex;
struct HLineList WorkingHLineList;
struct HLine *EdgePointPtr;
struct Point *VertexPtr;
/* Point to the vertex list */
VertexPtr = VertexList->PointPtr;
/* Scan the list to find the top and bottom of the polygon */
if (VertexList->Length == 0)
return(1); /* reject null polygons */
MaxPoint_Y = MinPoint_Y = VertexPtr[MinIndex = MaxIndex = 0].Y;
for (i = 1; i < VertexList->Length; i++) {
if (VertexPtr[i].Y < MinPoint_Y)
MinPoint_Y = VertexPtr[MinIndex = i].Y; /* new top */
else if (VertexPtr[i].Y > MaxPoint_Y)
MaxPoint_Y = VertexPtr[MaxIndex = i].Y; /* new bottom */
}
/* Set the # of scan lines in the polygon, skipping the bottom edge */
if ((WorkingHLineList.Length = MaxPoint_Y - MinPoint_Y) <= 0)
return(1); /* there's nothing to draw, so we're done */
WorkingHLineList.YStart = YOffset + MinPoint_Y;
/* Get memory in which to store the line list we generate */
if ((WorkingHLineList.HLinePtr =
(struct HLine *) (malloc(sizeof(struct HLine) *
WorkingHLineList.Length))) == NULL)
return(0); /* couldn't get memory for the line list */
/* Scan the first edge and store the boundary points in the list */
/* Initial pointer for storing scan converted first-edge coords */
EdgePointPtr = WorkingHLineList.HLinePtr;
/* Start from the top of the first edge */
PreviousIndex = CurrentIndex = MinIndex;
/* Scan convert each line in the first edge from top to bottom */
do {
INDEX_BACKWARD(CurrentIndex);
ScanEdge(VertexPtr[PreviousIndex].X + XOffset,
VertexPtr[PreviousIndex].Y,
VertexPtr[CurrentIndex].X + XOffset,
VertexPtr[CurrentIndex].Y, 1, 0, &EdgePointPtr);
PreviousIndex = CurrentIndex;
} while (CurrentIndex != MaxIndex);
/* Scan the second edge and store the boundary points in the list */
EdgePointPtr = WorkingHLineList.HLinePtr;
PreviousIndex = CurrentIndex = MinIndex;
/* Scan convert the second edge, top to bottom */
do {
INDEX_FORWARD(CurrentIndex);
ScanEdge(VertexPtr[PreviousIndex].X + XOffset,
VertexPtr[PreviousIndex].Y,
VertexPtr[CurrentIndex].X + XOffset,
VertexPtr[CurrentIndex].Y, 0, 0, &EdgePointPtr);
PreviousIndex = CurrentIndex;
} while (CurrentIndex != MaxIndex);
/* Draw the line list representing the scan converted polygon */
DrawHorizontalLineList(&WorkingHLineList, Color);
/* Release the line list's memory and we're successfully done */
free(WorkingHLineList.HLinePtr);
return(1);
}
[LISTING THREE]
; Draws all pixels in list of horizontal lines passed in, in mode 13h, VGA's
; 320x200 256-color mode. Uses REP STOS to fill each line.
; ******************************************************************
; NOTE: is able to reverse the X coords for a scan line, if necessary to make
; XStart < XEnd. Expects whichever edge is rightmost on any scan line to be in
; +1 format; that is, XEnd is 1 greater than rightmost pixel to draw. if
; XStart == XEnd, nothing is drawn on that scan line.
; ******************************************************************
; C near-callable as:
; void DrawHorizontalLineList(struct HLineList * HLineListPtr, int Color);
; All assembly code tested with TASM 2.0 and MASM 5.0
SCREEN_WIDTH equ 320
SCREEN_SEGMENT equ 0a000h
HLine struc
XStart dw ? ;X coordinate of leftmost pixel in line
XEnd dw ? ;X coordinate of rightmost pixel in line
HLine ends
HLineList struc
Lngth dw ? ;# of horizontal lines
YStart dw ? ;Y coordinate of topmost line
HLinePtr dw ? ;pointer to list of horz lines
HLineList ends
Parms struc
dw 2 dup(?) ;return address & pushed BP
HLineListPtr dw ? ;pointer to HLineList structure
Color dw ? ;color with which to fill
Parms ends
.model small
.code
public _DrawHorizontalLineList
align 2
_DrawHorizontalLineList proc
push bp ;preserve caller's stack frame
mov bp,sp ;point to our stack frame
push si ;preserve caller's register variables
push di
cld ;make string instructions inc pointers
mov ax,SCREEN_SEGMENT
mov es,ax ;point ES to display memory for REP STOS
mov si,[bp+HLineListPtr] ;point to the line list
mov ax,SCREEN_WIDTH ;point to the start of the first scan
mul [si+YStart] ; line in which to draw
mov dx,ax ;ES:DX points to first scan line to draw
mov bx,[si+HLinePtr] ;point to the XStart/XEnd descriptor
; for the first (top) horizontal line
mov si,[si+Lngth] ;# of scan lines to draw
and si,si ;are there any lines to draw?
jz FillDone ;no, so we're done
mov al,byte ptr [bp+Color] ;color with which to fill
mov ah,al ;duplicate color for STOSW
FillLoop:
mov di,[bx+XStart] ;left edge of fill on this line
mov cx,[bx+XEnd] ;right edge of fill
cmp di,cx ;is XStart > XEnd?
jle NoSwap ;no, we're all set
xchg di,cx ;yes, so swap edges
NoSwap:
sub cx,di ;width of fill on this line
jz LineFillDone ;skip if zero width
add di,dx ;offset of left edge of fill
test di,1 ;does fill start at an odd address?
jz MainFill ;no
stosb ;yes, draw the odd leading byte to
; word-align the rest of the fill
dec cx ;count off the odd leading byte
jz LineFillDone ;done if that was the only byte
MainFill:
shr cx,1 ;# of words in fill
rep stosw ;fill as many words as possible
adc cx,cx ;1 if there's an odd trailing byte to
; do, 0 otherwise
rep stosb ;fill any odd trailing byte
LineFillDone:
add bx,size HLine ;point to the next line descriptor
add dx,SCREEN_WIDTH ;point to the next scan line
dec si ;count off lines to fill
jnz FillLoop
FillDone:
pop di ;restore caller's register variables
pop si
pop bp ;restore caller's stack frame
ret
_DrawHorizontalLineList endp
end
[LISTING FOUR]
/*** Replace this... ***/
extern int FillConvexPolygon(struct PointListHeader *, int, int, int);
/*** ...with this... ***/
extern int FillMonotoneVerticalPolygon(struct PointListHeader *,
int, int, int);
extern int PolygonIsMonotoneVertical(struct PointListHeader *);
/*** Replace this... ***/
#ifdef CONVEX_CODE_LINKED
/* Pass convex polygons through to fast convex polygon filler */
if (PolygonShape == CONVEX)
return(FillConvexPolygon(VertexList, Color, XOffset, YOffset));
#endif
/*** ...with this... ***/
#ifdef CONVEX_CODE_LINKED
/* Pass convex polygons through to fast convex polygon filler */
if ((PolygonShape == CONVEX) ||
PolygonIsMonotoneVertical(VertexList))
return(FillMonotoneVerticalPolygon(VertexList, Color, XOffset,
YOffset));
#endif
[LISTING FIVE]
/* POLYGON.H: Header file for polygon-filling code */
#define CONVEX 0
#define NONCONVEX 1
#define COMPLEX 2
/* Describes a single point (used for a single vertex) */
struct Point {
int X; /* X coordinate */
int Y; /* Y coordinate */
};
/* Describes series of points (used to store a list of vertices that describe
a polygon; each vertex is assumed to connect to the two adjacent vertices, and
last vertex is assumed to connect to the first) */
struct PointListHeader {
int Length; /* # of points */
struct Point * PointPtr; /* pointer to list of points */
};
/* Describes beginning and ending X coordinates of a single horizontal line */
struct HLine {
int XStart; /* X coordinate of leftmost pixel in line */
int XEnd; /* X coordinate of rightmost pixel in line */
};
/* Describes a Length-long series of horizontal lines, all assumed to be on
contiguous scan lines starting at YStart and proceeding downward (used to
describe scan-converted polygon to low-level hardware-dependent drawing code)*/
struct HLineList {
int Length; /* # of horizontal lines */
int YStart; /* Y coordinate of topmost line */
struct HLine * HLinePtr; /* pointer to list of horz lines */
};
/* Describes a color as an RGB triple, plus one byte for other info */
struct RGB { unsigned char Red, Green, Blue, Spare; };
[LISTING SIX]
/* Mode set routine for VGA 640x400 16-color mode. Tested with
Borland C++ 2, in C compilation mode. */
#include <dos.h>
void Set640x400()
{
union REGS regset;
/* First, set to standard 640x350 mode (mode 10h) */
regset.x.ax = 0x0010;
int86(0x10, ®set, ®set);
/* Modify the sync polarity bits (bits 7 & 6) of the
Miscellaneous Output register (readable at 0x3CC, writable at
0x3C2) to select the 400-scan-line vertical scanning rate */
outp(0x3C2, ((inp(0x3CC) & 0x3F) | 0x40));
/* Now, tweak the registers needed to convert the vertical
timings from 350 to 400 scan lines */
outpw(0x3D4, 0x9C10); /* adjust the Vertical Sync Start register
for 400 scan lines */
outpw(0x3D4, 0x8E11); /* adjust the Vertical Sync End register
for 400 scan lines */
outpw(0x3D4, 0x8F12); /* adjust the Vertical Display End
register for 400 scan lines */
outpw(0x3D4, 0x9615); /* adjust the Vertical Blank Start
register for 400 scan lines */
outpw(0x3D4, 0xB916); /* adjust the Vertical Blank End register
for 400 scan lines */
}
[LISTING SEVEN]
/* Sample program to exercise VGA 640x400 16-color mode page flipping, by
drawing a horizontal line at the top of page 0 and another at bottom of page 1,
then flipping between them once every 30 frames. Tested with Borland C++ 2,
in C compilation mode. */
#include <dos.h>
#include <conio.h>
#define SCREEN_SEGMENT 0xA000
#define SCREEN_HEIGHT 400
#define SCREEN_WIDTH_IN_BYTES 80
#define INPUT_STATUS_1 0x3DA /* color-mode address of Input Status 1
register */
/* The page start addresses must be even multiples of 256, because page
flipping is performed by changing only the upper start address byte */
#define PAGE_0_START 0
#define PAGE_1_START (400*SCREEN_WIDTH_IN_BYTES)
void main(void);
void Wait30Frames(void);
extern void Set640x400(void);
void main()
{
int i;
unsigned int far *ScreenPtr;
union REGS regset;
Set640x400(); /* set to 640x400 16-color mode */
/* Point to first line of page 0 and draw a horizontal line across screen */
FP_SEG(ScreenPtr) = SCREEN_SEGMENT;
FP_OFF(ScreenPtr) = PAGE_0_START;
for (i=0; i<(SCREEN_WIDTH_IN_BYTES/2); i++) *ScreenPtr++ = 0xFFFF;
/* Point to last line of page 1 and draw a horizontal line across screen */
FP_OFF(ScreenPtr) =
PAGE_1_START + ((SCREEN_HEIGHT-1)*SCREEN_WIDTH_IN_BYTES);
for (i=0; i<(SCREEN_WIDTH_IN_BYTES/2); i++) *ScreenPtr++ = 0xFFFF;
/* Now flip pages once every 30 frames until a key is pressed */
do {
Wait30Frames();
/* Flip to page 1 */
outpw(0x3D4, 0x0C | ((PAGE_1_START >> 8) << 8));
Wait30Frames();
/* Flip to page 0 */
outpw(0x3D4, 0x0C | ((PAGE_0_START >> 8) << 8));
} while (kbhit() == 0);
getch(); /* clear the key press */
/* Return to text mode and exit */
regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */
int86(0x10, ®set, ®set);
}
void Wait30Frames()
{
int i;
for (i=0; i<30; i++) {
/* Wait until we're not in vertical sync, so we can catch leading edge */
while ((inp(INPUT_STATUS_1) & 0x08) != 0) ;
/* Wait until we are in vertical sync */
while ((inp(INPUT_STATUS_1) & 0x08) == 0) ;
}
}