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 (Cocoa-dev@lists.apple.com)

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:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to