Last modified 30 August 2003 by jdavis@math.wisc.edu
Inherits from NSObject
Conforms to NSCoding, NSCopying
SMKCamera provides high-level control of the OpenGL viewing transformations. Both perspective and orthogonal projections are supported. You aim the camera using any attached SMKTransformation, but SMKCamera supplies a variety of utility methods to help you; for example, there are methods that help you hook the aiming process into a user interface. SMKCamera has the ability to antialias the entire 3D scene, reducing unsightly pixelization. It can also mimic the OpenGL viewing transformations without using OpenGL.
If you want to use SMKCamera, then you have to use its setWidth:height: method. You must invoke this method at the start of your camera use, before any of the other methods below. You must also invoke it whenever the view is resized.
- (id)setWidth:(double)width height:(double)height;Informs the camera of the OpenGL view's width and height, which must not be 0. This method issues OpenGL commands.
Returns the current width.
Returns the current height.
For example, an NSOpenGLView might initialize a camera like this...
SMKCamera *camera; // assume this pointer exists
NSRect rect;
camera = [[SMKCamera alloc] init];
rect = [self frame];
[[self openGLContext] makeCurrentContext];
[camera setWidth:rect.size.width height:rect.size.height];
...and it could override the reshape method as follows.
- (void)reshape
{
NSRect rect;
rect = [self frame];
[[self openGLContext] makeCurrentContext];
[camera setWidth:rect.size.width height:rect.size.height];
}
SMKCamera handles both orthogonal and perspective projections. A perspective projection displays the scene as the human eye or an ordinary camera would perceive it, in that objects near the camera appear larger than objects far away. For realistic images, this is the projection style you want. On the other hand, orthogonal projections have some useful features. Under an orthogonal projection, parallel lines remain parallel and distances are preserved. For this reason, orthogonal projections are often used in drafting applications. They are also used in many third-person games, where the user does not consider herself part of the scene. (In vague terms, an orthogonal projection is what you get when you view the scene from an infinite distance with an infinite zoom lens.) Ultimately, you should choose the projection style that you prefer, or you can leave the choice up to your user.
- (id)setProjection:(int)projection;Sets the projection style. Valid arguments are SMKPerspectiveProjection (the default) and SMKOrthogonalProjection. This method issues OpenGL commands.
Returns the current projection.
When projecting a 3D scene, OpenGL automatically clips out parts of the scene that are either too close to or too distant from the camera. Specifically, there are two clipping planes, the near plane and the far plane, both perpendicular to the line of sight. Everything that's closer to the camera than the near plane gets clipped, and everything that's farther away than the far plane gets clipped. The following methods control these two clipping planes.
- (id)setNearDistance:(double)near farDistance:(double)far;Sets the distance to the near and far clipping planes. These distances must satisfy 0 < near < far. By default they are 1 and 100. This method issues OpenGL commands.
Returns the current distance to the near clipping plane.
Returns the current distance to the far clipping plane.
You'll also want to specify how large the field of vision is. For perspective projections, this is recorded as the vertical angle of the perspective frustum. For orthogonal projections, it's recorded as the distance, in world coordinates, between the top and the bottom of the view.
- (id)setPerspectiveAngle:(double)angle;Sets the full vertical angle of the perspective frustum, in radians. This angle must be greater than 0 and less than SMKPi; by default it is SMKPi / 6. This method issues OpenGL commands.
Returns the current perspective angle.
- (id)setOrthogonalField:(double)distance;
Sets the distance in world space between the top and bottom of the view. The distance must be positive; by default it is 1. This method issues OpenGL commands.
Returns the current height of the orthogonal field.
Finally we have a strange utility method that helps you switch between orthogonal and perspective projections with minimal visual dislocation.
- (id)switchProjectionAtDistance:(double)distance;Switches the projection style, automatically adjusting the camera so that objects at the specified distance appear at the same size in the new projection. The distance must be positive. When switching to orthogonal projection, this method adjusts the orthogonal field. When switching to perspective projection, it could simply adjust the perspective angle, but instead it chooses to move the camera, using the zoomByDistance: and updateScene methods of the next section. This method issues OpenGL commands.
When you create a camera, you'll want attach an SMKTransformation to describe its location and orientation. A nil transformation results in this situation: The world X-axis points right on the screen, the world Y-axis points up on the screen, and the world Z-axis points out of the screen.
- (id)setTransformation:(SMKTransformation *)transformation;Sets the transformation that describes the camera's location and orientation, retaining it.
Returns the embedded transformation.
The camera does not automatically notify OpenGL of changes to its transformation. After you alter the transformation manually or through the utility methods in this section, you'll want to invoke this method:
- (id)updateScene;Sets the OpenGL modelview transformation to reflect the current location and orientation of the camera. To be specific, this method loads the identity matrix and then applies the inverse of the transformation. This method issues OpenGL commands.
Next we have some simple methods to help you aim the camera. The first is similar to the OpenGL utility function gluLookAt.
- (id)lookAtPoint:(double *)target fromPoint:(double *)viewpoint withVertical:(double *)vertical;Adjusts the transformation so that the camera is at viewpoint, looking at target. The camera is rotated about the line of sight so that its top points in the direction of vertical, which must not be parallel with the line of sight. This method invokes makeVector:andVector:rotateToTarget:andTarget:.
- (id)lookAtPoint:(double *)target fromDistance:(double)distance;
Sets the camera's location so that it is looking at the target from the desired distance. The orientation of the camera is unchanged.
- (id)zoomByDistance:(double)distance;
Translates the camera forward by the indicated distance. If the distance is negative, then the camera is translated backward. The orientation of the camera is unchanged.
3D programs often present an interface in which the user rotates (or translates) the scene by dragging the mouse around the view. You can accomplish this easily by using the change in X and Y mouse coordinates to adjust two angles in the camera's rotation (or two components of its translation). SMKCamera supports a slightly refined form of this manipulation, in which the adjustment is computed in a way that lets the mouse pointer maintain "friction" with the scene, as if the user has actually grabbed something and is pulling it around. In fact, SMKCamera supports two distinct schemes for such tactile mouse manipulation.
The first scheme consists of two methods. You invoke the first one on a mouse-down event and the second one on each subsequent mouse-moved event.
- (BOOL)setPointAtX:(double)x y:(double)y;Records the given 2D screen point for subsequent tracking, returning YES if and only if successful.
- (BOOL)makePointMoveToX:(double)x y:(double)y;
Adjusts the camera so that the stored point seems to move to the new point (x, y), returning YES if and only if successful. Under an orthogonal projection, the camera is translated in a direction perpedicular to the line of sight. Under a perspective projection, the camera is rotated, using both makeVector:rotateToTarget: and makeVector:andVector:rotateToTarget:andTarget:; makeVector:rotateToTarget: need not return success for this method to return success.
The second tactile manipulation scheme lets you translate the scene or rotate it about something other than the camera. For this, SMKCamera requires some knowledge of the scene's contents. In the setHandleAtX:y:withObject:callback: method below, it picks the scene (following Example 1 in the SMKTester documentation) to establish a point in world space, called the "handle". You call this method on a mouse-down event. Then, on subsequent mouse-moved events, you can make the camera rotate or translate the handle in a tactile way.
- (BOOL)setHandleAtX:(double)x y:(double)y withObject:(id)object callback:(SEL)callback;Establishes the handle corresponding to the 2D screen point (x, y), returning YES if and only if successful. In the process, the scene is drawn by object in the method corresponding to callback, which must take no arguments, avoid manipulation of the OpenGL name stack, and return id. This method issues OpenGL commands.
- (BOOL)makeHandleRotateToX:(double)x y:(double)y aboutCenter:(double *)center;
Rotates the camera about the world point center so that the handle is projected to the 2D screen point (x, y), returning YES if and only if successful. In the process, both makeVector:rotateToTarget: and makeVector:andVector:rotateToTarget:andTarget: are invoked on the transformation; makeVector:rotateToTarget: need not return success for this method to return success.
- (BOOL)makeHandleTranslateToX:(double)x y:(double)y;
Translates the camera in a direction perpendicular to the line of sight, so that the handle is projected to the 2D screen point (x, y), returning YES if and only if successful.
One can combine rotation and translation like this: On a mouse-moved event, first try to rotate; if that fails, then translate. For example, if the scene consists of a globe, and the center of rotation is at the center of the globe, then the resulting manipulation is extremely intuitive. The mouse spins the globe if it can, and pulls the globe around if it has to.
SMKCamera offers two methods that simulate the OpenGL viewing transformations. These are equivalent to SMKProjectPoint and SMKUnProjectPoint, but SMKCamera performs the projection/unprojection itself, without accessing OpenGL at all. Notice that screen coordinates here include the depth value, which is typically between 0 and 1.
- (BOOL)projectVector:(double *)point withResult:(double *)result;Computes the 3D point in screen coordinates corresponding to the given world point, returning YES if and only if successful. The input may alias the output.
- (BOOL)unProjectVector:(double *)point withResult:(double *)result;
Computes the world point corresponding to the given 3D screen point, returning YES if and only if successful. The input may alias the output.
A similar method computes the direction in world space from the camera to a given screen point.
- (BOOL)getDirection:(double *)vector atX:(double)x y:(double)y;Unprojects the 2D screen point (x, y) (or rather the 3D screen point (x, y, 0.5)) and computes the unit vector pointing from the camera to the unprojected point, returning YES if and only if successful.
SMKCamera provides a simple mechanism for antialiasing, or smoothing, an entire 3D scene. The multi-pass algorithm it uses is elegant and reliable, but slow. On each pass, the camera is jittered a bit, the scene is drawn into an auxiliary color buffer, and this buffer is blended into the OpenGL accumulation buffer. Once the final image is ready, it is returned from the accumulation buffer to the main buffer for display. There are several customizable parameters here, and it is also possible to interrupt the smoothing process between passes in order to respond to user actions promptly.
First, you set the number of passes and the severity of the jitter, using these methods.
- (id)setSmoothingPasses:(unsigned int)numPasses jitter:(double)jitter;Sets the number of passes and the severity of the jitter. Increasing the number of passes increases the smoothness of your final image but also increases the time taken; currently the number of passes is clamped between 1 and 5. Increasing the jitter makes the jittering more dramatic and the final image more "blurry"; typically a trivial jitter (that is, 1) works well. By default, both parameters are 1.
- (unsigned int)smoothingPasses;
Returns the number of smoothing passes, which is always between 1 and 5, inclusively.
Returns the jitter.
Next, you specify which buffers are to be used in the smoothing process. Unless you are playing tricks with OpenGL's various buffers, you will probably not need to use these methods at all.
- (id)setSmoothedBuffer:(GLenum)mainBuffer auxiliaryBuffer:(GLenum)auxBuffer;Sets the OpenGL color buffers to use while smoothing. auxBuffer is used to store temporary images before they are accumulated into the accumulation buffer; mainbuffer is the buffer into which the final, smoothed image is copied at the end of the smoothing process. To avoid errors, mainBuffer and auxBuffer should be distinct, and they should be different from any buffers used in the rendering method that is being smoothed. By default, mainBuffer is GL_FRONT and auxBuffer is GL_AUX0.
Returns the main, smoothed buffer.
Returns the auxiliary buffer used while smoothing.
Finally you are ready to antialias your scene.
- (id)smoothObject:(id)object withDrawCallback:(SEL)drawCallback stopCallback:(SEL)stopCallback;For the specified object, antialiases the scene drawn by the method corresponding to drawCallback, aborting after any pass if the method corresponding to stopCallback returns a non-nil value. Because of restrictions imposed by -[NSObject performSelector:], both methods must take no arguments and must return id or a pointer value compatible with id. If you do not want to use stopCallback, pass NULL as its value. This method issues OpenGL commands.
One last item deserves mention. In order for the smoothing mechanism to use the accumulation buffer, you must make an accumulation buffer available. You can do this in Interface Builder, in the Attributes panel of your NSOpenGLView. You will probably want it to have greater precision than your ordinary color buffers (for example, 16-16-16-16 ARGB rather than 8-8-8-8 ARGB). You should also be aware that the accumulation buffer is often not hardware accelerated, which can make this antialiasing feature useful only for offline rendering.