/********************************************************************** Created by Rocco Bowling and Jonathan Saggau Big Nerd Ranch, Inc OpenGL Bootcamp Copyright 2006 Rocco Bowling and Jonathan Saggau, All rights reserved. /***************************** License ******************************** This code can be freely used as long as these conditions are met: 1. This header, in its entirety, is kept with the code 3. It is not resold, in it's current form or in modified, as a teaching utility or as part of a teaching utility This code is presented as is. The author of the code takes no responsibilities for any version of this code. (c) 2006 Rocco Bowling and Jonathan Saggau *********************************************************************/ #import "JSRunGLVizController.h" #import "StringTexture.h" #import "NSArrayDataSmoothing.h" #import "NSArrayAdditions.h" #import #include #define INITIAL_ZOOM_FACTOR 1.0 #define DEFAULT_UNIT_OF_MEASURE mile #define LogMethod() NSLog(@"-[%@ %s]", self, _cmd) extern void leftMouseUpGL(int x, int y); extern void leftMouseDownGL(int x, int y); extern void rightMouseUpGL(int x, int y); extern void rightMouseDownGL(int x, int y); extern BOOL animatingGraph; extern BOOL animatingEvents; float mouse_location[2]; float reflectionAlpha; float overallDistance; float *previousXPositions; NSArray *normalizedArray; NSDictionary *runEvents; StringTexture *HUD; unitOfMeasure unit = DEFAULT_UNIT_OF_MEASURE; float zoom = INITIAL_ZOOM_FACTOR; float splitsColArr[4]; float pausesColArr[4]; float reportsToUserColArr[4]; float powerSongsColArr[4]; float dataPointsColArr[4]; float getFrameRate() { static UnsignedWide startMicroSec, endMicroSec; static int _awoken = 0; float frame; if(!_awoken){ Microseconds(&startMicroSec); _awoken = 1; return 0.0; } Microseconds(&endMicroSec); WideSubtract((wide *) &endMicroSec, (wide *) &startMicroSec); Microseconds(&startMicroSec); // start timing the next frame frame = 1000000.0/(float)endMicroSec.lo; Microseconds(&startMicroSec); return frame; } float kilometerToMile(float km) { return ((1.0f/1.61f) * km); } NSString * makeHUDString(NSNumber *duration, NSNumber *distance, NSNumber *calories) { NSNumberFormatter *numberFormat = [[NSNumberFormatter alloc] init]; // expanded functionality in tiger == NSNumberFormatterBehavior10_4 [numberFormat setFormatterBehavior: NSNumberFormatterBehavior10_4]; [numberFormat setPositiveFormat:@"#,##0.00"]; float dur = [duration floatValue]; dur /= 60.0; NSNumber *durNum = [NSNumber numberWithFloat:dur]; NSString *durationString = [NSString stringWithFormat:@"%@ min. ", [numberFormat stringFromNumber:durNum]]; NSString *distanceString; if(kilometer == unit) distanceString = [NSString stringWithFormat:@"%@ km ", [numberFormat stringFromNumber:distance]]; if (mile == unit) { float mi = kilometerToMile([distance floatValue]); NSNumber *newDistance = [NSNumber numberWithFloat:mi]; distanceString = [NSString stringWithFormat:@"%@ mi ", [numberFormat stringFromNumber:newDistance]]; } [numberFormat release]; return [NSString stringWithFormat:@" Duration: %@ \n Distance: %@ \n Calories: %@ ", durationString, distanceString, calories]; } @interface JSRunGLVizController (privateAPI) - (void)openXMLFile; - (void)openPanelDidEnd:(NSOpenPanel *)sheet contextInfo:(void *)contextInfo; - (void) initGL:(GLView *)view; - (void) renderGL:(GLView *)view; - (void) postRenderGL:(GLView *)view; - (void) updateGL:(GLView *)view; - (void) reshapeGL:(GLView *)view; - (void) destructGL:(GLView *)view; - (void) eventGL:(GLView *)view; - (void) updateDataToPlot; - (void) updateEvents; - (void) updateHUD; - (void)setHUDString:(NSString *)anHUDString; // Convenience methods that return empty arrays when we don't want to show // certain of the below, otherwise they get the splits from runData. - (NSArray *) kmSplits; - (NSArray *) miSplits; - (NSArray *) pauses; - (NSArray *) reportsToUser; - (NSArray *) powerSongs; @end @implementation JSRunGLVizController - (id) init { self = [super init]; if (self != nil) { NSLog(@"JSRunGLVizController init"); movingAverageWidth = [[NSNumber alloc] initWithInt:12]; runData = [[JSRunModel alloc] init]; dataToPlot = [[NSArray alloc] init]; normalizedArray = [[NSArray alloc] init]; runEvents = [[NSDictionary alloc] init]; killDeviation = [[NSNumber alloc] init]; HUDfont=[[NSFont fontWithName:@"Helvetica" size:12.0] retain]; HUDString = [[NSString stringWithFormat:@" Duration: \n Distance: \n Calories: "] retain]; showHUD = YES; runData = nil; smoothData = YES; showSplits = NO; showPauses = NO; showPowerSongs = NO; showReportsToUser = NO; reflectionAlpha = 1.0; overallDistance = 0.0; _awoken = NO; _mousingAround = NO; animateOnOpen = YES; animateEvents = NO; [self setPausesColor:[NSColor colorWithCalibratedRed:0.9 green:0.4 blue:0.4 alpha:1.0]]; [self setPowerSongsColor:[NSColor colorWithCalibratedRed:0.98 green:0.12 blue:0.20 alpha:1.0]]; [self setReportsToUserColor:[NSColor colorWithCalibratedRed:0.45 green:0.75 blue:0.3 alpha:1.0]]; [self setSplitsColor:[NSColor colorWithCalibratedRed:0.45 green:0.75 blue:0.3 alpha:1.0]]; [self setDataPointsColor:[NSColor colorWithCalibratedRed:0.4 green:0.4 blue:1.0 alpha:1.0]]; [self setHUDColor:[NSColor colorWithCalibratedRed:0.1 green:0.8 blue:0.1 alpha:0.4]]; } return self; } - (void)awakeFromNib { NSLog(@"JSRunGLVizController awakeFromNib"); _awoken = YES; [self updateHUD]; [opengl_view stepUpdating]; [opengl_view setNeedsDisplay:YES]; } - (void)dealloc { _awoken = NO; //keeps the view from updating [HUDfont release]; [HUDString release]; [runData release]; [dataToPlot release]; [movingAverageWidth release]; [killDeviation release]; [HUD release]; [normalizedArray release]; [runEvents release]; [self setHUDfont:nil]; [self setHUDString:nil]; [self setRunData:nil]; [self setMovingAverageWidth:nil]; [self setKillDeviation:nil]; [self setSplitsColor:nil]; [self setPausesColor:nil]; [self setReportsToUserColor:nil]; [self setPowerSongsColor:nil]; [self setDataPointsColor:nil]; if (previousXPositions) free(previousXPositions); [super dealloc]; } - (void)animateGraphDraw; { [opengl_view startUpdating]; //start constant updating animateGraph(); } #pragma mark - #pragma mark OpenGL - (void) initGL:(GLView *)view { initGL(); //[view startUpdating]; } - (void) renderGL:(GLView *)view { renderGL(); _fps = getFrameRate(); } - (void) postRenderGL:(GLView *)view { postRenderGL(); } - (void) updateGL:(GLView *)view { NSPoint event_location = [[view window] mouseLocationOutsideOfEventStream]; NSPoint local_point = [view convertPoint:event_location fromView:nil]; mouse_location[0] = local_point.x; mouse_location[1] = local_point.y; if(updateGL()) { [view setNeedsDisplay:YES]; } // We cannot set an NSTextField's value inside of another view's drawRect: // function (it has horrible rendering issues). So, we store off the // fps in the render function, and then set it in the update function. if (!(animatingGraph || animatingEvents || _mousingAround)) { [opengl_view stopUpdating]; //If nothing is moving, we don't need to update anymore } } - (void) reshapeGL:(GLView *)view { if (_awoken) { NSRect frame = [view frame]; reshapeGL((int)frame.size.width, (int)frame.size.height); [view stepUpdating]; } } - (void) destructGL:(GLView *)view { destructGL(); } - (void) eventGL:(GLView *)view event:(NSEvent *)event { NSPoint event_location = [event locationInWindow]; NSPoint local_point = [view convertPoint:event_location fromView:nil]; if([event type] == NSScrollWheel) { [self setZoom:zoom + 0.004 * [event deltaY]]; //we use the scrollWheel to zoom } if([event type] == NSLeftMouseDown) { [opengl_view startUpdating]; //constant update of view during drag _mousingAround = YES; leftMouseDownGL(local_point.x, local_point.y); } if([event type] == NSLeftMouseUp) { leftMouseUpGL(local_point.x, local_point.y); _mousingAround = NO; } if([event type] == NSRightMouseDown) { [opengl_view startUpdating]; //constant update of view during drag _mousingAround = YES; rightMouseDownGL(local_point.x, local_point.y); } if([event type] == NSRightMouseUp) { rightMouseUpGL(local_point.x, local_point.y); _mousingAround = NO; } } #pragma mark - #pragma mark accessor helpers - (void) updateDataToPlot { NSArray *newData; if (! smoothData) newData = [[runData runDistanceHistory] distanceTraveledPerSnapshot]; else { NSArray *rawData = [[runData runDistanceHistory] distanceTraveledPerSnapshot]; NSArray *smoothedData = [rawData movingAverageWithWidth:[movingAverageWidth intValue]]; newData = smoothedData; } if (newData != dataToPlot) { [newData retain]; [dataToPlot release]; dataToPlot = newData; overallDistance = [[[self runData] distance] floatValue]; [normalizedArray release]; NSArray *newNormArray = [[dataToPlot normalizedArrayToMin:0.001 toMax:getAspectRatio() + 0.001] retain]; normalizedArray = newNormArray; //malloc some memory to track previous x positions //it's a bit of a kludge to put it here, but it's the only place //that makes sense as this is the only place where we *know* the data has changed. if (previousXPositions) { free(previousXPositions); previousXPositions = NULL; } int arrayLen = [normalizedArray count]; if (arrayLen > 0) { previousXPositions = (float *)calloc(arrayLen * sizeof(float), arrayLen); if (previousXPositions == NULL) { fprintf(stderr, "ERROR: calloc failed\n"); // we have run out of memory... time to buy a new machine or hire a better programmer. exit(1); // bail } } } [self updateEvents]; } - (void) updateEvents { NSDictionary *eventsTemp = [self events]; if (eventsTemp != runEvents) { [eventsTemp retain]; [runEvents release]; runEvents = eventsTemp; } [opengl_view stepUpdating]; } - (void) updateHUD { NSString *currHUDStr = showHUD ? HUDString:@""; NSMutableDictionary *standardStrAttrib=[NSMutableDictionary dictionary]; [standardStrAttrib setObject:HUDfont forKey:NSFontAttributeName]; [standardStrAttrib setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName]; StringTexture *anHUD = [[StringTexture alloc] initWithString:currHUDStr withAttributes:standardStrAttrib withTextColor:[NSColor colorWithDeviceRed:1.0f green:1.0f blue:1.0f alpha:1.0f] withBoxColor:HUDColor withBorderColor:[NSColor colorWithDeviceRed:0.6f green:0.6f blue:0.6f alpha:0.8f]]; [anHUD setMargins:NSMakeSize(0.0f,0.0f)]; [HUD release]; HUD = anHUD; [opengl_view stepUpdating]; } #pragma mark - #pragma mark accessors //=========================================================== // HUDfont //=========================================================== - (NSFont *)HUDfont { return HUDfont; } - (void)setHUDfont:(NSFont *)anHUDfont { if (HUDfont != anHUDfont) { [anHUDfont retain]; [HUDfont release]; HUDfont = anHUDfont; [self updateHUD]; } } //=========================================================== // HUDString //=========================================================== - (NSString *)HUDString { return HUDString; } - (void)setHUDString:(NSString *)anHUDString { if (HUDString != anHUDString) { [anHUDString retain]; [HUDString release]; HUDString = anHUDString; [self updateHUD]; } } //=========================================================== // showHUD //=========================================================== - (BOOL)showHUD { return showHUD; } - (void)setShowHUD:(BOOL)flag { showHUD = flag; [self updateHUD]; } //=========================================================== // runData //=========================================================== - (JSRunModel *)runData { return runData; } - (void)setRunData:(JSRunModel *)aRunData { if (runData != aRunData) { [aRunData retain]; [runData release]; runData = aRunData; [self setHUDString: makeHUDString([runData duration], [runData distance], [runData calories])]; } [self updateDataToPlot]; } //=========================================================== // smoothData //=========================================================== - (BOOL)smoothData { return smoothData; } - (void)setSmoothData:(BOOL)flag { smoothData = flag; [self updateDataToPlot]; } //=========================================================== // movingAverageWidth //=========================================================== - (NSNumber *)movingAverageWidth { return movingAverageWidth; } - (void)setMovingAverageWidth:(NSNumber *)aMovingAverageWidth { //always round down when setting the width. Makes it easier //to bind this attribute to a slider. int width = (int) floorf([aMovingAverageWidth floatValue]); aMovingAverageWidth = [NSNumber numberWithInt:width]; if (movingAverageWidth != aMovingAverageWidth) { [aMovingAverageWidth retain]; [movingAverageWidth release]; movingAverageWidth = aMovingAverageWidth; } [self updateDataToPlot]; } //=========================================================== // unit //=========================================================== - (unitOfMeasure)unit { return unit; } - (void)setUnit:(unitOfMeasure)anUnit { unit = anUnit; [self setHUDString: makeHUDString([runData duration], [runData distance], [runData calories])]; } //=========================================================== // showKmSplits //=========================================================== - (BOOL)showSplits { return showSplits; } - (void)setShowSplits:(BOOL)flag { showSplits = flag; [self updateEvents]; } //=========================================================== // showPauses //=========================================================== - (BOOL)showPauses { return showPauses; } - (void)setShowPauses:(BOOL)flag { showPauses = flag; [self updateEvents]; } //=========================================================== // showReportsToUser //=========================================================== - (BOOL)showReportsToUser { return showReportsToUser; } - (void)setShowReportsToUser:(BOOL)flag { showReportsToUser = flag; [self updateEvents]; } //=========================================================== // showPowerSongs //=========================================================== - (BOOL)showPowerSongs { return showPowerSongs; } - (void)setShowPowerSongs:(BOOL)flag { showPowerSongs = flag; [self updateEvents]; } //=========================================================== // zoom //=========================================================== - (float)zoom { return zoom; } - (void)setZoom:(float)aZoom { if (zoom != aZoom) { zoom = aZoom; [opengl_view stepUpdating]; } } //=========================================================== // reflectionAlpha //=========================================================== - (float)reflectionAlpha { return reflectionAlpha; } - (void)setReflectionAlpha:(float)aReflectionAlpha { if (reflectionAlpha != aReflectionAlpha) { reflectionAlpha = aReflectionAlpha; [opengl_view stepUpdating]; } } ////NOT IMPLEMENTED //=========================================================== // killPoints //=========================================================== - (BOOL)killPoints { return killPoints; } - (void)setKillPoints:(BOOL)flag { killPoints = flag; [opengl_view stepUpdating]; } //=========================================================== // killDeviation //=========================================================== - (NSNumber *)killDeviation { return killDeviation; } - (void)setKillDeviation:(NSNumber *)aKillDeviation { if (killDeviation != aKillDeviation) { [aKillDeviation retain]; [killDeviation release]; killDeviation = aKillDeviation; } [opengl_view stepUpdating]; } //=========================================================== // animateOnOpen //=========================================================== - (BOOL)animateOnOpen { return animateOnOpen; } - (void)setAnimateOnOpen:(BOOL)flag { if (animateOnOpen != flag) { animateOnOpen = flag; } } //=========================================================== // animateEvents //=========================================================== - (BOOL)animateEvents { return animateEvents; } - (void)setAnimateEvents:(BOOL)flag { if (animateEvents != flag) { animateEvents = flag; } } //=========================================================== // splitsColor //=========================================================== - (NSColor *)splitsColor { return splitsColor; } - (void)setSplitsColor:(NSColor *)aSplitsColor { if (splitsColor != aSplitsColor) { [aSplitsColor retain]; [splitsColor release]; splitsColor = aSplitsColor; [splitsColor getRed:&splitsColArr[0] green:&splitsColArr[1] blue:&splitsColArr[2] alpha:&splitsColArr[3]]; [opengl_view stepUpdating]; } } //=========================================================== // pausesColor //=========================================================== - (NSColor *)pausesColor { return pausesColor; } - (void)setPausesColor:(NSColor *)aPausesColor { if (pausesColor != aPausesColor) { [aPausesColor retain]; [pausesColor release]; pausesColor = aPausesColor; [pausesColor getRed:&pausesColArr[0] green:&pausesColArr[1] blue:&pausesColArr[2] alpha:&pausesColArr[3]]; [opengl_view stepUpdating]; } } //=========================================================== // reportsToUserColor //=========================================================== - (NSColor *)reportsToUserColor { return reportsToUserColor; } - (void)setReportsToUserColor:(NSColor *)aReportsToUserColor { if (reportsToUserColor != aReportsToUserColor) { [aReportsToUserColor retain]; [reportsToUserColor release]; reportsToUserColor = aReportsToUserColor; [reportsToUserColor getRed:&reportsToUserColArr[0] green:&reportsToUserColArr[1] blue:&reportsToUserColArr[2] alpha:&reportsToUserColArr[3]]; [opengl_view stepUpdating]; } } //=========================================================== // powerSongsColor //=========================================================== - (NSColor *)powerSongsColor { return powerSongsColor; } - (void)setPowerSongsColor:(NSColor *)aPowerSongsColor { if (powerSongsColor != aPowerSongsColor) { [aPowerSongsColor retain]; [powerSongsColor release]; powerSongsColor = aPowerSongsColor; [powerSongsColor getRed:&powerSongsColArr[0] green:&powerSongsColArr[1] blue:&powerSongsColArr[2] alpha:&powerSongsColArr[3]]; [opengl_view stepUpdating]; } } //=========================================================== // dataPointsColor //=========================================================== - (NSColor *)dataPointsColor { return dataPointsColor; } - (void)setDataPointsColor:(NSColor *)aDataPointsColor { if (dataPointsColor != aDataPointsColor) { [aDataPointsColor retain]; [dataPointsColor release]; dataPointsColor = aDataPointsColor; [dataPointsColor getRed:&dataPointsColArr[0] green:&dataPointsColArr[1] blue:&dataPointsColArr[2] alpha:&dataPointsColArr[3]]; [opengl_view stepUpdating]; } } //=========================================================== // HUDColor //=========================================================== - (NSColor *)HUDColor { return HUDColor; } - (void)setHUDColor:(NSColor *)anHUDColor { if (HUDColor != anHUDColor) { [anHUDColor retain]; [HUDColor release]; HUDColor = anHUDColor; [self updateHUD]; } } #pragma mark - #pragma mark Run Events Convenience - (NSArray *) kmSplits; { NSArray *tempArray = [[self runData] kmSplits]; if ([self showSplits] && tempArray && (kilometer == unit)){ return tempArray; } return [NSArray array]; } - (NSArray *) miSplits; { NSArray *tempArray = [[self runData] miSplits]; if ([self showSplits] && tempArray && (mile == unit)){ return tempArray; } return [NSArray array]; } - (NSArray *) pauses; { NSArray *tempArray = [[self runData] pauses]; if ([self showPauses] && tempArray){ return tempArray; } return [NSArray array]; } - (NSArray *) reportsToUser; { NSArray *tempArray = [[self runData] reportsToUser]; if ([self showReportsToUser] && tempArray){ return tempArray; } return [NSArray array]; } - (NSArray *) powerSongs; { NSArray *tempArray = [[self runData] powerSongs]; if ([self showPowerSongs] && tempArray){ return tempArray; } return [NSArray array]; } - (NSDictionary *) events; { NSArray *objs = [NSArray arrayWithObjects:[self kmSplits],\ [self miSplits],\ [self pauses],\ [self reportsToUser],\ [self powerSongs],\ nil]; NSArray *keys = [NSArray arrayWithObjects:@"kmSplits",\ @"miSplits",\ @"pauses",\ @"reportsToUser",\ @"powerSongs",\ nil]; return [NSDictionary dictionaryWithObjects:objs forKeys:keys]; } @end