![]() |
RuTh's
RuThLEss
HomEpAgE
|
|
* INDEX *
3D game to-do list *
3D Engine to-do list
( chapter
1,
2,
3,
4,
5,
6,
7 ) *
* projection formula * transformation matrices * Bresenham algorithm * Scanline Polygonfill algorithm * * Spieldesign / game design * Troubleshooting 3D * Irrlicht 3D engine * Blender for Beginners * Chapter 4 -- Overwriting the Drawing MethodThis chapter is a preparation for the optimization we'll do in chapter 5, hidden-surface-removal. The hidden-surface problem comes up as soon as there's more than one object in our game world: We then have to define what to draw if objects overlap. To do that, we will need access to every individual pixel to decide whether it has to be drawn or not. Cocoa's Your Very Own Image Representation With MYDrawNSBezierPath is Cocoa's drawing library that we used for the Rotating Cube demo earlier. You may have created a NSBezierPath object bp, added some polygon vertices to it using [bp moveToPoint:p] and [bp lineToPoint:p]. Then you completed the polygon by calling [bp closePath], and drew either its wireframe with [bp stroke] or drew it filled with [bp fill]. Then you reseted the NSBezierPath by calling [bp removeAllPoints]. We want MYDraw to support the same features ([mydraw moveToPoint:p] etc), but it's supposed to draw the pixels into a custom framebuffer over which we have complete control. So first we need a framebuffer in MYDraw. We create an NSBitmapImageRep containing 4 "planes", one of which contains our NSMutableData pixel buffer named
Implementation: InitializerThe init function creates a new MYDraw object. The object contains an NSMutableData framebuffer of bytes ( - (id) init:(NSRect)cliprect {
self = [super init];
if( self )
{
bps = 8; // 8 Bits Per Sample = 1 byte/sample (0-255)
spp = 3; // Samples Per Pixel (R, G, B, no alpha = 3)
vertexNum = 0;
rect = cliprect;
bitmap = [NSBitmapImageRep alloc];
vertexListX = [[NSMutableArray alloc] init]; // x-coordinates
vertexListY = [[NSMutableArray alloc] init]; // y-coordinates
ppr = (int)rect.size.width; // pixels per row
bpr = spp*(int)rect.size.width; // bytes per row (a.k.a. RowBytes)
size = spp*(int)rect.size.width*(int)rect.size.height; // size of bitmap in bytes
samplerange = pow(2,bps)-1; // RGB colors from 0-255
pixels = [[NSMutableData alloc] initWithLength:size]; // create new framebuffer
baseAddr = [pixels mutableBytes]; // where the framebuffer's content starts
[self reset]; // Fill framebuffer with white pixels
}
return self;
}
This method instantiates MYDraw's instance variable
- (NSBitmapImageRep*) imgRep {
unsigned char* planes[4]= { [pixels mutableBytes], NULL, NULL, NULL };
[bitmap release];
bitmap = [NSBitmapImageRep alloc];
[bitmap initWithBitmapDataPlanes:planes
pixelsWide:rect.size.width pixelsHigh:rect.size.height
bitsPerSample:bps samplesPerPixel:spp
hasAlpha:NO isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bytesPerRow:bpr bitsPerPixel:(bps*spp) ];
return bitmap;
}
Implemetation: Drawing RoutinesThe two arrays vertexListX and vertexListY contain respectively the x and y coordinates of the polygon to be drawn. The arrays are filled with coordinates with every call of a drawing routine.
- (void) moveToPoint:(NSPoint)p
{
[self removeAllPoints];
[vertexListX addObject:[NSNumber numberWithFloat:p.x]];
[vertexListY addObject:[NSNumber numberWithFloat:p.y]];
vertexNum+=1;
}
- (void) lineToPoint:(NSPoint)p
{
[vertexListX addObject:[NSNumber numberWithFloat:p.x]];
[vertexListY addObject:[NSNumber numberWithFloat:p.y]];
vertexNum+=1;
}
- (void) closePath
{
[vertexListX addObject:[vertexListX objectAtIndex:0]];
[vertexListY addObject:[vertexListY objectAtIndex:0]];
vertexNum+=1;
}
- (void) removeAllPoints
{
[vertexListX removeAllObjects];
[vertexListY removeAllObjects];
vertexNum=0;
}
This is finally the method that puts an actual pixel into the framebuffer. It multiplies the pixeloffset loc by the number of samples per pixel, because each RGB-pixel needs three bytes (there are three samples per pixel, Red, Green and Blue). It sets these three bytes to the corresponding RGB values to light a pixel.
- (void) setPixel:(int)loc to:(NSColor*)f
{
// TODO: Clipping
baseAddr = [pixels mutableBytes];
loc*=spp;
baseAddr[loc] =(int)(samplerange*[f redComponent]);
baseAddr[loc+1]=(int)(samplerange*[f greenComponent]);
baseAddr[loc+2]=(int)(samplerange*[f blueComponent]);
}
Integrating MYDraw Into MYGameViewLook back at our trivial Rotating Cube demo: MYGameView's drawRect:-method called MYGameWorld's draw:-method. MYGameWorld then looped over its MYEntitys and MYPolygons, calling their draw:-methods. Each MYPolygon then loops over the screen coordinates of its vertices and finally draws stroked or filled [gameworld draw:clipRect]; /* MYGameView*/ | V [entity draw:clipRect]; /* MYGameWorld */ | V [polygon draw:clipRect]; /* MYEntity */ | V NSBezierPath* polygon = [NSBezierPath bezierPath]; /* MYPolygon */ ... [polygon fill]; /* MYPolygon (filled) */ or [polygon stroke]; /* MYPolygon (wireframe) */ To accomodate our own framebuffer MYDraw instead of the default graphic context, we have to duplicate all drawing functions in these objects with an additional (MYDraw*)mybuffer as argument. [gameworld draw:clipRect toBuffer:mybuffer]; /* MYGameView */ | V [entity draw:clipRect toBuffer:mybuffer]; /* MYGameWorld */ | V [polygon draw:clipRect toBuffer:mybuffer]; /* MYEntity */ | V [mybuffer drawFilledPolygon]; /* MYPolygon (filled) */ or [mybuffer drawWireframePolygon]; /* MYPolygon (wireframe) */ Initialize MYDraw's framebuffer in MYGameView's initWithFrame:. - (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
mybuffer = [[MYDraw alloc] init:frame];
image = [[NSImage alloc] init];
}
return self;
}
Edit MYGameView's drawRect to call - (void)drawRect:(NSRect)clipRect
{
if( ! gameworld ) return;
...
/* fill framebuffer with white pixels */
[mybuffer reset];
/* project game world into framebuffer */
[gameworld draw:clipRect toBuffer:mybuffer];
/* draw framebuffer to the screen */
[image drawRepresentation:[mybuffer imgRep] inRect: clipRect];
}
Implementation of drawFilledPolygon and drawWireframePolygon[mybuffer drawWireframePolygon] is implemented by Bresenham's Line Algorithm. This algorithm is one of the essentials in 3D game development and deserves a whole page of its own.
- (void) drawWireframePolygon
{
...
[Bresenham drawLineX1:x1 y1:y1 x2:x2 y2:y2
buffer:self ppr:ppr color:color];
}
+ (void) drawLineX1:(short)x1 y1:(short)y1 x2:(short)x2 y2:(short)y2
buffer:(MYDraw*) mybuffer ppr:(int)ppr color:(NSColor*)color
{
... Bresenham line algorithm for Cocoa (Objective C) ...
}
For [mybuffer drawFilledPolygon], we use the Scanline Polygonfill Algorithm. This is another very essential algorithm that needs a separate page to be fully explained. - (void) drawFilledPolygon {
... Scanline Polygonfill algorithm for Cocoa (Objective C) ...
}
Turning the Buffer Right-Side-UpWhile we were using our game world data with the built-in drawing methods, everything was fine, but now when we want to use our own framebuffer, Cocoa draws it upside-down — this is because our coordinates' origin is located 'top left' in the buffer, whereas Cocoa internally fill the buffer from bottom to top with the origin being the buffer's bottom left corner. That's why every instance of NSView can implement a Boolean - (BOOL)isFlipped { return YES; }
I recommend to create a BOOL variable An Efficient Reset MethodThe C function memset() can set a whole bunch of pixels to a value very quickly. It is very usefull for rapidly clearing the framebuffer and filling it all with white pixels, RGB(255,255,255). The variable - (void) reset
{
memset(baseAddr,samplerange,size);
}
Other MethodsOf course you need Accessor methods for Useful Links |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
http://www.ruthless.zathras.de/ |