views:

1800

answers:

4

I have a camera structure that holds location, up, and direction vectors. I'd like to update these vectors based on the gravity vector I get from the iPhone's accelerometer. The effect I'm going for is: when the top of the phone is tilted away from you, the camera looks towards the ground. In other words, the scene/geometry follows the orientation of the gravity vector while the camera follows the orientation of the phone itself.

I thought I could multiply the matrix I built from the camera vectors by the one built from the gravity vector and then just pull out the new up and direction vectors, but I don't think I fully understand the process as I can't get it to work. I'd greatly appreciate any help. Thanks!

+1  A: 

You would simply need to update the direction in which your camera is looking. You don't have to change the world matrix, the openGL "gluLookAt()" method does that for you automatically behind the scenes.

If you have a camera class setup, simply create a function to set the camera's up direction vector which you will need to obtain calculate based on a float/double value (i assume) from the iPhone's compass. When your camera update's it's lookAt() position it should change the camera to look in the correct location.

This is not that much different to what you do when you rotate your camera in an FPS based game. The difference is that you want to rotate the camera along the X-Axis instead of along the Y-Axis.

Take a look at how a camera class performs rotation for moving the camera left or right with the keyboard, then modify it to work using your compass direction values.

Here is some C++ code I wrote which may give you an insight as to how your camera class should be working:

/* This reshapes the camera using the perspective projection */
void Camera::ReshapePerspectiveForPicking( void )
{   
    glMatrixMode(GL_PROJECTION);

    // Sets the clipping volume
    gluPerspective( m_FieldOfView, (float)m_width/(float)m_height, m_zNear, m_zFar );

    gluLookAt( camPos.x, camPos.y, camPos.z, 
      camPos.x + camView.x, camPos.y + camView.y, camPos.z + camView.z,
      0.0f, 1.0f, 0.0f );

    glMatrixMode( GL_MODELVIEW );
}

Take note to the line above (0.0f, 1.0f, 0.0f). This is the UP direction vector. It was static for my game because the camera never needed to look down. You would simply need to change this vector by created a new up vector on the compass orientation.

The method below was simply an alternative method we needed to sometimes update the camera by passing it a special vector. You can probably ignore it, I just included it so you could learn from it.

   /* This updates the camera to look at the changed camera position. This uses a passed in camPosition and camView GameMath::Vector */
    void Camera::Update( GameMath::Vector camPos, GameMath::Vector camView )
    {
        glMatrixMode( GL_PROJECTION );
        gluLookAt( camPos.x, camPos.y, camPos.z, 
          camPos.x + camView.x, camPos.y + camView.y, camPos.z + camView.z,
          0.0f, 1.0f,0.0f );
    }

Here is my method for rotating the camera along the Y-Axis (Remember you want to rotate along the X-Axis) --I would rewrite this method now because it's kinda dodgey (I wrote it years ago) but it's enough to show you how it can be done.

void Camera::Rotate( void )
{
    if ( m_rotateCamera == true )
    {
     // Keep the radians within 2 pi, roughly
     float minimumRadiansRotate = 0.00;
     float maximumRadiansRotate = 6.2831853072;
     m_YRotateAngle = GameMath::Wrap( m_YRotateAngle, minimumRadiansRotate, maximumRadiansRotate );

     m_YRotateAngle += m_rotateSpeed * m_rotateDirection; // Add to the camera's current angle value
     camView.x = sin( m_YRotateAngle );
     camView.z = -cos( m_YRotateAngle );
    }
}

It's a bit difficult to provide you with a specific piece of code to do what you want to do because your camera class is probably different to mine, although this should get you to understand what needs to be done.

The CoreLocation framework contains the bits of code you will need to read values from the compass incase you haven't coded that part as yet.

Good luck.

