500 likes | 814 Views
2D Graphics. CIS 487/587 Bruce R. Maxim UM-Dearborn. Vector Graphics. Advantages No jagged lines Can only draw what is on the screen Control electron gun directly (very fast) Store line end points only Disadvantages Best for wire frames
E N D
2D Graphics CIS 487/587 Bruce R. Maxim UM-Dearborn
Vector Graphics • Advantages • No jagged lines • Can only draw what is on the screen • Control electron gun directly (very fast) • Store line end points only • Disadvantages • Best for wire frames • Have to draw everything as lines including circles • Can’t use TV technology
Raster Graphics • Advantages • Cheaper • Can easily draw solid surfaces • Can move blocks and images around • Can control individual pixels • Disadvantages • Memory intensive • Aliasing problems
Jaggies • Here’s the problem with raster graphics • A diagonal line does not always pass through the center of the pixels
This won’t work • Compute the slope of the line • Plot (x0, y0) • Advance xi = xi + 1 • Advance yi = yi + 1 • Plot (xi, yi) • Repeat
Bresenhem’s Algorithm • Starts at x0, y0 • Plots x0, y0 • xi = xi + 1 • When advancing yi decide between plotting (xi, yi) or (xi,yi - 1) Note: Algorithm treats separately m < 1 (<45°) and m > 1 (>45°) Quadrants II, III, IV derived from I by symmetry
Draw_Line( ) int Draw_Line(int x0, int y0, // starting position int x1, int y1, // ending position UCHAR color, // color index UCHAR *vb_start, int lpitch) // video buffer & memory pitch { // function draws a line from xo,yo to x1,y1 using differential error // terms (based on Bresenahams work) int dx, // difference in x's dy, // difference in y's dx2, // dx,dy * 2 dy2, x_inc, // amount in pixel space to move during drawing y_inc, // amount in pixel space to move during drawing error, // the discriminant i.e. error i.e. decision variable index; // used for looping // pre-compute first pixel address in video buffer vb_start = vb_start + x0 + y0*lpitch;
Draw_Line( ) // compute horizontal and vertical deltas dx = x1-x0; dy = y1-y0; // test which direction the line is going in i.e. slope angle if (dx>=0) { x_inc = 1; } // end if line is moving right else { x_inc = -1; dx = -dx; // need absolute value } // end else moving left
DrawLine( ) // now based on which delta is greater we can draw the line if (dx > dy) { error = dy2 - dx; // initialize error term for (index=0; index <= dx; index++) // draw the line { *vb_start = color; // set the pixel // test if error has overflowed if (error >= 0) { error-=dx2; // move to next line vb_start+=y_inc; } // end if error overflowed error+=dy2; // adjust the error term vb_start+=x_inc; // move to next pixel } // end for } // end if |slope| <= 1
DrawLine( ) else { error = dx2 - dy; // initialize error term for (index=0; index <= dy; index++) // draw the line { *vb_start = color; // set the pixel if (error >= 0) // test if error overflowed { error-=dy2; vb_start+=x_inc; // move to next line } // end if error overflowed error+=dx2; // adjust the error term vb_start+=y_inc; // move to next pixel } // end for } // end else |slope| > 1 return(1); // return success } // end Draw_Line
DrawLine( ) // test y component of slope if (dy>=0) { y_inc = lpitch; } // end if line is moving down else { y_inc = -lpitch; dy = -dy; // need absolute value } // end else moving up // compute (dx,dy) * 2 dx2 = dx << 1; dy2 = dy << 1;
Clipping • Goal is to only display the part of the image that is really viewable on the portion of the screen used for drawing • Approaches: • Border • Image space • Object space
Border Clipping • Create a border that is a wide as any movable screen object • Only draw the object • Still need to detect when object will be off-screen • Requires more memory
Image Space Clipping • Image space (pixel level representation of complete image) • Test each point to see if it is in the region before trying to draw it • Easy to implement • Works for all objects • Works for sub regions • Requires lots of computation for each pixel
Object Space Clipping • Object space (representation of figures) • Change object to one that does not need to be clipped (e.g. chop triangle into a trapezoid) • New object passed to graphics engine • Without testing for clipping • More efficient than image space clipping • Lines are easy • Concave objects are tough
Cohen Sutherland • Each region is assigned a bit code • End points P1and P2 • If y < minY or y > maxY or x < minX or x > maxX then save Sign bits of P1 and P2 P2
Cohen Sutherland • Accept the point if P1 + P2 = 0 • Reject the point if P1 & P2 != 0 Note: It is best to break up the line so that line segment only occupies a single region
Draw_Clip_Line( ) int Draw_Clip_Line(int x0,int y0, int x1, int y1, UCHAR color, UCHAR *dest_buffer, int lpitch) { // this helper function draws a clipped line int cxs, cys,cxe, cye; // clip and draw each line cxs = x0; cys = y0; cxe = x1; cye = y1; // clip the line if (Clip_Line(cxs,cys,cxe,cye)) // use Bresenham like before Draw_Line(cxs, cys, cxe,cye,color,dest_buffer,lpitch); return(1); // return success } // end Draw_Clip_Line
Clip_Line( ) int Clip_Line(int &x1,int &y1,int &x2, int &y2) { // function clips the line using globally defined clippingregion // internal clipping codes #define CLIP_CODE_C 0x0000 #define CLIP_CODE_N 0x0008 #define CLIP_CODE_S 0x0004 #define CLIP_CODE_E 0x0002 #define CLIP_CODE_W 0x0001 #define CLIP_CODE_NE 0x000a #define CLIP_CODE_SE 0x0006 #define CLIP_CODE_NW 0x0009 #define CLIP_CODE_SW 0x0005 int xc1=x1, yc1=y1, xc2=x2, yc2=y2; int p1_code=0, p2_code=0;
Clip_Line( ) // determine codes for p1 and p2 if (y1 < min_clip_y) p1_code|=CLIP_CODE_N; elseif (y1 > max_clip_y) p1_code|=CLIP_CODE_S; if (x1 < min_clip_x) p1_code|=CLIP_CODE_W; elseif (x1 > max_clip_x) p1_code|=CLIP_CODE_E; if (y2 < min_clip_y) p2_code|=CLIP_CODE_N; elseif (y2 > max_clip_y) p2_code|=CLIP_CODE_S; if (x2 < min_clip_x) p2_code|=CLIP_CODE_W; else if (x2 > max_clip_x) p2_code|=CLIP_CODE_E;
Clip_Line( ) // try and trivially reject if ((p1_code & p2_code)) return(0); // test for totally visible, if so leave points untouched if (p1_code==0 && p2_code==0) return(1); // determine end clip point for p1 … // determine end clip point for p2 …
Clip_Line( ) // do bounds check if ((xc1 < min_clip_x) || (xc1 > max_clip_x) || (yc1 < min_clip_y) || (yc1 > max_clip_y) || (xc2 < min_clip_x) || (xc2 > max_clip_x) || (yc2 < min_clip_y) || (yc2 > max_clip_y) ) { return(0); } // end if // store vars back x1 = xc1; y1 = yc1; x2 = xc2; y2 = yc2; return(1); } // end Clip_Line
Polygons • Defined by vertices • Closed (all line connected) • Simply draw them one line at a time • Can be convex or concave • Attributes: Number of vertices, color, position, list of vertices (x, y) form
Draw_Polygon2D( ) if (poly->state) // test if the polygon is visible { // loop thru and draw a line from vertices 1 to n for (int index=0; index < poly->num_verts-1; index++) { // draw line from ith to ith+1 vertex Draw_Clip_Line(poly->vlist[index].x+poly->x0, poly->vlist[index].y+poly->y0, poly->vlist[index+1].x+poly->x0, poly->vlist[index+1].y+poly->y0, poly->color,vbuffer, lpitch); } // end for // draw line from last vertex to 0th Draw_Clip_Line(poly->vlist[0].x+poly->x0, poly->vlist[0].y+poly->y0, poly->vlist[index].x+poly->x0, poly->vlist[index].y+poly->y0, poly->color, vbuffer, lpitch); return(1); } // end if else return(0);
Moving Objects • If you move an object do you need to change every vertex coordinate? • Yes, if you use world coordinates (meaning the screen coordinate system) • No, if you local coordinates (meaning local to the object itself) • Example: triangle at (4,0) might have local vertex coordinates (0,1), (-1,-1), (1,-1)
Translation (Moving) • If you want to move a point (x0 , y0) to a new position (xt , yt) • The transformation would be xt = x0 + dx and yt = y0 + dy • For motion dx and dy are the components of the velocity vector dx = cos v and dy = - sin v
Game_Main( ) // draw all the asteroids for (int curr_index = 0; curr_index < NUM_ASTEROIDS; curr_index++) { // glow asteroids asteroids[curr_index].color = rand()%256; // do the graphics Draw_Polygon2D(&asteroids[curr_index], (UCHAR *)ddsd.lpSurface, ddsd.lPitch); // move the asteroid without matrix operations asteroids[curr_index].x0+=asteroids[curr_index].xv; asteroids[curr_index].y0+=asteroids[curr_index].yv; // test for out of bounds if (asteroids[curr_index].x0 > SCREEN_WIDTH+100) asteroids[curr_index].x0 = - 100; if (asteroids[curr_index].y0 > SCREEN_HEIGHT+100) asteroids[curr_index].y0 = - 100 if (asteroids[curr_index].x0 < -100) asteroids[curr_index].x0 = SCREEN_WIDTH+100; if (asteroids[curr_index].y0 < -100) asteroids[curr_index].y0 = SCREEN_HEIGHT+100; } // end for curr_asteroid
Scaling (Changing Size) • Multiply the coordinates of each vertex by the scale factor • Everything will expand from the center • The transformation would be xt = x0 * scale and yt = y0 * scale
Scale_Polygon2D( ) int Scale_Polygon2D(POLYGON2D_PTR poly, float sx, float sy) { // this function scalesthe local coordinates of the polygon // test for valid pointer if (!poly) return(0); // loop and scale each point for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++) { // scale and store result back poly->vlist[curr_vert].x *= sx; poly->vlist[curr_vert].y *= sy; } // end for curr_vert // return success return(1); } // end Scale_Polygon2D
Game_Main( ) // do the graphics Draw_Polygon2D(&asteroid, (UCHAR *)ddsd.lpSurface, ddsd.lPitch); // test for scale if (KEYDOWN('A')) // scale up Scale_Polygon2D(&asteroid, 1.1, 1.1); else if (KEYDOWN('S')) // scale down Scale_Polygon2D(&asteroid, 0.9, 0.9); // rotate the polygon by 5 degrees Rotate_Polygon2D(&asteroid, 5);
Rotation (Turning) • Spin and object around it centered around the z-axis • Rotate each point the same angle • Positive angles are clockwise • Negative angles are counterclockwise • C++ uses radians (not degrees) • The transformation would be xt = x0 * cos() – y0 * sin() yt = y0 * cos() + x0 * sin()
Rotate_Poloygon2D( ) int Rotate_Polygon2D(POLYGON2D_PTR poly, int theta) { // function rotates the local coordinates of the polygon – no matrices // test for valid pointer if (!poly) return(0); // loop and rotate each point, very crude, no lookup!!! for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++) { // perform rotation float xr = (float)poly->vlist[curr_vert].x*cos_look[theta] - (float)poly->vlist[curr_vert].y*sin_look[theta]; float yr = (float)poly->vlist[curr_vert].x*sin_look[theta] + (float)poly->vlist[curr_vert].y*cos_look[theta]; // store result back poly->vlist[curr_vert].x = xr; poly->vlist[curr_vert].y = yr; } // end for curr_vert // return success return(1); } // end Rotate_Polygon2D
Summary • Drawing polygons is simply a matter of drawing lines and connecting end points • Clipping is used to remove images outside viewing area • Center polygons at (x0 , y0) and use local coordinates rather than world coordinates for the vertices
World Coordinates • When using world coordinates you always need to perform the following steps • Translate to origin • Perform rotation • Translate back to new position • It’s always a good idea to put sin(x) and cos(x) values in a lookup table to avoid recomputing them
Matrix Operations • Translation | 1 0 0| |x y 1| * | 0 1 0| |dx dy 1| • Scaling |sx 0 0| |x y 1| * | 0 sy 0| | 0 0 1|
Matrix Operations • Rotation |cos(a) -sin(a) 0| |x y 1| * |sin(a) cos(a) 0| | 0 0 1| • (S * R) * T | sx*cos(a) –sx*sin(a) 0| |x y 1| * | sy*sin(a) sy*cos(a) 0| | dx dy 1|
Mat_Init_3x2( ) inline int Mat_Init_3X2(MATRIX3X2_PTR ma, float m00, float m01, float m10, float m11, float m20, float m21) { // fills a 3x2 matrix with the sent data in row major form ma->M[0][0] = m00; ma->M[0][1] = m01; ma->M[1][0] = m10; ma->M[1][1] = m11; ma->M[2][0] = m20; ma->M[2][1] = m21; // return success return(1); } // end Mat_Init_3X2
Mat_Mul1x2_3x2( ) int Mat_Mul1X2_3X2(MATRIX1X2_PTR ma, MATRIX3X2_PTR mb,MATRIX1X2_PTR mprod) { // function multiplies a 1x2 matrix against a 3x2 matrix - ma*mb and // stores result using a dummy element for the 3rd element of the 1x2 for (int col=0; col<2; col++) { // compute dot product from row of ma and column of mb float sum = 0; // used to hold result for (int index=0; index<2; index++) { sum+=(ma->M[index]*mb->M[index][col]); // add next product pair } // end for index sum += mb->M[index][col]; // add in last element * 1 mprod->M[col] = sum; // insert resulting col element } // end for col return(1); } // end Mat_Mul_1X2_3X2
Matrix Translation int Translate_Polygon2D_Mat(POLYGON2D_PTR poly, int dx, int dy) { // function translates center polygon by using a matrix multiply if (!poly) return(0); // test for valid pointer MATRIX3X2 mt; // used to hold translation transform matrix // initialize the matrix with translation values dx dy Mat_Init_3X2(&mt,1,0, 0,1, dx, dy); // create a 1x2 matrix to do the transform MATRIX1X2 p0 = {poly->x0, poly->y0}; MATRIX1X2 p1 = {0,0}; // this will hold result // now translate via a matrix multiply Mat_Mul1X2_3X2(&p0, &mt, &p1); // now copy the result back into polygon poly->x0 = p1.M[0]; poly->y0 = p1.M[1]; // return success return(1); } // end Translate_Polygon2D_Mat
Matrix Scaling int Scale_Polygon2D_Mat(POLYGON2D_PTR poly, float sx, float sy) { // this function scales the local coordinates of the polygon if (!poly)return(0); // test for valid pointer MATRIX3X2 ms; // used to hold scaling transform matrix Mat_Init_3X2(&ms, sx, 0, 0, sy, 0, 0); // loop and scale each point for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++) { // scale and store result back MATRIX1X2 p0 = {poly->vlist[curr_vert].x, poly->vlist[curr_vert].y}; MATRIX1X2 p1 = {0,0}; // this will hold result Mat_Mul1X2_3X2(&p0, &ms, &p1); // now scale via a matrix multiply poly->vlist[curr_vert].x = p1.M[0]; // copy result back to vertex poly->vlist[curr_vert].y = p1.M[1]; } // end for curr_vert return(1); } // end Scale_Polygon2D_Mat
Matrix Rotation int Rotate_Polygon2D_Mat(POLYGON2D_PTR poly, int theta) { // function rotates the local coordinates of the polygon if (!poly) return(0); // test for valid pointer // test for negative rotation angle if (theta < 0) theta += 360; MATRIX3X2 mr; // used to hold rotation transform matrix Mat_Init_3X2(&mr,cos_look[theta],sin_look[theta], -sin_look[theta],cos_look[theta], 0, 0);
Matrix Rotation // loop and rotate each point, very crude, no lookup!!! for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++) { // create a 1x2 matrix to do the transform MATRIX1X2 p0 = {poly->vlist[curr_vert].x, poly->vlist[curr_vert].y}; MATRIX1X2 p1 = {0,0}; // this will hold result // now rotate via a matrix multiply Mat_Mul1X2_3X2(&p0, &mr, &p1); // now copy the result back into vertex poly->vlist[curr_vert].x = p1.M[0]; poly->vlist[curr_vert].y = p1.M[1]; } // end for curr_vert return(1); } // end Rotate_Polygon2D_Mat
Filling Polygons • Many systems draw 2D and 3D objects as collections of triangles • If we can fill a triangle, we are in pretty good shape • The general idea will be to break up figures into triangles whose base is parallel to the x-axis and the draw lots of horizontal lines
Triangle Filling P0(x0 , y0) P2(x2 , y2) P1(x1 , y1)
Triangle Filling • Compute dx/dy for left and right sides (basically 1/slope) call them dxy_left and dxy_right • Starting at top vertex (x0 , y0) set xs=xl= x0 and y= y0 • Add dxy_left to xs and add dxy_right to xl • Draw lines (xs,y) to (xl,y) • Increment y and go to step 3
Drawing Filled Triangles void Draw_Top_TriFP(int x1,int y1,int x2, int y2, int x3,int y3, int color, UCHAR *dest_buffer, int mempitch) // function draws a triangle that has a flat top using fixed point math void Draw_Bottom_TriFP(int x1,int y1, int x2,int y2, int x3,int y3, int color, UCHAR *dest_buffer, int mempitch) // function draws triangle that has flat bottom using fixed point math void Draw_Top_Tri(int x1,int y1, int x2, int y2, int x3,int y3, int color, UCHAR *dest_buffer, int mempitch) // this function draws a triangle that has a flat top void Draw_Bottom_Tri(int x1,int y1, int x2, int y2, int x3,int y3, int color, UCHAR *dest_buffer, int mempitch) // this function draws a triangle that has a flat bottom
Drawing Filled Triangles void Draw_TriangleFP_2D(int x1,int y1,int x2, int y2,int x3,int y3, int color,UCHAR *dest_buffer, int mempitch) // function draws triangle on the destination buffer using fixed point // it decomposes all triangles into a pair of flat top, flat bottom void Draw_Triangle_2D(int x1,int y1,int x2, int y2,int x3,int y3, int color, UCHAR *dest_buffer, int mempitch) // this function draws a triangle on the destination buffer // it decomposes all triangles into a pair of flat top, flat bottom
Breaking Up Polygons • If number of vertices left to process is greater then 3 continue to step 2 • Take first 3 vertices and create a triangle • Split off triangle and recursively process remaining vetices
Breaking Up Quadrilaterals inline void Draw_QuadFP_2D(int x0,int y0, int x1,int y1, int x2,int y2, int x3, int y3, int color, UCHAR *dest_buffer, int mempitch) { // this function draws a 2D quadrilateral // simply call the triangle function 2x, let it do all the work Draw_TriangleFP_2D(x0,y0,x1,y1,x3,y3,color,dest_buffer,mempitch); Draw_TriangleFP_2D(x1,y1,x2,y2,x3,y3,color,dest_buffer,mempitch); } // end Draw_QuadFP_2D