3. Handling the Heads-Up DisplayMost applications that need to render a HUD
take the following approach when rendering a single frame of
animation: Set up the model-view and projection
matrices for the 3D scene. Disable depth testing, and enable
blending. Set up the model-view and projection
matrices for 2D rendering.
Warning: Always remember to completely reset your
transforms at the beginning of the render routine; otherwise, you’ll
apply transformations that are left over from the previous frame. For
example, calling glFrustum alone simply multiplies
the current matrix, so you might need to issue a glLoadIdentity immediately before
calling glFrustum.
Let’s go ahead and modify the
Render method to render buttons; replace the ellipses
in Example 5 with the code in Example 7. Example 7. Adding buttons to HolodeckglEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); glBindTexture(GL_TEXTURE_2D, m_textures.Button); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrthof(-160, 160, -240, 240, 0, 1);
if (buttons & ButtonFlagsShowHorizontal) { glMatrixMode(GL_MODELVIEW); glTranslatef(200, 0, 0); SetButtonAlpha(buttons, ButtonFlagsPressingLeft); RenderDrawable(m_drawables.Quad); glTranslatef(-400, 0, 0); glMatrixMode(GL_TEXTURE); glRotatef(180, 0, 0, 1); SetButtonAlpha(buttons, ButtonFlagsPressingRight); RenderDrawable(m_drawables.Quad); glRotatef(-180, 0, 0, 1); glMatrixMode(GL_MODELVIEW); glTranslatef(200, 0, 0); }
if (buttons & ButtonFlagsShowVertical) { glMatrixMode(GL_MODELVIEW); glTranslatef(0, 125, 0); glMatrixMode(GL_TEXTURE); glRotatef(90, 0, 0, 1); SetButtonAlpha(buttons, ButtonFlagsPressingUp); RenderDrawable(m_drawables.Quad); glMatrixMode(GL_MODELVIEW); glTranslatef(0, -250, 0); glMatrixMode(GL_TEXTURE); glRotatef(180, 0, 0, 1); SetButtonAlpha(buttons, ButtonFlagsPressingDown); RenderDrawable(m_drawables.Quad); glRotatef(90, 0, 0, 1); glMatrixMode(GL_MODELVIEW); glTranslatef(0, 125, 0); }
glColor4f(1, 1, 1, 1); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND);
|
Note that Example 7
contains quite a few transform operations; while this is fine for
teaching purposes, in a production environment I recommend including all
four buttons in a single VBO. You’d still need four separate draw calls,
however, since the currently pressed button has a unique alpha
value. In fact, making this optimization would be an
interesting project: create a single VBO that contains all four
pretransformed buttons, and then render it with four separate draw
calls. Don’t forget that the second argument to
glDrawArrays can be nonzero! The SetButtonAlpha method
sets alpha to one if the button is being pressed; otherwise, it makes
the button semitransparent: void RenderingEngine::SetButtonAlpha(ButtonMask buttonFlags, ButtonFlags flag) const { float alpha = (buttonFlags & flag) ? 1.0 : 0.75; glColor4f(1, 1, 1, alpha); }
Next let’s go over the code in
GLView.mm that detects button presses and maintains
the azimuth/altitude angles. See Example 8 for
the GLView class declaration and Example 9 for the interesting potions of the class
implementation. Example 8. GLView.h for Holodeck#import "Interfaces.hpp" #import <UIKit/UIKit.h> #import <QuartzCore/QuartzCore.h> #import <CoreLocation/CoreLocation.h>
@interface GLView : UIView { @private IRenderingEngine* m_renderingEngine; IResourceManager* m_resourceManager; EAGLContext* m_context; bool m_paused; float m_theta; float m_phi; vec2 m_velocity; ButtonMask m_visibleButtons; float m_timestamp; }
- (void) drawView: (CADisplayLink*) displayLink;
@end
|
Example 9. GLView.mm for Holodeck...
- (id) initWithFrame: (CGRect) frame { m_paused = false; m_theta = 0; m_phi = 0; m_velocity = vec2(0, 0); m_visibleButtons = ButtonFlagsShowHorizontal | ButtonFlagsShowVertical; if (self = [super initWithFrame:frame]) { CAEAGLLayer* eaglLayer = (CAEAGLLayer*) self.layer; eaglLayer.opaque = YES;
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES1; m_context = [[EAGLContext alloc] initWithAPI:api]; if (!m_context || ![EAGLContext setCurrentContext:m_context]) { [self release]; return nil; } m_resourceManager = CreateResourceManager();
NSLog(@"Using OpenGL ES 1.1"); m_renderingEngine = CreateRenderingEngine(m_resourceManager);
[m_context renderbufferStorage:GL_RENDERBUFFER fromDrawable: eaglLayer]; m_timestamp = CACurrentMediaTime();
m_renderingEngine->Initialize(); [self drawView:nil]; CADisplayLink* displayLink; displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView:)]; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; } return self; }
- (void) drawView: (CADisplayLink*) displayLink { if (m_paused) return; if (displayLink != nil) { const float speed = 30; float elapsedSeconds = displayLink.timestamp - m_timestamp; m_timestamp = displayLink.timestamp; m_theta -= speed * elapsedSeconds * m_velocity.x; m_phi += speed * elapsedSeconds * m_velocity.y; }
ButtonMask buttonFlags = m_visibleButtons; if (m_velocity.x < 0) buttonFlags |= ButtonFlagsPressingLeft; if (m_velocity.x > 0) buttonFlags |= ButtonFlagsPressingRight; if (m_velocity.y < 0) buttonFlags |= ButtonFlagsPressingUp; if (m_velocity.y > 0) buttonFlags |= ButtonFlagsPressingDown; m_renderingEngine->Render(m_theta, m_phi, buttonFlags); [m_context presentRenderbuffer:GL_RENDERBUFFER]; }
bool buttonHit(CGPoint location, int x, int y) { float extent = 32; return (location.x > x - extent && location.x < x + extent && location.y > y - extent && location.y < y + extent); }
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event { UITouch* touch = [touches anyObject]; CGPoint location = [touch locationInView: self]; float delta = 1;
if (m_visibleButtons & ButtonFlagsShowVertical) { if (buttonHit(location, 35, 240)) m_velocity.y = -delta; else if (buttonHit(location, 285, 240)) m_velocity.y = delta; } if (m_visibleButtons & ButtonFlagsShowHorizontal) { if (buttonHit(location, 160, 40)) m_velocity.x = -delta; else if (buttonHit(location, 160, 440)) m_velocity.x = delta; } }
- (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event { m_velocity = vec2(0, 0); }
|
At this point, you now have a complete app
that lets you look around inside a (rather boring) virtual world, but
it’s still a far cry from augmented reality!
|