Re: Creating a radar sweep effect
Re: Creating a radar sweep effect
- Subject: Re: Creating a radar sweep effect
- From: Graham Cox <email@hidden>
- Date: Sun, 27 Sep 2009 21:42:58 +1000
Hi again - you got me going now! ;-)
Occurs to me that there's no need to store more than one buffered
image (actually really obvious once it dawned on me). Checking the
OpenGL approach, this is what it does also - just one "history"
buffered image, which is then drawn into the new image at each update,
with a lower opacity such that over time it naturally fades away. This
is much more efficient as there are only two bitblits per frame, and a
maximum of two images in memory at once and then only briefly, no
matter how much "persistence" you dial in or what the frame rate is,
etc. It also looks a lot more realistic, so it's a winner all round.
Here's my revised view which still uses NSImage, but even with this is
working nice and smoothly. I also added a "target" to show how actual
blips on the screen could be handled.
@interface GCRadarView : NSView
{
NSTimer* mTimer;
NSTimeInterval mLastUpdateTime;
CGFloat mAngle;
NSImage* mHistoryImage;
}
- (void) drawHistoryBuffer;
- (void) updateHistoryBuffer;
- (void) drawContentIntoImage:(NSImage*) theImage;
- (void) animateWithTimer:(NSTimer*) timer;
- (IBAction) startSweep:(id) sender;
- (IBAction) stopSweep:(id) sender;
@end
#define SWEEP_RATE 0.5 // radians per second
//------------------------------------------------------------------------------------
#import "GCRadarView.h"
@implementation GCRadarView
- (void) drawHistoryBuffer
{
// draw the current buffered image to the view
[mHistoryImage drawInRect:[self bounds] fromRect:NSZeroRect
operation:NSCompositeSourceOver fraction:1.0];
}
- (void) updateHistoryBuffer
{
NSImage* newImage = [[NSImage alloc] initWithSize:[self bounds].size];
// copy the current history into it at reduced opacity - dial in
different opacity values to change the "persistence"
// values closer to 1.0 give more persistence, closer to 0 give less.
1.0 gives infinite persistence, so the image never fades.
[newImage lockFocus];
if( mHistoryImage )
[mHistoryImage drawInRect:[self bounds] fromRect:NSZeroRect
operation:NSCompositeSourceOver fraction:0.92];
// draw new content
[self drawContentIntoImage:newImage];
[newImage unlockFocus];
// this is now the history buffer:
[mHistoryImage release];
mHistoryImage = newImage;
}
- (void) drawContentIntoImage:(NSImage*) theImage
{
// draw view content to the image. Image is already focused.
NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval elapsedTime = currentTime - mLastUpdateTime;
mLastUpdateTime = currentTime;
// sweep rate is 0.5 rad/sec - so how many radians since we last
updated?
CGFloat angleIncrement = SWEEP_RATE * elapsedTime;
mAngle += angleIncrement;
mAngle = fmod( mAngle, 2 * pi );
// set the transform to this angle rotated around the centre point
NSPoint centre;
centre.x = NSMidX([self bounds]);
centre.y = NSMidY([self bounds]);
[NSGraphicsContext saveGraphicsState];
NSAffineTransform* transform = [NSAffineTransform transform];
[transform translateXBy:centre.x yBy:centre.y];
[transform rotateByRadians:mAngle];
[transform translateXBy:-centre.x yBy:-centre.y];
[transform concat];
// set a shadow to simulate the backscatter glow
NSShadow* shadow = [[NSShadow alloc] init];
[shadow setShadowColor:[NSColor colorWithCalibratedRed:0.4 green:1.0
blue:0.4 alpha:1.0]];
[shadow setShadowOffset:NSZeroSize];
[shadow setShadowBlurRadius:8.0];
[shadow set];
// draw the sweep line from centre to edge. The transform is rotated
so draw at a fixed angle.
[[NSColor greenColor] set];
[NSBezierPath setDefaultLineWidth:1.5];
[NSBezierPath strokeLineFromPoint:centre toPoint:NSMakePoint( NSMaxX
([self bounds]), NSMidY([self bounds]))];
// draw the "target" on the screen when the beam sweeps across it to
simulate a blip - here just a square
// restore first so that effect of rotation is removed - target stays
at a fixed place.
[NSGraphicsContext restoreGraphicsState];
if( fabs(mAngle) < 0.1 )
{
NSBezierPath* target = [NSBezierPath bezierPathWithRect:NSMakeRect
(( centre.x + NSMaxX([self bounds])) / 2.0, NSMidY([self bounds]), 10,
10 )];
[[NSColor greenColor] set];
[target fill];
}
[shadow release];
}
- (void) animateWithTimer:(NSTimer*) timer
{
// timer callback ends up here. It updates the history buffer and
marks the display needed.
#pragma unused(timer)
[self updateHistoryBuffer];
[self setNeedsDisplay:YES];
}
- (IBAction) startSweep:(id) sender
{
#pragma unused(sender)
if( mTimer == nil )
{
mTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
target:self selector:@selector(animateWithTimer:) userInfo:nil
repeats:YES];
mLastUpdateTime = [NSDate timeIntervalSinceReferenceDate];
}
}
- (IBAction) stopSweep:(id) sender
{
#pragma unused(sender)
if( mTimer )
{
[mTimer invalidate];
mTimer = nil;
}
}
#pragma mark -
#pragma mark - as a NSView
- (void) drawRect:(NSRect) dirtyRect
{
[[NSColor blackColor] set];
NSRectFill( dirtyRect );
[self drawHistoryBuffer];
}
- (void) dealloc
{
[self stopSweep:nil];
[mHistoryImage release];
[super dealloc];
}
@end
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden