
// This object tracks the dirty rectangle of the frame buffer.
// If two rects overlap, they are coalesced.  If they don't
// overlap, they are kept separate.  This way, when a redraw
// is done, we have two flushes that are small rather than one
// large flush.  (The large flush would draw *lots* of unnecessary
// pixels!)

#import <gamekit/gamekit.h>

#import <appkit/appkit.h>
#import <dpsclient/dpsNeXT.h>
#import <stdio.h>

// function to coalesce two rectangles if they overlap.
// Returns NO if they don't, returns YES if they do and
// leaves the union rect in *r1.  I suppose that I could
// have used NXIntersectsRect() and NXUnionRect(), but
// this is just as easy.
BOOL coalesce(NXRect *r1, NXRect *r2)
{
	NXRect r3;
	
	// see if the two rects intersect.  If they don't, return NO.
	if (((NX_X(r1) + NX_WIDTH(r1)) < NX_X(r2)) ||
		((NX_X(r2) + NX_WIDTH(r2)) < NX_X(r1)) ||
		((NX_Y(r1) + NX_HEIGHT(r1)) < NX_Y(r2)) ||
		((NX_Y(r2) + NX_HEIGHT(r2)) < NX_Y(r1))) return NO;
	
	// take the union of the rects; the new, larger rect is in r3
	NX_X(&r3) = MIN(NX_X(r1), NX_X(r2));
	NX_Y(&r3) = MIN(NX_Y(r1), NX_Y(r2));
	NX_WIDTH(&r3) = MAX(NX_X(r1) + NX_WIDTH(r1),
		NX_X(r2) + NX_WIDTH(r2)) - NX_X(&r3);
	NX_HEIGHT(&r3) = MAX(NX_Y(r1) + NX_HEIGHT(r1),
		NX_Y(r2) + NX_HEIGHT(r2)) - NX_Y(&r3);
			
	*r1 = r3;
	return YES;	// tell them we coalesced it.
}


@implementation DirtPile

- init		// initialize the instance
{
	[super init];
	
	// reset instance variables.
	maxRects = MAX_RECTS;
	numRects = 0;
	return self;
}


- addRegion:(float)x :(float)y :(float)w :(float)h	// add a dirty rect
{
	register NXRect *r;
	
	// make sure we have enough room.  Complain if we don't.  (Shouldn't
	// ever get the printf(), though! )
	if (numRects >= maxRects) {
		fprintf(stderr, "Need more rects allocated in DirtPile!\n");
		return self;
	}
	
	// copy coords into the rect list
	r = &rectList[numRects];
	NX_X(r) = x; NX_Y(r) = y;
	NX_WIDTH(r) = w; NX_HEIGHT(r) = h;
	
	// update number of rects in list
	numRects++;
	
	return self;
}


- addRegion:(NXRect *)rect							// add a dirty rect
{
	return [self addRegion:NX_X(rect) :NX_Y(rect)
		:NX_WIDTH(rect) :NX_HEIGHT(rect)];
}


- fullRedraw:sender :buffer	// assumed to be a view
{
	NXRect bounds = {{0.0, 0.0}, {0.0, 0.0}};
	
	[sender getBounds:&bounds];
	[buffer composite:NX_COPY fromRect:&bounds
			toPoint:&(bounds.origin)];
	numRects = 0;
	return self;
}


// Flush dirty parts of the buffer to the currently focused view.
// The view should be in a retained window for this to actually
// work as it is supposed to do.
- doRedraw:buffer
{
	register NXRect *r1, *r2;
	register int i, j;
	BOOL changed;
	
	// Leave if nothing to flush.  This isn't really necessary, since if
	// this object is properly used, there will be some dirt when this
	// method gets called.
	if (!numRects) return self;
	
	r1 = &rectList[0]; // not necessary, but silences an irrelevant
		// compiler warning.  You could remove it, though, if it bothers you.
	
	// first, coaselse all redraw regions.
	do {
		changed = NO;
		for (i=0; i<(numRects-1); i++) {
			r1 = &rectList[i];
			for (j=i+1; j<numRects; j++) {
				r2 = &rectList[j];
				if (coalesce(r1, r2)) {
					changed = YES;
					numRects--;
					for (; j<numRects; j++) {
						*r2 = *(r2+1);
						r2++;
					}
				}
			}
		}
	} while (changed);
	
	// flush dirty rects from buffer onto the screen
	for (i=0; i<numRects; i++) {
		[buffer composite:NX_COPY fromRect:&rectList[i]
			toPoint:&rectList[i].origin];
		// if using a buffered window instead of retained, you
		// need to flush the window here.
	}
	
	// reset dirty list, since it's all clean now
	numRects = 0;
	return self;
}


@end
