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 18:50:52 +1000
John, the code below is similar to my oscilloscope view in principle -
here though I just use an NSImage to buffer the "history", as it's
simplest, but for performance you might try something more
sophisticated. Also, when drawing the history, this just uses a linear
opacity ramp - it might work better with a curve of some sort but I'll
leave that to you! Hope it helps,
--Graham
@interface GCRadarView : NSView
{
NSMutableArray* mHistoryBuffer;
NSTimer* mTimer;
NSTimeInterval mLastUpdateTime;
CGFloat mAngle;
}
- (void) drawHistoryBuffer;
- (void) updateHistoryBuffer;
- (void) drawContentIntoImage:(NSImage*) theImage;
- (void) animateWithTimer:(NSTimer*) timer;
- (IBAction) startSweep:(id) sender;
- (IBAction) stopSweep:(id) sender;
@end
#define MAX_HISTORY_BUFFER_SIZE 6
#define SWEEP_RATE 0.3 // radians per second
//--------------------------------------------------------------------------------
#import "GCRadarView.h"
@implementation GCRadarView
- (void) drawHistoryBuffer
{
// draw the stack of images in the history buffer, each one with a
different transparency
if([mHistoryBuffer count] > 0 )
{
CGFloat opacityIncrement = 1.0 / [mHistoryBuffer count];
NSUInteger i;
NSImage* image;
for( i = 0; i < [mHistoryBuffer count]; ++i )
{
image = [mHistoryBuffer objectAtIndex:i];
[image drawInRect:[self bounds] fromRect:NSZeroRect
operation:NSCompositeSourceOver fraction:(CGFloat)i * opacityIncrement];
}
}
}
- (void) updateHistoryBuffer
{
// this generates the images. The image list is limited to a maximum
- if exceeded images are discarded
// from the front of the list. New images are added to the end of the
list.
while([mHistoryBuffer count] > MAX_HISTORY_BUFFER_SIZE )
[mHistoryBuffer removeObjectAtIndex:0];
NSImage* newImage = [[NSImage alloc] initWithSize:[self bounds].size];
[newImage lockFocus];
[self drawContentIntoImage:newImage];
[newImage unlockFocus];
[mHistoryBuffer addObject:newImage];
[newImage release];
}
- (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 SWEEP_RATE rad/sec - so how many radians since we
last updated?
CGFloat angleIncrement = SWEEP_RATE * elapsedTime;
mAngle += angleIncrement;
mAngle = fmod( mAngle, 2 * pi ); // not really needed
// 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];
// draw the sweep line from centre to edge. The transform is rotated
so we simply draw at a fixed angle.
// add a shadow to simulate the glow of backscatter and help hide the
discrete nature of the sweep lines
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];
// beam line
[[NSColor greenColor] set];
[NSBezierPath setDefaultLineWidth:1.5];
[NSBezierPath strokeLineFromPoint:centre toPoint:NSMakePoint( NSMaxX
([self bounds]), 0 )];
[shadow release];
[NSGraphicsContext restoreGraphicsState];
}
- (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/30.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
- (id) initWithFrame:(NSRect) frame
{
self = [super initWithFrame:frame];
if (self)
{
mHistoryBuffer = [[NSMutableArray alloc] init];
}
return self;
}
- (void) drawRect:(NSRect) dirtyRect
{
[[NSColor blackColor] set];
NSRectFill( dirtyRect );
[self drawHistoryBuffer];
}
- (void) dealloc
{
[self stopSweep:nil];
[mHistoryBuffer release];
[super dealloc];
}
@end
On 27/09/2009, at 4:50 PM, Graham Cox wrote:
Hi John,
The difficulty with this kind of display is simulating the
persistence of the old cathode-ray tubes in a realistic way. Just
drawing a shadow is unlikely to work, though it might help get you
some way by creating the glow caused by scattering.
One possibility is to use OpenGL. It has a "history buffer" mode
(may not be called that - I forget exactly) that stores the previous
image at a diminished brightness and that can be stacked for a
series of frames, giving a fade or trail effect.
Alternatively you can model persistence yourself by buffering up
several frames (for example, using a NSBitmapImageRep) and then
drawing the stack for each frame followed by the latest content. I
have used this approach to simulate an oscilloscope display and it
works well in terms of realism, but performance can be an issue.
You'll probably need to store at least 3 or 4 "previous" frames to
get the effect you want - there's no really good way to do it in one
pass and get realism. Fact is those old tubes literally stored the
image in the phosphors which naturally faded in their own time after
the beam passed - to simulate that realistically requires that you
model the image storage.
_______________________________________________
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