/********************************************************************** 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 *********************************************************************/ #include "DataVisualization.h" #include #include #include #include "png_texture.h" #include "vvector.h" #include "normals.h" #include "model_loader.h" #include "primitiveDrawing.h" #import #import #import #import "StringTexture.h" #import #define MOVE_IN_Y_AXIS 1 #define glReportError()\ {\ GLenum error=glGetError();\ if(GL_NO_ERROR!=error)\ {\ printf("GL error at %s:%d: %s\n",__FILE__,__LINE__,(char*)gluErrorString(error));\ }\ }\ #pragma mark - #pragma mark File Vars #pragma mark externs extern int mouse_location[2]; extern float zoom; extern float reflectionAlpha; extern float overallDistance; extern NSArray *normalizedArray; //holds data points normalized from 0 .. 1 extern NSDictionary *runEvents; extern StringTexture *HUD; extern float * previousXPositions; //used in the animation of the graph to hold previous x positions //(The graph animates left to right and the events animate top to bottom) extern unitOfMeasure unit; //shared with JSRunGLVizController extern float splitsColArr[4]; extern float pausesColArr[4]; extern float reportsToUserColArr[4]; extern float powerSongsColArr[4]; extern float dataPointsColArr[4]; #pragma mark animation and mouse static float animation = 0.0; static float window_size[2]; static float camera_location[3] = {0}; int animatingGraph = 0; //shared with JSRunGLVizController int animatingEvents = 0; //shared with JSRunGLVizController static float mouse_scaling[3] = {0}; static int mouse_drag_object = 0; static int dragFlag = 0; #pragma mark Textures //textures will go away sooner or later ? static GLuint lineTexture = -1; #pragma mark Color static const GLfloat greyishColor[] = {0.55, 0.55, 0.65}; #pragma mark models enum { kModel_Sphere = 0, kModel_Pause, kModel_Milemark, kModel_Target, kModel_Question, kModel_NumberOfModels }; static NSString * model_names[kModel_NumberOfModels] = { @"sphere.obj", @"pause.obj", @"milemark.obj", @"target.obj", @"question.obj" }; WFObject * models[kModel_NumberOfModels] = {0}; #pragma mark - #pragma mark perspective and camera float getAspectRatio() { float aspectRatio = ((float) window_size[1] / (float) window_size[0]); if (aspectRatio > 1) { aspectRatio = ((float) window_size[0] / (float) window_size[1]); } return aspectRatio; } void setInitialCameraLocation() { float aspectRatio = getAspectRatio(); camera_location[0] = -0.5; camera_location[1] = -0.5 * aspectRatio; camera_location[2] = 0.0; } void setPerspective() { gluPerspective(30.0*zoom, 1.0/getAspectRatio(), 0.1, 2.0); } #pragma mark - #pragma mark draw geometry void animateGraph() { animatingGraph = 1; setInitialCameraLocation(); } void renderGraph(const GLfloat pointColor[3], const GLfloat lineColor[3]) { glEnable(GL_DEPTH_TEST); float goalX, onePointBackX, onePointBackY, onePointBackZ, x, y, z, scale, alpha, aspectRatio; int numberOfPointsToPlot = [normalizedArray count]; scale = (1.0 / (float) numberOfPointsToPlot) * .49; z = - zoom; aspectRatio = getAspectRatio(); for(int pass = 1; pass <= 2; pass++) { //two passes through the drawing loop; once with lights on, once with lights off if (1 == pass) { const GLfloat specularColor[3] = {0.316228, 0.316228, 0.316228}; const GLfloat shininess = 40.8; setModelSpecularColor(specularColor, shininess); glEnable(GL_LIGHTING); } if (2 == pass) { unsetModelSpecularColor(); glDisable(GL_LIGHTING); } onePointBackX = onePointBackY = NULL; onePointBackZ = z; alpha = .99; for(int i = 0; i < numberOfPointsToPlot; i++) { //////////////////////////////////GRAPHIC/////////////////////////////////// // put x position at index + a little to make sure we don't try to // render at 0 point because that screws up the impostor program. goalX = ((float) i + 0.001) / (float) numberOfPointsToPlot; x = previousXPositions[i]; y = ([[normalizedArray objectAtIndex:i] floatValue]); //handle animation if (animatingGraph != 0 && x < (goalX - 0.00000003)) { // the 0.00000004 makes it jump to full width just as it finishes // we can change "how hard it locks in at the end" by changing the // value. Lower number = softer. if (1 == pass) x += (goalX - x) * 0.12345; //.12345 == Rocco's magic animation number } else { //done animating so X is at the goal position. x = goalX; animatingGraph = 0; } GLfloat begin[3] = {onePointBackX, onePointBackY, onePointBackZ}; GLfloat end[3] = {x, y, z}; //render the dataPoint on the first pass (lights on) and the line on //the secont pass (lights off) if (pass == 1) { glLoadName(i); //generate an integer for a-clickin' drawModel(models[kModel_Sphere], end, scale, pointColor, alpha); } if (pass == 2 && onePointBackX && onePointBackY) lineAsQuad(begin, end, scale*.7, lineColor, alpha * 0.85, lineTexture); /////////////////////////////////REFLECTION///////////////////////////////// //reflections are easy, just rerender with negative Y values and a lower //alpha value. (Look at the new itunes interface for an example). begin[1] = -onePointBackY - (scale * 1.8); end[1] = -y - (scale * 1.8); float currentAlpha = reflectionAlpha * (aspectRatio - y); if (currentAlpha < 0.0) currentAlpha = 0.0; if (pass ==1) drawModel(models[kModel_Sphere], end, scale, pointColor, currentAlpha); if (pass == 2 && onePointBackX && onePointBackY) lineAsQuad(begin, end, scale*.7, lineColor, currentAlpha * 0.55, lineTexture); //////////////////////////////////////////////////////////////////////////// //put the position number back before we save it for the next pass begin[1] = -onePointBackY - (scale * 1.8); end[1] = -y - (scale * 1.8); // save position for the line origin on the next go 'round onePointBackX = x; onePointBackY = y; onePointBackZ = z; // save current x positions for the animation to add to on the next go 'round previousXPositions[i] = x; } //Yes Virginia, there are more efficient ways to do the above, but this is //fast enough and is easier for the future me to understand. Future me is //dumb. In fact present me isn't so bright, either. Just ask Future me. } static const GLfloat clickPlaneColor[] = {0.0, 0.0, 0.0, 0.0}; const GLfloat lowerLeft[] = {0, -aspectRatio, z * 1.0001}; const GLfloat upperRight[] = {1, aspectRatio, z * 1.0001}; //labels our plain quad with the next available label number glLoadName(numberOfPointsToPlot); plainQuad(lowerLeft, upperRight, clickPlaneColor); } float getNearDataPoint(float normalizedPosition, BOOL above) { // Returns the largest or smallest value of nearby data points so // we can place icons above or below the graph. int length = [normalizedArray count]; int interestingWidth = 4; float indexNear = (normalizedPosition * (float) length - 1); int centerIndex = (int) floorf(indexNear); if (centerIndex < 0) centerIndex = 0; float point = [[normalizedArray objectAtIndex:centerIndex] floatValue]; float currPoint; for(int index = centerIndex - interestingWidth; index < centerIndex + interestingWidth; index++) { if (index >= 0 && index < length) { currPoint = [[normalizedArray objectAtIndex:index] floatValue]; if (above && (point < currPoint)) point = currPoint; if (!above && (point > currPoint)) point = currPoint; } } return point; } extern void animateEvents() { animatingEvents = 1; } //Possible way to do animation here: /* (Move all of this code into renderEvents()) Start constant GL update. Make an NSDictionary for positions (w/ keys corresponding to the keys in events) Similar for velocity Enumerate through the runevents dictionary keeping track of position and velocity on each run through. Use Rocco's gravity calculation. Stop updatingGL when all points are stopped. */ void renderEventType(NSArray *events, const GLfloat pointColor[3], const GLuint texture, BOOL followGraph, float scale, float position) { float lastX, x, y, z, alpha; alpha = 1.0; z = (- zoom); lastX = -10.0; // set it waaay beyond the bounds for the first comparison NSEnumerator * eventsEnumerator = [events objectEnumerator]; JSRunEvent * event; while(event = [eventsEnumerator nextObject]) { NSEnumerator * distances = [[event distances] objectEnumerator]; // Usually only one piece of data, but the splits have multiple distances // so we enumerate through distances, too. float eventDistance; while (eventDistance = [[distances nextObject] floatValue]) { //there is only one if (overallDistance > 0) // nans suck { x = eventDistance / overallDistance; if (followGraph) { BOOL above; if (position < 0) above = NO; else above = YES; y = (getNearDataPoint(x, above) + position); //handle animation here } else //!followGraph y = position; GLfloat position[3] = {x, y, z}; if ((x - lastX) >= scale) { // Don't draw another if it is near enough to overlap. // Near successive events of a given type just won't render dotQuad(position, scale, pointColor, alpha , texture); lastX = x; } } } } } void renderEventTypeModel(NSArray *events, const GLfloat pointColor[3], WFObject * model, BOOL followGraph, float scale, float position) { const GLfloat specularColor[3] = {0.316228, 0.316228, 0.316228}; const GLfloat shininess = 12.8; setModelSpecularColor(specularColor, shininess); glEnable(GL_LIGHTING); float lastX, x, y, z, alpha; alpha = 1.0; z = (- zoom); lastX = -10.0; // set it waaay beyond the bounds for the first comparison NSEnumerator * eventsEnumerator = [events objectEnumerator]; JSRunEvent * event; while(event = [eventsEnumerator nextObject]) { NSEnumerator * distances = [[event distances] objectEnumerator]; // Usually only one piece of data, but the splits have multiple distances // so we enumerate through distances, too. float eventDistance; while (eventDistance = [[distances nextObject] floatValue]) { //there is only one if (overallDistance > 0) // nans suck { x = eventDistance / overallDistance; if (followGraph) { BOOL above; if (position < 0) above = NO; else above = YES; y = (getNearDataPoint(x, above) + position); //handle animation here } else //!followGraph y = position; GLfloat position[3] = {x, y, z}; if ((x - lastX) >= scale) { // Don't draw another if it is near enough to overlap. // Near successive events of a given type just won't render drawModel(model, position, scale, pointColor, alpha); lastX = x; } } } } unsetModelSpecularColor(); glDisable(GL_LIGHTING); } void renderEvents() { glEnable(GL_DEPTH_TEST); //glLoadName(0); //load the nothing click number for events //keys = @"kmSplits", @"miSplits", @"pauses", @"reportsToUser", @"powerSongs" //using a model renderEventTypeModel([runEvents valueForKey:@"pauses"], pausesColArr, models[kModel_Pause], YES, 0.008, 0.04); renderEventTypeModel([runEvents valueForKey:@"powerSongs"], powerSongsColArr, models[kModel_Target], YES, 0.003, 0.02); renderEventTypeModel([runEvents valueForKey:@"reportsToUser"], reportsToUserColArr, models[kModel_Question], YES, 0.003, -0.03); //render splits in a row along the bottom renderEventTypeModel([runEvents valueForKey:@"kmSplits"], splitsColArr, models[kModel_Milemark], NO, 0.02, -0.02); renderEventTypeModel([runEvents valueForKey:@"miSplits"], splitsColArr, models[kModel_Milemark], NO, 0.02, -0.02); } void renderBackground() { glDisable(GL_DEPTH_TEST); //fprintf(stderr, " Background!! %i ", clickNumber); glMatrixMode(GL_PROJECTION); glPushMatrix(); { glLoadIdentity(); gluOrtho2D(0.0, 1.0, 0.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glDisable(GL_LIGHTING); glDisable(GL_DEPTH_TEST); GLfloat lowerLeft[3] = {0.0, 0.0, 0.0}; GLfloat upperRight[3] = {1.0, 0.5, 0.0}; gradientQuad(lowerLeft, upperRight); glMatrixMode(GL_PROJECTION); }glPopMatrix(); } void renderHUD() { GLint matrixMode; GLfloat viewport[4]; glGetFloatv(GL_VIEWPORT,viewport); GLfloat width=viewport[2]; GLfloat height=viewport[3]; glDisable(GL_DEPTH_TEST); // ensure text is not remove by depth buffer test. glEnable(GL_TEXTURE_RECTANGLE_EXT); // set orthograhic 1:1 pixel transform in local view coords glGetIntegerv(GL_MATRIX_MODE,&matrixMode); glMatrixMode(GL_PROJECTION); glPushMatrix(); { glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); { glLoadIdentity(); glScalef(2.0/width,-2.0/height,1.0); glTranslatef(-width/2.0,-height/2.0,0.0); glColor4f(1.0,1.0,1.0,1.0); [HUD drawAtPoint:NSMakePoint(7.0,height-[HUD framesize].height-7.0)]; } // reset orginal martices glPopMatrix(); // GL_MODELVIEW glMatrixMode(GL_PROJECTION); } // reset orginal martices glPopMatrix();// GL_PROJECTION glMatrixMode(matrixMode); glDisable(GL_TEXTURE_RECTANGLE_EXT); glReportError(); } void renderScene() { renderBackground(); glColor4f(1.0, 1.0, 1.0, 1.0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); setPerspective(); glMatrixMode(GL_MODELVIEW); glTranslated(camera_location[0], camera_location[1], camera_location[2]); renderGraph(dataPointsColArr, greyishColor); if (!animatingGraph) renderEvents(); renderHUD(); } #pragma mark - #pragma mark Mouse and Pan int selectScene(int x, int y, GLdouble * click_depth) { GLuint buffer[512]; GLint numberOfHits; GLint selection_viewport[4]; int choice = -1; glGetIntegerv(GL_VIEWPORT, selection_viewport); // Set the buffer we want the select results stored inside glSelectBuffer(512, buffer); // Turn on selection mode glRenderMode(GL_SELECT); // Init selection names glInitNames(); glPushName(0); // Need to set up a separate projection matrix... glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Define a picking region gluPickMatrix(x, y, 3.0, 3.0, selection_viewport); setPerspective(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslated(camera_location[0], camera_location[1], camera_location[2]); // only render those things we might want to click on! Saves some cycles. renderGraph(dataPointsColArr, greyishColor); // All done rendering, did we get any hits? numberOfHits = glRenderMode(GL_RENDER); if(numberOfHits > 0) { unsigned int depth = buffer[1]; int loop; choice = buffer[3]; for(loop = 1; loop < numberOfHits; loop++) { if(buffer[ loop * 4 + 1 ] < (GLuint) depth) { choice = buffer[loop * 4 + 3]; depth = buffer[loop * 4 + 1]; } fprintf(stderr, " (choice) %i\n", choice); } if(click_depth) { fprintf(stderr, "depth = %u\n", depth); *click_depth = depth / (GLdouble)4294967295.0; } } // Reset the projection matrix... // Need to set up a separate projection matrix... glMatrixMode(GL_PROJECTION); glLoadIdentity(); setPerspective(); glMatrixMode(GL_MODELVIEW); return choice; } void rightMouseDownGL(int x, int y) { } void rightMouseUpGL(int x, int y) { } void leftMouseDownGL(int x, int y) { dragFlag = 0; // keeps the mouseDelta from grabbing any old stale mouse delta only the // initially. This gets set to 1 in the dragging code so we only drop // the first one. If we do not do this, we get things jumping around // from old mouse drags that didn't actually touch anything. GLint viewport[4]; GLdouble mvmatrix[16] = {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0}, projmatrix[16]; GLdouble pointA[3], pointB[3]; GLdouble depth = 0; int choice; if (!animatingGraph) choice = selectScene(x, y, &depth); else choice = 0; //avoid clicking while things are moving 'round if(choice > 1) // 0 is the initial "nothing" choice from glpushname and 1 is the background, // so we seek out objects pushed on the stack afterward. { fprintf(stderr, "\nClicked at (%d, %d) on object %d at depth %f\n", x, y, choice, depth); // Figure out the window coordinate ratio for the specified depth... glGetIntegerv(GL_VIEWPORT, viewport); glGetDoublev(GL_PROJECTION_MATRIX, projmatrix); gluUnProject(x, y, depth, mvmatrix, projmatrix, viewport, pointA+0, pointA+1, pointA+2); gluUnProject(x+1, y+1, depth, mvmatrix, projmatrix, viewport, pointB+0, pointB+1, pointB+2); mouse_scaling[0] = fabs(pointB[0] - pointA[0]); mouse_scaling[1] = fabs(pointB[1] - pointA[1]); mouse_scaling[2] = fabs(pointB[2] - pointA[2]); mouse_drag_object = 1; } } void leftMouseUpGL(int x, int y) { mouse_drag_object = 0; } #pragma mark - #pragma mark Load Models and Textures // may eventually replace all of these with .obj models void loadImpostorTextures() { NSBundle *glVizBundle = [NSBundle bundleWithIdentifier:@"com.jonathansaggau.JSRunGLVizFramework"]; lineTexture = png_texture([[glVizBundle pathForResource:@"line" ofType:@"png"] fileSystemRepresentation], GL_CLAMP_TO_BORDER); } void loadModels() { NSBundle *glVizBundle = [NSBundle bundleWithIdentifier:@"com.jonathansaggau.JSRunGLVizFramework"]; for(int i = 0; i < kModel_NumberOfModels; i++) { models[i] = obj_load([[glVizBundle pathForResource:model_names[i] ofType:NULL] fileSystemRepresentation]); NSLog(@""); NSLog(@"%@",model_names[i]); obj_print(models[i]); } } #pragma mark - #pragma mark GL void initGL() { GLdouble bounds[4]; glGetDoublev(GL_VIEWPORT, bounds); window_size[0] = bounds[2]; window_size[1] = bounds[3]; dragFlag = 0; // background color glClearColor(0.0, 0.0, 0.0, 0.0); glShadeModel(GL_SMOOTH); glEnable(GL_CULL_FACE); // Setup GL glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_DEPTH_TEST); loadImpostorTextures(); initImpostorProgram(); loadModels(); threePointLighting(); setInitialCameraLocation(); } void renderGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); setPerspective(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); renderScene(); } void postRenderGL() { } int updateGL() { CGMouseDelta x, y; CGGetLastMouseDelta(&x, &y); if(mouse_drag_object != 0 and dragFlag != 0) { camera_location[0] += x * mouse_scaling[0]; #ifdef MOVE_IN_Y_AXIS camera_location[1] += -y * mouse_scaling[1]; #endif } dragFlag = 1; animation += 1.0; return 1; } void reshapeGL(int width, int height) { glViewport(0, 0, width, height); glMatrixMode(GL_MODELVIEW); window_size[0] = width; window_size[1] = height; } void destructGL() { }