Three Dimensions

The XY plane is the plane of the iPhone screen. By default, the torus is centered at the origin and lies in the XY plane. The translate matrix in the display function in threedimensions.cpp moves the torus 7 units away from us. (This puts the torus squarely in the visible zone, which goes from 1 to 10 units away from us. See the SetupRC function.) The rotate matrix in display rotates the torus around the Y axis. The modelView matrix is a combination of translate followed by rotate. The projection matrix in the frustum make distant objects look smaller and near objects look bigger.

Source code in GL.zip

  1. threedimensions.cpp
  2. main.m
  3. Class GLAppDelegate
  4. Class GLViewController
  5. Class EAGLView

Create the project

  1. Create the project using the OpenGL ES Application template. I named the project GL. If you run the project at this point you get a bouncing two-d square. We will change it to a rotatable three-d torus.

  2. Let the fourth argument of UIApplicationMain in main.m remain nil. Do not remove the MainWindow.xib file from the GL-Info.plist or from the project.

  3. Create a new group. In the Groups & Files pane of Xcode, right-click on the name of the project (GL) and select
    Add → New Group. Name the new group GLTools. It isn’t a folder, but it looks like one.

  4. Download SB5.zip and Xcode.zip from the OpenGL SuperBible home page into the Downloads folder on your Mac. In SB5.zip, go to the folder Src/GLTools/include and copy the 11 .h files into the root directory of your project. Then go to the folder Src/GLTools/src and copy the 5 .cpp files into the root folder of your project. In the Groups & Files pane of Xcode, select the GLTools group you created and add the 16 files to the project: 11 .h files and 5 .cpp files.

  5. Project → Edit Active Target "GL" → Build → Search Paths
    Set the Header Search Paths to dot (the character .). Dot means the current directory.

  6. Select the Other Sources folder in the Groups & Files pane of Xcode.
    File → New File…
    Choose a template for your new file: Mac OS X C and C++
    C++ File
    Next
    File Name: threedimensions.cpp
         (do not create a corresponding .h file)
    Finish
    Then copy this content into your new file.

  7. In SB5.zip, go to the folder GLTools/GLTools. Copy the file libGLTools.a into the root directory of your project.

  8. Project → Edit Active Target "GL" → General → Linked Libraries
    Press the plus sign and add the OpenGLES.framework to the project. Press the plus sign again and Add Other… to add to the project the libGLTools.a you copied into the root folder of your project.

  9. Add the instance variable theta to class EAGLView in EAGLView.h.
    	GLfloat theta;	//in degrees
    
    Initialize it in the initWithCoder: method of class EAGLView.
    		theta = 0.0f;
    
    Add the property theta to class EAGLView.
    @property (nonatomic, assign) GLfloat theta;	//in EAGLView.h
    
    @synthesize theta;	//in EAGLView.m
    

  10. The two .m files that call functions written in C++ have to be renamed to .mm: GLViewController.mm and EAGLView.mm. In the Classes folder of the Groups & Files pane of Xcode, right-click on the filename and select Rename. Update the filename in the comment at the top of each file.

  11. Declare the C++ functions at the top of EAGLView.mm immediately after the the #imports.
    //C++ functions defined in threedimensions.cpp
    void SetupRC();
    void reshape(GLsizei width, GLsizei height);
    void display(GLfloat theta);
    
    SetupRC is called in the createFramebuffer method of class EAGLView immediately before the glCheckFramebufferStatus. reshape is called in the setFramebuffer method of class EAGLView in place of the call to glViewport. display will be called in the following method touchesMoved:withEvent:.

  12. Add the following method to class EAGLView in EAGLView.m.
    - (void) touchesMoved: (NSSet *) touches withEvent: (UIEvent *) event {
    	CGFloat newX = [[touches anyObject]         locationInView: self].x;
    	CGFloat oldX = [[touches anyObject] previousLocationInView: self].x;
    
    	//Swiping across the width of the screen should rotate the torus 180°.
    	//Theta is in degrees.
    	CGFloat dx = newX - oldX;
    	theta += 180.0f * dx / framebufferWidth;
    
    	[self setFramebuffer];
    	display(theta);
    	[self presentFramebuffer];
    }
    

  13. Declare one fuction at the top of GLViewController.mm immediately after the the #imports.
    //C++ function defined in threedimensions.cpp
    void display(GLfloat theta);
    
    Call it in the drawFrame method of class GLViewController, replacing all of the code between setFrameBuffer and presentFramebuffer.
    	display(((EAGLView *)self.view).theta);
    

  14. The framebuffer in EAGLView already contains a renderbuffer. Let’s also give it a depth buffer. Add the following instance variable to class EAGLView in EAGLView.h.
    	GLuint depthRenderbuffer;
    
    Insert the following code into the createFramebuffer method of class EAGLView immediately before the SetupRC.
    	//Create the depth buffer.
    
    	glGenRenderbuffers(1, &depthRenderbuffer);
    	glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
    	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16,
    		framebufferWidth, framebufferHeight);
    	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
    		depthRenderbuffer);
    
    Insert the following code into the deleteFramebuffer method of class EAGLView immediately after the two analogous ifs.
    	if (depthRenderbuffer)
            {
                glDeleteRenderbuffers(1, &depthRenderbuffer);
                depthRenderbuffer = 0;
            }
    

