Creating a Movable Interface - Quaternions

Started by
-1 comments, last by mattwood2855 13 years, 1 month ago
[font="Arial"][color=#000000][font=Arial][size=2] When developing the 3D world for our games or modeling software, it’s important that we used a very OOP approach in abstraction. This makes our code robust, re-usable, and more importantly, shorter - saving us from spending so much time typing. So how do we do this? [/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] It is often common practice to start with developing a Camera class so that we can start moving around our 3D environment. I will be using OpenGL here. Once we setup a Device Context using OpenGL/DirectX/etc we are ready to begin. If you don’t know how to set up a basic environment in one of these APIs you will want to head to another tutorial on doing this. But assuming you have made a cube that sits in front of you ( the “Hello World” of the graphics world ), the next task to achieve is developing a MOVABLE INTERFACE. Rather than developing a camera class that contains all the math, it will be more beneficial for us to develop an IMoveable interface and derive all our world objects (ie camera) from this. [/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] For this interface to work we will need 2 base classes to start:[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] - a Vector3D class for handling vertices and vectors[/font]
[color=#000000][font=Arial][size=2] - a Quaternion class for storing a vector with a rotation[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] Our Vector3D class is simply 3 floats that hold x, y, and z positions. Our Quaternion class contains a single vector and the number of degrees to rotate around that vector. I will not get too much into these classes as Vectors are extremely simple and Quaternions are extremely difficult. Despite how involved the math is for Quaternions, however, they are fairly easy to implement if you remember a few rules/tricks which i will explain later. Here are the headers for my Vector3D and Quaternion classes so that you can see what functions we will be using:[/font]
[color=#000000][font=Arial][size=2][/font]
[size=2]

[color=#000000][font=Arial][size=2]class Vector3D[/font]
[color=#000000][font=Arial][size=2]{[/font]
[color=#000000][font=Arial][size=2]public:[/font]
[color=#000000][font=Arial][size=2] float x, y, z, u, v, w;[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] Vector3D(void);[/font]
[color=#000000][font=Arial][size=2]Vector3D(float _x, float _y, float _z, float _w = 1.0f);[/font]

[color=#000000][font=Arial][size=2]~Vector3D(void);[/font]

[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]float length(void);[/font]

[color=#000000][font=Arial][size=2]float normalize(void);[/font]

[color=#000000][font=Arial][size=2]};[/font]
[size=2][/quote]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]...and the quaternion header....[/font]
[color=#000000][font=Arial][size=2][/font]
[size=2]

[color=#000000][font=Arial][size=2]class Quaternion [/font]
[color=#000000][font=Arial][size=2]{[/font]
[color=#000000][font=Arial][size=2]public:[/font]
[color=#000000][font=Arial][size=2] Quaternion();[/font]
[color=#000000][font=Arial][size=2] virtual ~Quaternion();[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] void CreateMatrix(float *pMatrix);[/font]
[color=#000000][font=Arial][size=2] void CreateFromAxisAngle(float x, float y, float z, float degrees);[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] Quaternion operator *(Quaternion q);[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]private:[/font]
[color=#000000][font=Arial][size=2] float _w;[/font]
[color=#000000][font=Arial][size=2] float _x;[/font]
[color=#000000][font=Arial][size=2] float _y;[/font]
[color=#000000][font=Arial][size=2] float _z;[/font]
[color=#000000][font=Arial][size=2]};[/font]
[size=2][/quote]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] So now that we have an overview of our starting tools lets make our interface! Our goal is to make a moveable object in 3D space that we can position, rotate, and accelerate. We will create accelerations based on the objects current orientation and according to the world’s axes. This allows us to move according to the object itself, or with respects to the world.[/font]
[color=#000000][font=Arial][size=2][/font]
[size=2]

[color=#000000][font=Arial][size=2]#ifndef IMOVABLE_H[/font]
[color=#000000][font=Arial][size=2]#define IMOVABLE_H[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]#include "Vector3D.h"[/font]
[color=#000000][font=Arial][size=2]#include "Quaternion.h"[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]class IMovable[/font]
[color=#000000][font=Arial][size=2]{[/font]
[color=#000000][font=Arial][size=2]public:[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] void moveTo( float x, float y, float z );[/font]
[color=#000000][font=Arial][size=2] void moveTo(Vector3D &v); [/font]
[color=#000000][font=Arial][size=2] void moveTo(Vector3D *v); // Warps the object to a given vertex[/font]
[color=#000000][font=Arial][size=2] void setX(double x); // Set the x value of the position[/font]
[color=#000000][font=Arial][size=2] void setY(double y); // Set the y value of the position[/font]
[color=#000000][font=Arial][size=2] void setZ(double z); // Set the z value of the position[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] void accelerateForward( float scaleFactor = 1 ); // Accelerates the object FORWARD at current _acceleration speed[/font]
[color=#000000][font=Arial][size=2] void accelerateBack( float scaleFactor = 1 ); // Accelerates the object BACK at current _acceleration speed[/font]
[color=#000000][font=Arial][size=2] void accelerateLeft( float scaleFactor = 1 ); // Accelerates the object LEFT at current _acceleration speed[/font]
[color=#000000][font=Arial][size=2] void accelerateRight( float scaleFactor = 1 ); // Accelerates the object RIGHT at current _acceleration speed[/font]
[color=#000000][font=Arial][size=2] void accelerateUp( float scaleFactor = 1 ); // Accelerates the object UP at current _acceleration speed[/font]
[color=#000000][font=Arial][size=2] void accelerateDown( float scaleFactor = 1 ); // Accelerates the object DOWN at current _acceleration speed[/font]
[color=#000000][font=Arial][size=2] void accelerateXAxis( float scaleFactor = 1 ); // Accelerates the object on the world's x axis: true = (+)positive direction[/font]
[color=#000000][font=Arial][size=2] void accelerateYAxis( float scaleFactor = 1 ); // Accelerates the object on the world's y axis: true = (+)positive direction[/font]
[color=#000000][font=Arial][size=2] void accelerateZAxis( float scaleFactor = 1 ); // Accelerates the object on the world's z axis: true = (+)positive direction[/font]
[color=#000000][font=Arial][size=2] [/font]
[color=#000000][font=Arial][size=2] void zeroAxisVelocity( bool x = true, bool y = true, bool z = true ); // Zero's the object's velocity on the world's axis[/font]
[color=#000000][font=Arial][size=2] void zeroLocalVelocity( bool x = true, bool y = true, bool z = true ); // Zero's the objects velocity on the local axis[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] void changePitch(float degrees); // Adjust the pitch n degrees (rotation about the x axis)[/font]
[color=#000000][font=Arial][size=2] void changeHeading(float degrees); // Adjust the pitch n degrees (rotation about the y axis) [/font]
[color=#000000][font=Arial][size=2] void setPitch( float degrees ); // Set the pitch to given degrees[/font]
[color=#000000][font=Arial][size=2] void setHeading( float degrees ); // Set the heading to given degrees[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] void setAcceleration( float acceleration ); // Sets the objects acceleration speed[/font]
[color=#000000][font=Arial][size=2] void setFriction( float friction ); // Sets the objects personal friction[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] virtual void update();[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] Vector3D* getPosition(); // Returns the current X, Y, Z coordinates of the object[/font]
[color=#000000][font=Arial][size=2] Vector3D* getLookAngles(); // Returns a vector with degrees rotated on each axis[/font]
[color=#000000][font=Arial][size=2] Vector3D* getUpVector(); // Returns the upVector of the object[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]protected:[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] void calculateQuats();[/font]
[color=#000000][font=Arial][size=2] [/font]
[color=#000000][font=Arial][size=2] Vector3D _pos, _lookVector, _upVector, [/font]
[color=#000000][font=Arial][size=2] _localVelocity, _axisVelocity;[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] Quaternion _qHeading, _qPitch; [/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] float _pitch, _heading, _roll,[/font]
[color=#000000][font=Arial][size=2] _acceleration, _friction;[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] bool _xLocked, _yLocked, _zLocked, [/font]
[color=#000000][font=Arial][size=2] _xViewLocked, _yViewLocked, _zViewLocked,[/font]
[color=#000000][font=Arial][size=2] _moving;[/font]
[color=#000000][font=Arial][size=2]};[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]#endif[/font]
[color=#000000][font=Arial][size=2][/font]
[size=2][/quote]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]**One thing that is important to mention up front is that, in the IMoveable class, I use the function glMultMatrix() to calculate. This makes IMoveable dependent on OpenGL. While this is usually bad for design, it is much more of an inconvenience to have to pass the current matrix to the object every time. So if you are using DirectX you can substitute glMultMatrix with ID3DXMatrixStack::MultMatrix() instead - or your API’s equivelant. Also the z values are flipped to account for OpenGL’s flipped z axis.[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] So where do we start? The first thing to do is create all your get/set functions. However we I wont cover this since it is fairly straight forward and a boring read. The meat and potatoes of this interface is the actual movement. So lets look at our update function first. While this seems a little backwards, it will help in understanding how it all fits together. [/font]
[color=#000000][font=Arial][size=2][/font]
[size=2]

[color=#000000][font=Arial][size=2]void IMoveable::update()[/font]
[color=#000000][font=Arial][size=2]{[/font]
[color=#000000][font=Arial][size=2] _pos.x += _localVelocity.x;[/font]
[color=#000000][font=Arial][size=2] _pos.y += _localVelocity.y;[/font]
[color=#000000][font=Arial][size=2] _pos.z += _localVelocity.z;[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] _pos.x += _axisVelocity.x;[/font]
[color=#000000][font=Arial][size=2] _pos.y += _axisVelocity.y;[/font]
[color=#000000][font=Arial][size=2] _pos.z += _axisVelocity.z;[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] _localVelocity *= _friction;[/font]
[color=#000000][font=Arial][size=2] _axisVelocity *= _friction;[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]if( _localVelocity.x + _localVelocity.y + _localVelocity.z > 0 )[/font]

[color=#000000][font=Arial][size=2] _moving = true;[/font]
[color=#000000][font=Arial][size=2] else[/font]
[color=#000000][font=Arial][size=2] _moving = false;[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]}[/font]
[size=2][/quote]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] Fairly straight forward: Every update(), we add the Velocity vector to the objects coordinates and then multiply the velocities by the objects _friction ( where 0 < _friction < 1 ). This will cause our object to move in the directions we place into the _localVelocity and _axisVelocity vectors and then multiplies those velocities by a number that will go down slowly, giving a smooth or gliding effect.[/font]
[color=#000000][font=Arial][size=2] So now we have to come up with a way to put numbers into those velocity variables. Since _axisVelocity is easiest we’ll look at it first:[/font]
[color=#000000][font=Arial][size=2][/font]
[size=2]

[color=#000000][font=Arial][size=2]void IMovable::accelerateXAxis( float scaleFactor )[/font]
[color=#000000][font=Arial][size=2]{[/font]
[color=#000000][font=Arial][size=2] _axisVelocity.x += _acceleration * scaleFactor; [/font]
[color=#000000][font=Arial][size=2]}[/font]
[size=2][/quote]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]Since the world axis wont ever change, we just pass it a scale factor. The declaration in the header will default with 1 but we can call with -1 to get the opposite direction. Pretty simple ey? And this repeats for all three axes respectively.[/font]

[color=#000000][font=Arial][size=2]Now for the quaternions! A quaternion is an array that holds a vector and the rotation around this vector. When multiplying our heading and pitch quaternions together we create a matrix that contains our up, forward, and right vectors aaaaand they are already normalized! Order of operations:[/font]

[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]1. Create 2 Quaternions from the pitch and heading[/font]

[color=#000000][font=Arial][size=2]2. Multiply the 2 together (pitch x heading)[/font]

[color=#000000][font=Arial][size=2]3. Create a 4x4 homogeneous matrix[/font]

[color=#000000][font=Arial][size=2]4. Multiply the newly created matrix by the current GL matrix[/font]

[color=#000000][font=Arial][size=2]5. Create a matrix from the pitch quaternion[/font]

[color=#000000][font=Arial][size=2]6. Extract our vector’s y value[/font]

[color=#000000][font=Arial][size=2]7. Multiply the 2 quaternions in the opposite order (heading x pitch)[/font]

[color=#000000][font=Arial][size=2]6. Extract our vector’s x and z values from the Matrix[/font]

[color=#000000][font=Arial][size=2]7. Add/subtract this from our velocity to create movement in our object[/font]

[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2][/font]
[size=2]

[color=#000000][font=Arial][size=2]void IMovable::accelerateForward( float scaleFactor )[/font]
[color=#000000][font=Arial][size=2]{[/font]
[color=#000000][font=Arial][size=2] float Matrix[16];[/font]
[color=#000000][font=Arial][size=2] Quaternion q;[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] calculateQuats(); // calculates Quaternions from our heading and pitch into _qHeading and _qPitch[/font]
[color=#000000][font=Arial][size=2] q = _qPitch * _qHeading; // Combine the 2 vectors and rotations - Order specific multiplication![/font]
[color=#000000][font=Arial][size=2] q.CreateMatrix(Matrix); // Store our new vector and its rotation[/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] glMultMatrixf(Matrix); // This is the same as glRotatef[/font]
[color=#000000][font=Arial][size=2] [/font]
[color=#000000][font=Arial][size=2] _qPitch.CreateMatrix(Matrix); // Create a new Matrix from _qPitch[/font]
[color=#000000][font=Arial][size=2] _localVelocity.y += Matrix[9] * _acceleration * scaleFactor; // Location [9] will always contain our Y vector[/font]
[color=#000000][font=Arial][size=2] q = _qHeading * _qPitch; // Combine the Quarts again to get our X and Z vectors[/font]
[color=#000000][font=Arial][size=2] q.CreateMatrix(Matrix); // Store our new vector and its rotation[/font]
[color=#000000][font=Arial][size=2] _localVelocity.x += Matrix[8] * _acceleration * scaleFactor; // Location [8] will always contain our X movemet[/font]
[color=#000000][font=Arial][size=2] _localVelocity.z += -Matrix[10] * _acceleration * scaleFactor; // Location [10] will always contain our Z movemet (negate Z in OpenGL)[/font]
[color=#000000][font=Arial][size=2]}[/font]
[size=2][/quote]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] [0][1][2] - contains our RIGHT vector x, y, z[/font]
[color=#000000][font=Arial][size=2] [4][5][6] - contains our UP vector x, y, z[/font]
[color=#000000][font=Arial][size=2][8][9][10] - contain our FORWARD vector x, y, z [/font]
[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]Every function will look the same EXCEPT when moving left or right we omit the y to create a straffe style walking. So drop this line in your accelerateRight():[/font]

[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2]_localVelocity.y += Matrix[1] * _acceleration * scaleFactor;[/font]

[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] Also I have included the functions to move left, down, and backwards. but these functions just call their counterpart with a negative scale factor. Functions for left, down, and back are not needed, but I like to keep my code readable. For calculating an up vector we just return the values instead of adding them to our velocities.[/font]

[color=#000000][font=Arial][size=2][/font]
[color=#000000][font=Arial][size=2] Feel free to download the class and use it in your code. Is should be easy to drop into any project and happy coding![/font][/font]

This topic is closed to new replies.

Advertisement