MULTIMEDIA

iPhone 3D Programming : Holodeck Sample (part 4) - Replacing Buttons with Orientation Sensors

4/2/2011 3:18:48 PM

4. Replacing Buttons with Orientation Sensors

The next step is carefully integrating support for the compass and accelerometer APIs. I say “carefully” because we’d like to provide a graceful runtime fallback if the device (or simulator) does not have a magnetometer or accelerometer.

We’ll be using the accelerometer to obtain the gravity vector, which in turn enables us to compute the phi angle (that’s “altitude” for you astronomers) but not the theta angle (azimuth). Conversely, the compass API can be used to compute theta but not phi. You’ll see how this works in the following sections.

4.1. Adding accelerometer support

Using the low-level accelerometer API directly is ill advised; the signal includes quite a bit of noise, and unless your app is somehow related to The Blair Witch Project, you probably don’t want your camera shaking around like a shivering chihuahua.

Discussing a robust and adaptive low-pass filter implementation is beyond the scope of this book, but thankfully Apple includes some example code for this. Search for the AccelerometerGraph sample on the iPhone developer site (http://developer.apple.com/iphone) and download it. Look inside for two key files, and copy them to your project folder: AccelerometerFilter.h and AccelerometerFilter.m.

After adding the filter code to your Xcode project, open up GLView.h, and add the three code snippets that are highlighted in bold in Example 10.
Example 10. Adding accelerometer support to GLView.h
#import "Interfaces.hpp"
#import "AccelerometerFilter.h"
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@interface GLView : UIView <UIAccelerometerDelegate> {
@private
IRenderingEngine* m_renderingEngine;
IResourceManager* m_resourceManager;
EAGLContext* m_context;
AccelerometerFilter* m_filter;
...
}

- (void) drawView: (CADisplayLink*) displayLink;

@end

Next, open GLView.mm, and add the lines shown in bold in Example 11. You might grimace at the sight of the #if block, but it’s a necessary evil because the iPhone Simulator pretends to support the accelerometer APIs by sending the application fictitious values (without giving the user much control over those values). Since the fake accelerometer won’t do us much good, we turn it off when building for the simulator.


Note:

An Egyptian software company called vimov produces a compelling tool called iSimulate that can simulate the accelerometer and other device sensors. Check it out at http://www.vimov.com/isimulate.


Example 11. Adding accelerometer support to initWithFrame
- (id) initWithFrame: (CGRect) frame
{
m_paused = false;
m_theta = 0;
m_phi = 0;
m_velocity = vec2(0, 0);
m_visibleButtons = 0;

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);

#if TARGET_IPHONE_SIMULATOR
BOOL compassSupported = NO;
BOOL accelSupported = NO;
#else
BOOL compassSupported = NO; // (We'll add compass support shortly.)
BOOL accelSupported = YES;
#endif

if (compassSupported) {
NSLog(@"Compass is supported.");
} else {
NSLog(@"Compass is NOT supported.");
m_visibleButtons |= ButtonFlagsShowHorizontal;
}

if (accelSupported) {
NSLog(@"Accelerometer is supported.");
float updateFrequency = 60.0f;
m_filter =
[[LowpassFilter alloc] initWithSampleRate:updateFrequency
cutoffFrequency:5.0];
m_filter.adaptive = YES;

[[UIAccelerometer sharedAccelerometer]
setUpdateInterval:1.0 / updateFrequency];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
} else {
NSLog(@"Accelerometer is NOT supported.");
m_visibleButtons |= ButtonFlagsShowVertical;
}

[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;
}



Since GLView sets itself as the accelerometer delegate, it needs to implement a response handler. See Example 12.

Example 12. Accelerometer response handler
- (void) accelerometer: (UIAccelerometer*) accelerometer
didAccelerate: (UIAcceleration*) acceleration
{
[m_filter addAcceleration:acceleration];
float x = m_filter.x;
float z = m_filter.z;
m_phi = atan2(z, -x) * 180.0f / Pi;
}

You might not be familiar with the atan2 function, which takes the arctangent of the its first argument divided by the its second argument (see Phi as a function of acceleration). Why not use the plain old single-argument atan function and do the division yourself? You don’t because atan2 is smarter; it uses the signs of its arguments to determine which quadrant the angle is in. Plus, it allows the second argument to be zero without throwing a divide-by-zero exception.


Note:

An even more rarely encountered math function is hypot. When used together, atan2 and hypot can convert any 2D Cartesian coordinate into a polar coordinate.


Phi as a function of acceleration


Phi as a function of acceleration shows how we compute phi from the accelerometer’s input values. To understand it, you first need to realize that we’re using the accelerometer as a way of measuring the direction of gravity. It’s a common misconception that the accelerometer measures speed, but you know better by now! The accelerometer API returns a 3D acceleration vector according to the axes depicted in Figure 4.

Figure 4. Accelerometer axes in landscape mode


When you hold the device in landscape mode, there’s no gravity along the y-axis (assuming you’re not slothfully laying on the sofa and turned to one side). So, the gravity vector is composed of X and Z only—see Figure 5.

Figure 5. Computing phi from acceleration


4.2. Adding compass support

The direction of gravity can’t tell you which direction you’re facing; that’s where the compass support in third-generation devices comes in. To begin, open GLView.h, and add the bold lines in Example 13.

Example 13. Adding compass support to GLView.h
#import "Interfaces.hpp"
#import "AccelerometerFilter.h"
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <CoreLocation/CoreLocation.h>

@interface GLView : UIView <CLLocationManagerDelegate,
UIAccelerometerDelegate> {
@private
IRenderingEngine* m_renderingEngine;
IResourceManager* m_resourceManager;
EAGLContext* m_context;
CLLocationManager* m_locationManager;
AccelerometerFilter* m_filter;
...
}

- (void) drawView: (CADisplayLink*) displayLink;

@end

The Core Location API is an umbrella for both GPS and compass functionality, but we’ll be using only the compass functionality in our demo. Next we need to create an instance of CLLocationManger somewhere in GLview.mm; see Example 14.

Example 14. Adding compass support to initWithFrame
- (id) initWithFrame: (CGRect) frame
{
...

if (self = [super initWithFrame:frame]) {

...

m_locationManager = [[CLLocationManager alloc] init];

#if TARGET_IPHONE_SIMULATOR
BOOL compassSupported = NO;
BOOL accelSupported = NO;
#else
BOOL compassSupported = m_locationManager.headingAvailable;
BOOL accelSupported = YES;
#endif

if (compassSupported) {
NSLog(@"Compass is supported.");
m_locationManager.headingFilter = kCLHeadingFilterNone;
m_locationManager.delegate = self;
[m_locationManager startUpdatingHeading];
} else {
NSLog(@"Compass is NOT supported.");
m_visibleButtons |= ButtonFlagsShowHorizontal;
}

...
}
return self;
}



Similar to how it handles the accelerometer feedback, GLView sets itself as the compass delegate, so it needs to implement a response handler. See Example 6-31. Unlike the accelerometer, any noise in the compass reading is already eliminated, so there’s no need for handling the low-pass filter yourself. The compass API is embarrassingly simple; it simply returns an angle in degrees, where 0 is north, 90 is east, and so on. See Example 15 for the compass response handler.

Example 15. Compass response handler
- (void) locationManager: (CLLocationManager*) manager
didUpdateHeading: (CLHeading*) heading
{
// Use magneticHeading rather than trueHeading to avoid usage of GPS:
CLLocationDirection degrees = heading.magneticHeading;
m_theta = (float) -degrees;
}

The only decision you have to make when writing a compass handler is whether to use magneticHeading or trueHeading. The former returns magnetic north, which isn’t quite the same as geographic north. To determine the true direction of the geographic north pole, the device needs to know where it’s located on the planet, which requires usage of the GPS. Since our app is looking around a virtual world, it doesn’t matter which heading to use. I chose to use magneticHeading because it allows us to avoid enabling GPS updates in the location manager object. This simplifies the code and may even improve power consumption.
Other  
  •  Building LOB Applications : Printing in a Silverlight LOB Application
  •  Building LOB Applications : Data Validation through Data Annotation
  •  Building LOB Applications : Implementing CRUD Operations in RIA Services
  •  Microsoft XNA Game Studio 3.0 : Displaying Images - Resources and Content (part 2) - Adding Resources to a Project
  •  Microsoft XNA Game Studio 3.0 : Displaying Images - Resources and Content (part 1)
  •  iPhone 3D Programming : Blending and Augmented Reality - Rendering Anti-Aliased Lines with Textures
  •  Programming with DirectX : Game Math - Bounding Geometry (part 2) - Bounding Spheres & Bounding Hierarchies
  •  Programming with DirectX : Game Math - Bounding Geometry (part 1) - Bounding Boxes
  •  Programming with DirectX : Game Math - Matrices
  •  iPhone 3D Programming : Anti-Aliasing Tricks with Offscreen FBOs (part 2) - Jittering
  •  iPhone 3D Programming : Anti-Aliasing Tricks with Offscreen FBOs (part 1) - A Super Simple Sample App for Supersampling
  •  Building LOB Applications : Navigating RIA LOB Data
  •  Building LOB Applications : Databinding in XAML
  •  Microsoft XNA Game Studio 3.0 : Program Bugs
  •  Microsoft XNA Game Studio 3.0 : Getting Player Input - Adding Vibration
  •  Microsoft XNA Game Studio 3.0 : Getting Player Input - Using the Keyboard
  •  iPhone 3D Programming : Blending and Augmented Reality - Stencil Alternatives for Older iPhones
  •  iPhone 3D Programming : Blending and Augmented Reality - Poor Man’s Reflection with the Stencil Buffer
  •  Microsoft XNA Game Studio 3.0 : Getting Player Input - Reading a Gamepad
  •  iPhone 3D Programming : Blending and Augmented Reality - Shifting Texture Color with Per-Vertex Color
  •  
    Most View
    Cooler Master Hyper T4 - A Step Up In Budget Cooling
    Cooler Master Seidon 120M - Won’t Break The Bank Or Any Records
    AMD Trinity the Chip to Take down Core i3? (Part 3)
    Tablet Group Test – December 2012 (Part 3) : Kurio 7, Samsung Galaxy Tab 2 10.1, Toshiba AT300
    Modernize Your Business Website
    Jot Touch – The Magic Sketchpad
    Microsoft Dynamics Sure Step 2010 : A repeatable process for the sales teams (part 2)
    Sharepoint 2010 : Business Connectivity Services Deployment Types (part 1) - Code-Based Solutions
    Buying Guide – Router (Part 3) - Buffalo AirStation 1750, Western Digital My Net N900 Central, Fractal Design Define R4
    Home Cinema Amplifiers Awards – Q1 2013
    Top 10
    Kingston Wi - Drive 128GB: Simple To Get Started
    Seagate Wireless Plus 1 TB - Streaming Videos To Various Devices
    Seagate Wireless Plus 1TB - Seagate's Second Wireless External Hard Drive
    Western Digital My Passport 2TB - The Ideal Companion For Anyone
    Lenovo IdeaTab A2109 - A Typical Midrange Android Tablet
    Secret Tips For Your Kindle Fire
    The Best Experience With Windows 8 Tablets And Hybrids (Part 2)
    The Best Experience With Windows 8 Tablets And Hybrids (Part 1)
    Give Your Browser A Health Check
    New Ways To Block Irritating Ads…