#import "MyOpenGLView.h" @implementation MyOpenGLView - (void)awakeFromNib { glInitialized = NO; // We'll do this from the first draw loop // Create a dummy object, since NSArrays can't hold nil values myNull = [NSNull null]; // Create our main array, which holds the polygon itself at index 0, and // each subdivision pass at successive indices mySubdivisions = [[NSMutableArray alloc] init]; // Fill the array with dummies since none of this has been calculated yet { int ii; for (ii = 0; ii < MAX_SUBDIVISIONS; ii++) [mySubdivisions addObject: myNull]; } currentRotation.x = 0.0f; currentRotation.y = 0.0f; currentTranslation.x = 0.0f; currentTranslation.y = 0.0f; subdivisionType = SPLIT_AND_TWEAK; [solidDrawingMenuItem setEnabled: YES]; [fogMenuItem setEnabled: YES]; // Create the rendering loop timer renderTimer = [[NSTimer scheduledTimerWithTimeInterval:0.025 // 40 fps theoretical max target:self // Target is this object selector:@selector(draw) // What function are we calling userInfo:nil repeats:YES] // No userinfo / repeat infinitely retain]; // No autorelease // Add our timer to the EventTracking loop and the ModalPanel loop, so that we draw // as often as possible. [[NSRunLoop currentRunLoop] addTimer: renderTimer forMode: NSEventTrackingRunLoopMode]; [[NSRunLoop currentRunLoop] addTimer: renderTimer forMode: NSModalPanelRunLoopMode]; } - (void)refillDummiesForSubdivisions { int ii; for (ii = 1; ii < MAX_SUBDIVISIONS; ii++) // start at one--zero should always be just the poly [mySubdivisions replaceObjectAtIndex: ii withObject: myNull]; } - (void)readySubdivision:(int)idx { if (([[mySubdivisions objectAtIndex: idx] isKindOfClass: [MyPolygon class]] == NO) && ([[mySubdivisions objectAtIndex: idx] isKindOfClass: [MyTube class]] == NO)) { // We're dealing with a dummy object, so we must calculate the subdivision // before we draw it. MyPolygon *newSubdivision, *previousSubdivision; // Keep in mind that the object before this one had better be defined. previousSubdivision = [mySubdivisions objectAtIndex: (idx - 1)]; newSubdivision = [previousSubdivision subdivideWith: subdivisionType]; [mySubdivisions replaceObjectAtIndex: idx withObject: newSubdivision]; [newSubdivision release]; NSLog(@"Created subdivision level %d", idx); } } - (void)drawSubdivision:(int)idx { NSObject *currentSubdivision = [mySubdivisions objectAtIndex: idx]; if ([currentSubdivision isKindOfClass: [MyPolygon class]]) { MyPolygon *thePoly = (MyPolygon *)currentSubdivision; glDisable(GL_FOG); // Fog isn't necessary for simple polygons glDisable(GL_LIGHTING); // Nor is lighting glDisable(GL_LIGHT0); [thePoly drawSelf]; } else if ([currentSubdivision isKindOfClass: [MyTube class]]) { MyTube *theTube = (MyTube *)currentSubdivision; if ([fogMenuItem state] == NSOnState) glEnable(GL_FOG); // But, fog helps me visualize the 3D-ness of the tubes else glDisable(GL_FOG); glEnable(GL_LIGHTING); [theTube drawSelf]; } } - (void)initGL { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glLineWidth(2.0f); // Big, fat lines /* Fog helps me see in 3D, so I'll just put some simple fog in here */ { GLfloat fogColor[4] = {0.0f, 0.0f, 0.0f, 1.0f}; glFogi(GL_FOG_MODE, GL_EXP2); glFogfv(GL_FOG_COLOR, fogColor); glFogf(GL_FOG_DENSITY, 0.30); glHint(GL_FOG_HINT, GL_DONT_CARE); glFogf(GL_FOG_START, 1.0f); glFogf(GL_FOG_END, -1.0f); } { GLfloat specular [] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat shininess [] = { 100.0 }; GLfloat position [] = { 1.0, 1.0, 1.0, 0.0 }; // Set the polygon mode to fill glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // Define material properties of specular color and degree of // shininess. Since this is only done once in this particular // example, it applies to all objects. Material properties can // be set for individual objects, individual faces of the objects, // individual vertices of the faces, etc... glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular); glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shininess); // Set the GL_AMBIENT_AND_DIFFUSE color state variable to be the // one referred to by all following calls to glColor glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); // Create a Directional Light Source glLightfv(GL_LIGHT0, GL_POSITION, position); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); } glInitialized = YES; } - (void)drawGL { float scale = [scaleSlider floatValue]; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glTranslatef(0.0f, 0.0f, -4.0f); // Move away from the visible area glScalef(scale, scale, scale); glTranslatef(currentTranslation.x / scale, currentTranslation.y / scale, 0.0f); // Translate according to mouse drags glRotatef(currentRotation.y, 1.0f, 0.0f, 0.0f); glRotatef(currentRotation.x, 0.0f, 1.0f, 0.0f); // Set the shading model if ([smoothShadingMenuItem state] == NSOnState) glShadeModel(GL_SMOOTH); else glShadeModel(GL_FLAT); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // Only execute the drawing steps if we have a loaded polygon or tube. if (([[mySubdivisions objectAtIndex: 0] isKindOfClass: [MyPolygon class]]) || ([[mySubdivisions objectAtIndex: 0] isKindOfClass: [MyTube class]])) { int ii; int currentSelectedSubdivision = [subdivisionLevelSlider intValue]; BOOL aggregate = ([aggregateCheckbox state] == NSOnState); BOOL solid = ([solidDrawingMenuItem state] == NSOnState); // Calculate those subdivisions that we're going to display for (ii = 0; ii < currentSelectedSubdivision; ii++) [self readySubdivision: ii]; if ((!solid) || (([[mySubdivisions objectAtIndex: 0] isKindOfClass: [MyPolygon class]]))) { if (aggregate) { // Draw the subdivisions, with the smoothest on the bottom for (ii = 0; ii < currentSelectedSubdivision; ii++) { if (ii > 0) // prevent divide by zero glColor4f(1.0f, 1.0f, 1.0f, 0.7f / (pow((float)ii, 1.0))); [self drawSubdivision: ii]; } // Highlight the current subdivision glColor4f(0.3f, 0.3f, 1.0f, 0.7f); [self drawSubdivision: (currentSelectedSubdivision - 1)]; } else { [self drawSubdivision: (currentSelectedSubdivision - 1)]; } } else { // Draw a solid tube MyTube *currentSubdivision = (MyTube *)[mySubdivisions objectAtIndex: (currentSelectedSubdivision - 1)]; if ([fogMenuItem state] == NSOnState) glEnable(GL_FOG); // Fog helps me visualize the 3D-ness of the tubes else glDisable(GL_FOG); glEnable(GL_LIGHTING); [currentSubdivision drawSolid]; // [currentSubdivision drawNormals]; } } } - (void)draw { [[self openGLContext] makeCurrentContext]; if (glInitialized == NO) [self initGL]; [self drawGL]; // Update the GL context glFlush(); [[self openGLContext] flushBuffer]; } - (void)mouseDragged:(NSEvent *)theEvent { BOOL keepOn = YES; NSPoint myLoc; NSPoint initialLoc; theEvent = [[self window] nextEventMatchingMask: (NSLeftMouseUpMask | NSLeftMouseDraggedMask)]; initialLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; while (keepOn == YES) { theEvent = [[self window] nextEventMatchingMask: (NSLeftMouseUpMask | NSLeftMouseDraggedMask)]; myLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; if ([theEvent modifierFlags] & NSControlKeyMask) { // Control key translates rather than rotates currentTranslation.x += (myLoc.x - initialLoc.x) / 50.0f; currentTranslation.y += (myLoc.y - initialLoc.y) / 50.0f; } else { currentRotation.x += myLoc.x - initialLoc.x; currentRotation.y += myLoc.y - initialLoc.y; } if ([theEvent type] == NSLeftMouseUp) keepOn = NO; initialLoc = myLoc; } } - (void)reshape: (NSRect)newRect { GLsizei w, h; GLfloat windowAspect, windowWidth, windowHeight; w = (GLsizei)NSWidth(newRect); h = (GLsizei)NSHeight(newRect); if (h == 0) // Dumb hack to prevent divide by zero h = 1; glViewport(0, 0, w, h); windowAspect = (GLfloat)w/(GLfloat)h; windowWidth = w; windowHeight = h; // Reset coordinate system glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Set perspective projection for any later use gluPerspective(60.0f, windowAspect, 1.0f, 400.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } - (void)drawRect:(NSRect)rect { [self reshape: rect]; [self draw]; } - (void)openDoc:(id)sender { int result; NSArray *fileTypes = [NSArray arrayWithObjects:@"poly", @"tube", nil]; NSOpenPanel *oPanel = [NSOpenPanel openPanel]; [oPanel setAllowsMultipleSelection: NO]; result = [oPanel runModalForDirectory: [[NSBundle mainBundle] bundlePath] file: nil types: fileTypes]; if (result == NSOKButton) { NSString *fileToOpen = [[oPanel filenames] objectAtIndex: 0]; // Re-fill the array with dummies since we're gonna have to recalculate [self refillDummiesForSubdivisions]; if ([[fileToOpen pathExtension] isEqualToString: @"poly"]) { MyPolygon *newPolygon; // And load the new polygon into subdivision location 0 newPolygon = [[MyPolygon alloc] init]; [newPolygon loadFromFile: fileToOpen]; [mySubdivisions replaceObjectAtIndex: 0 withObject: newPolygon]; [newPolygon release]; // release it since the array retained it. } else if ([[fileToOpen pathExtension] isEqualToString: @"tube"]) { MyTube *newTube; // And load the new tube into subdivision location 0 newTube = [[MyTube alloc] init]; [newTube loadFromFile: fileToOpen]; [mySubdivisions replaceObjectAtIndex: 0 withObject: newTube]; [newTube release]; // release it since the array retained it. } } } - (void)changeSubdivisionType:(id)sender { subdivisionType = ([sender selectedRow] == 0) ? SPLIT_AND_TWEAK : FOUR_POINT; // Since this invalidates our previous subdivision calculations, we'll reset the array. [self refillDummiesForSubdivisions]; } - (void)menuItemStatusChange:(id)sender { // Just toggle the menuitem state... We'll handle everything else in other routines. [sender setState: ![sender state]]; } @end