Brock Woolf
This would work for the newer iPhone that has a built in compass (unless I misunderstand what you're explaining), but I'm only working with an accelerometer so all I have at any given moment is the new up vector. Also, the new up vector is not guaranteed to be perpendicular to the camera's x-axis, so how do I determine the new direction of the camera?
Bishop
+1  A: 

I think that Apple's sample GLGravity application does exactly what you want, unless I'm reading your request wrong.

Brad Larson
Yes, it is very similar to what I'm trying to achieve. That's why I started my research with it. However, getting this effect while storing the orientation vectors for later use is where I'm finding the difficulty. Thanks!
Bishop
A: 

i gotta start by sayin, i'm very new to this stuff, so be wary, but...

i looked at brock's code above and his explanation and i think i came up with a camera rotating around both the x and the y axis. actually, my camera rotates around the z too, but i think that is a different story (i just feed filtered values from the x & y accelerometer values directly to the "up" vector to create the illusion that my objects are affected by real gravity... works pretty good).

so... here is what i came up with:

float lx = sin(DEGREES_TO_RADIANS(horzAngle));
float ly = sin(DEGREES_TO_RADIANS(vertAngle));
float lz = -cos(DEGREES_TO_RADIANS(horzAngle));

float x;
float y;
float z;

    // get the default camera eye for the object
    // this will center the object on the screen
[sprite.camera restore];
[sprite.camera eyeX:&x eyeY:&y eyeZ:&z];

// add distance calcs
x = x + (-1 * sprite.distance)*(lx);
z = z + (-1 * sprite.distance)*(-1);

[sprite.camera setEyeX:x eyeY:y eyeZ:z];
[sprite.camera setCenterX:x + lx centerY:y + ly centerZ:z + lz];

this is using cocos2d library (very easy to use)... this wraps the camera stuff so that it'll end up calling gluLookAt like this:

 gluLookAt( eyeX, eyeY, eyeZ,
   centerX, centerY, centerZ,
   upX, upY, upZ
   );

like i said, i'm new to this, so this might not look right, but it seems to... i intend to add the accelerometer to control the vertAngle as you are describing, so when i get the accel code wired up, i'll try and remember to post that here.

also, if i'm missing something that someone else can add some light to, i'd love to hear it.

thanks,

john

john ellis
A: 

What you really want is a new projection matrix based on acceleration values. Oolong provides this crazy math to do that (not mine).

/*
Oolong Engine for the iPhone / iPod touch
Copyright (c) 2007-2008 Wolfgang Engel  http://code.google.com/p/oolongengine/

This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, 
including commercial applications, and to alter it and redistribute it freely, 
subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/

#import "Accelerometer.h"

#define FILTERINGFACTOR 0.1

@implementation Accel

- (void) SetupAccelerometer: (float) AcclerometerFrequency
{
        //Configure and start accelerometer
        [[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / AcclerometerFrequency)];
        [[UIAccelerometer sharedAccelerometer] setDelegate:self];
}



- (void) accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)Acceleration
{
        // use a basic low-pass filter to only keep the gravity in the accelerometer values
        _accelerometer[0] = Acceleration.x * FILTERINGFACTOR + _accelerometer[0] * (1.0 - FILTERINGFACTOR);
        _accelerometer[1] = Acceleration.y * FILTERINGFACTOR + _accelerometer[1] * (1.0 - FILTERINGFACTOR);
        _accelerometer[2] = Acceleration.z * FILTERINGFACTOR + _accelerometer[2] * (1.0 - FILTERINGFACTOR);
}

- (void) GetAccelerometerMatrix:(GLfloat *) matrix
{

        GLfloat length = sqrtf(_accelerometer[0] * _accelerometer[0] + _accelerometer[1] * _accelerometer[1] + _accelerometer[2] * _accelerometer[2]);

        //Clear matrix to be used to rotate from the current referential to one based on the gravity vector
        bzero(matrix, sizeof(matrix));
        matrix[15] = 1.0f;
        //matrix[3][3] = 1.0;

        //Setup first matrix column as gravity vector
        matrix[0] = _accelerometer[0] / length;
        matrix[1] = _accelerometer[1] / length;
        matrix[2] = _accelerometer[2] / length;

        //Setup second matrix column as an arbitrary vector in the plane perpendicular to the gravity vector {Gx, Gy, Gz} defined by by the equation "Gx * x + Gy * y + Gz * z = 0" in which we arbitrarily set x=0 and y=1
        matrix[4] = 0.0;
        matrix[5] = 1.0;
        matrix[6] = -_accelerometer[1] / _accelerometer[2];
        length = sqrtf(matrix[4] * matrix[4] + matrix[5] * matrix[5] + matrix[6] * matrix[6]);
        matrix[4] /= length;
        matrix[5] /= length;
        matrix[6] /= length;

        //Setup third matrix column as the cross product of the first two
        matrix[8] = matrix[1] * matrix[6] - matrix[2] * matrix[5];
        matrix[9] = matrix[4] * matrix[2] - matrix[6] * matrix[0];
        matrix[10] = matrix[0] * matrix[5] - matrix[1] * matrix[4];
}

- (void) GetAccelerometerVector:(double *) AccelValue;
{
        // the vector is read-only, so make a copy of it and do not expose a pointer to it
        AccelValue[0] = (double)_accelerometer[0];
        AccelValue[1] = (double)_accelerometer[1];
        AccelValue[2] = (double)_accelerometer[2];
}

@end
slf