Things to try

  1. The call to glViewPort in the function reshape in threedimensions.cpp makes the picture fill the entire iPhone screen. What happens if you change
    	glViewport(0, 0, width, height);
    
    to one of the following?
    	glViewport(0, 0, width / 2, height / 2);
    	glViewport(width / 4, height / 4, width / 2, height / 2);
    

  2. The call to the SetPerspective member function of the frustum in reshape allows us to see objects up to 10 units away from us. This is okay because the display function translates the torus only 7 units away. What hapens if you change the fourth argument of SetPerspective from 10.0f to 7.0f?

  3. What goes wrong without the
    	glEnable(GL_CULL_FACE);
    	glEnable(GL_DEPTH_TEST);
    
    in SetupRC in threedimensions.cpp?

  4. Make the torus rotate by itself, without having to be dragged. Change
    	modelViewMatrix.Rotate(theta, 0.0f, 1.0f, 0.0f);
    
    to the following, and include <StopWatch.h>.
    	static CStopWatch watch;
    	modelViewMatrix.Rotate(watch.GetElapsedSeconds() * 60.f, 0.0f, 1.0f, 0.0f);
    
    Then change it back.

  5. Draw a sphere, too. Declare it at the top of threedimensions.cpp.
    GLTriangleBatch torus;
    GLTriangleBatch sphere;
    
    In SetupRC,
    	gltMakeTorus(torus, 1.0f, 1.0f / 3.0f, 40, 40);
    
    	gltMakeSphere(
    		sphere,
    		.25,	//radius
    		40,
    		40
    	);
    
    The sphere is centered at (0, 0, 0), but is then translated and rotated along with the torus. In display,
    	torus.Draw();
    	sphere.Draw();
    

  6. Make a cylinder. By default, its axis lies along the Z axis. It has no top or bottom, only sides. Try it without GL_CULL_FACE and GL_DEPTH_TEST.
    	GLTriangleBatch cylinder;
    
    	gltMakeCylinder(
    		cylinder,
    		.25f,	//base radius
    		.25f,	//top radius
    		.25f,	//height
    		40,
    		40
    	);
    
    	cylinder.Draw();
    

  7. Make a cone. Same as the cylinder, but let the top radius be 0.0f.

  8. Make a disk, or a phonograph record with a hole in the middle. Do not enable GL_CULL_FACE.
    	GLTriangleBatch disk;
    
    	gltMakeDisk(
    		disk,
    		.05,	//inner radius
    		.25f,	//outer radius
    		40,
    		40
    	);
    
    	disk.Draw();
    

  9. Make a cube:
    	GLBatch cube;
    
    	gltMakeCube(
    		cube,
    		.25f	//half of length of side
    	);
    
    	cube.Draw();
    

  10. [Dawn with her fingertips of rose.] Change the x coördinate of the light position to 100.0f.

  11. [Phases of the moon.] Replace the torus with a sphere. Change its color from red to greenCheese. Change the background to midnight blue (0.09765f, 0.09765f, 0.4375f).

    The phases of the moon traditionally start with New Moon, so change the initial location of the light to the point 0.0f, 0.0f, -100.0f, which is behind the moon. We will rotate the light in a circle that lies in the XZ plane. In other words, the axis of this circle will be the Y axis. We negate the elapsed seconds to move from New Moon to First Quarter, which looks like an uppercase D. Without the negation, we would move from New Moon to Third Quarter (a backwards uppercase D): the phases would run backwards.

    Change the call to UseStockShader to the following.
    	//One lunar month per 2pi seconds of real time.
    	static CStopWatch watch;
    	M3DMatrix44f rot;
    	m3dRotationMatrix44(rot, -watch.GetElapsedSeconds(), 0.0f, 1.0f, 0.0f);
    	M3DVector4f current;
    	m3dTransformVector4(current, lightPosition, rot);
    
    	shaderManager.UseStockShader(
    		GLT_SHADER_POINT_LIGHT_DIFF,
    		modelView,
    		frustum.GetProjectionMatrix(),
    		current,
    		greenCheese
    	);