Show exquat.c syntax highlighted
/*
* Example program for the Allegro library, by Jason Wilkins.
*
* A Comparison Between Euler Angles and Quaternions.
*
* Euler angles are convenient for storing and creating 3D orientations.
* However, this program demonstrates that they are not good when
* interpolating between two different orientations. The problem is
* solved by using Allegro's quaternion operations.
*
* In this program, two cubes are rotated between random orientations.
* Notice that although they are have the same beginning and ending
* orientations, they do not follow the same path between orientations.
*
* One cube is being rotated by directly incrementing or decrementing
* the Euler angles from the starting point to the ending point.
* This is an intuitive notion, but it is incorrect because it does not
* cause the object to turn around a single unchanging axis of rotation.
* The axis of rotation wobbles resulting in the object spinning in
* strange ways. The object will eventually end up in the orientation
* that the user intended, but it gets there in a way that is unattractive.
* Imagine if this method was used to update the position of a camera in a
* game! Sometimes it would swing wildly and disorient the player.
*
* The other cube is animated using quaternions. This results in a much
* more pleasing animation because the cube turns around a single axis
* of rotation.
*/
#include <stdlib.h>
#include <time.h>
#include "allegro.h"
/* the number of steps to get from the starting to the ending orientation */
#define NUM_STEPS 200
/* this structure holds an orientation expressed as Euler angles. Each number
* represents a rotation about the x, y, and z axis. In the case of Allegro
* there are 256 degrees to a al_draw_circle. Yaw, pitch, and roll correspond to
* x, y, and z
*/
typedef struct EULER
{
float x, y, z;
} EULER;
/* matrix to transform world coordinates into normalized eye coordinates */
AL_MATRIX_F camera;
AL_MATRIX_F rotation;
/* the parts of the al_screen that display the demo boxes */
AL_BITMAP *euler_screen;
AL_BITMAP *quat_screen;
/* these are backbuffers, drawing is done here before updating the al_screen */
AL_BITMAP *euler_buffer;
AL_BITMAP *quat_buffer;
/* In these identifiers, 'from' refers to the starting orientation, 'to'
* refers to the ending orientation and 'in' refers to the interpolated
* orientation. 'q' refers to quaternion, 'e' refers to Euler
*/
AL_MATRIX_F q_from_matrix;
AL_MATRIX_F q_to_matrix;
AL_MATRIX_F q_in_matrix;
AL_MATRIX_F e_from_matrix;
AL_MATRIX_F e_to_matrix;
AL_MATRIX_F e_in_matrix;
AL_QUAT q_to;
AL_QUAT q_in;
AL_QUAT q_from;
EULER e_from;
EULER e_to;
EULER e_in;
/* Here is defined a 2x2x2 cube centered about the origin, and
* an arrow pointing straight up. They are wireframe objects
* so only the points and edges are specified.
*
* It should be noted that the world coordinate system in this
* program is oriented like it is in most math books. X and Y
* are oriented like a floor and Z refers to the height above
* that floor.
*
* N - North
* S - South
* W - West
* E - East
* U - Up
* D - Down
*/
float box_points[8][3] =
{
/* X, Y, Z */
{ -1.0, -1.0, -1.0 }, /* NWD */
{ -1.0, -1.0, 1.0 }, /* NWU */
{ -1.0, 1.0, -1.0 }, /* NED */
{ -1.0, 1.0, 1.0 }, /* NEU */
{ 1.0, -1.0, -1.0 }, /* SWD */
{ 1.0, -1.0, 1.0 }, /* SWU */
{ 1.0, 1.0, -1.0 }, /* SED */
{ 1.0, 1.0, 1.0 }, /* SEU */
};
int box_edges[12][2] =
{
/* from, to */
{ 0, 2 }, /* bottom */
{ 2, 6 },
{ 6, 4 },
{ 4, 0 },
{ 1, 3 }, /* top */
{ 3, 7 },
{ 7, 5 },
{ 5, 1 },
{ 0, 1 }, /* sides */
{ 2, 3 },
{ 4, 5 },
{ 6, 7 }
};
float arrow_points[4][3] =
{
/* X, Y, Z */
{ 0.0, 0.0, 0.0 }, /* tail of the arrow, at the origin */
{ 0.0, 0.0, 2.0 }, /* tip of the arrow head */
{ 0.0, 0.25, 1.5 }, /* eastern part of the head */
{ 0.0, -0.25, 1.5 } /* western part of the head */
};
int arrow_edges[3][2] =
{
/* from, to */
{ 0, 1 },
{ 1, 2 },
{ 1, 3 }
};
/* Each demo box has associated with it two paths (stored as wireframe
* objects). These are used to store a history of the orientation of their
* interpolated axis. These sets of points are used to draw ribbons that
* show how an object rotated from one orientation to another.
*/
float e_path_points_1[NUM_STEPS+1][3];
float e_path_points_2[NUM_STEPS+1][3];
float q_path_points_1[NUM_STEPS+1][3];
float q_path_points_2[NUM_STEPS+1][3];
/* these arrays are shared by both ribbons */
float tmp_points[NUM_STEPS+1][3];
int path_edges[NUM_STEPS][2];
/* draw an object defined as a set of points and edges */
void render_wireframe_object(AL_MATRIX_F *m, AL_BITMAP *b, float p[][3], float t[][3], int e[][2], int np, int ne, int c)
{
int index, from, to;
/* transform the points and store them in a buffer */
for (index=0; index<np; index++) {
al_apply_matrix_f(m, p[index][0], p[index][1], p[index][2],
&(t[index][0]), &(t[index][1]), &(t[index][2]));
al_persp_project_f(t[index][0], t[index][1], t[index][2],
&(t[index][0]), &(t[index][1]));
}
/* draw the edges */
for (index=0; index<ne; index++) {
from = e[index][0];
to = e[index][1];
al_draw_line(b, (int)(t[from][0]), (int)(t[from][1]), (int)(t[to][0]), (int)(t[to][1]), c);
}
}
/* draws a set of objects that demonstrate whats going on. It consists
* of a cube, an arrow showing the 'to' orientation, an another arrow
* showing the 'from' orientation, and another arrow showing the
* interpolated orientation.
*/
void render_demo_box(AL_BITMAP *b, AL_MATRIX_F *from, AL_MATRIX_F *in, AL_MATRIX_F *to, int col1, int col2, int col3)
{
float tmp_points[8][3];
render_wireframe_object(in, b, box_points, tmp_points, box_edges, 8, 12, col1);
render_wireframe_object(from, b, arrow_points, tmp_points, arrow_edges, 4, 3, col3);
render_wireframe_object(to, b, arrow_points, tmp_points, arrow_edges, 4, 3, col3);
render_wireframe_object(in, b, arrow_points, tmp_points, arrow_edges, 4, 3, col2);
}
/* Just interpolate linearly yaw, pitch, and roll. Doing this _correctly_
* (I.E get the same results as al_quat_interpolate) would require one to use
* linear integration, a subject that is in the last 100 pages of my 1500
* page Calculus book. This function is an example of what you should NOT
* do, as in some cases it will cause the orientation to swing wildly about.
* The path could be anything from nearly correct, a spiral, or a curly Q.
* The simple solution is to use quaternion interpolation, which always
* results in a simple circular path.
*/
void euler_interpolate(EULER * from, EULER * to, float t, EULER * out)
{
float delta;
delta = (to->x-from->x) * t;
out->x = from->x+delta;
delta = (to->y-from->y) * t;
out->y = from->y+delta;
delta = (to->z-from->z) * t;
out->z = from->z+delta;
}
int main()
{
int index;
allegro_init();
al_install_keyboard();
if (al_set_gfx_mode(AL_GFX_SAFE, 640, 480, 0, 0) != 0) {
al_set_gfx_mode(AL_GFX_NONE, 0, 0, 0, 0);
al_show_message("Unable to set any graphic mode\n%s\n", al_error);
return 1;
}
al_set_palette(al_desktop_palette);
al_clear_to_color(al_screen, al_palette_color[0]);
al_text_mode(-1);
/* Each back-buffer is one quarter the size of the al_screen
*/
euler_buffer = al_create_bitmap(320, 240);
quat_buffer = al_create_bitmap(320, 240);
if ((!euler_buffer) || (!quat_buffer)) {
al_set_gfx_mode(AL_GFX_NONE, 0, 0, 0, 0);
al_show_message("Error creating bitmaps\n");
return 1;
}
al_set_palette(al_desktop_palette);
/* setup the viewport for rendering into the back-buffers */
al_set_projection_viewport(0, 0, 320, 240);
/* print out something helpful for the user */
al_put_text(al_screen, al_font_8x8, "SPACE - next interpolation", 184, 24, al_palette_color[15]);
al_put_text(al_screen, al_font_8x8, " R - repeat last interpolation", 184, 40, al_palette_color[15]);
al_put_text(al_screen, al_font_8x8, " ESC - quit", 184, 56, al_palette_color[15]);
al_put_text(al_screen, al_font_8x8, "Interpolating Euler Angles", 56, 110, al_palette_color[15]);
al_put_text(al_screen, al_font_8x8, "Interpolating Quaternions", 380, 110, al_palette_color[15]);
al_put_text(al_screen, al_font_8x8, "Incorrect!", 120, 360, al_palette_color[15]);
al_put_text(al_screen, al_font_8x8, "Correct!", 448, 360, al_palette_color[15]);
/* initialize the path edges. This structure is used by both the Euler
* path and the quaternion path. It connects all the points end to end
*/
for (index=0; index<(NUM_STEPS-1); index++) {
path_edges[index][0] = index;
path_edges[index][1] = index + 1;
}
/* initialize the first destination orientation */
srand(time(NULL));
e_to.x = (float)(rand() % 256);
e_to.y = (float)(rand() % 256);
e_to.z = (float)(rand() % 256);
/* the camera is backed away from the origin and turned to face it */
al_get_camera_matrix_f(&camera, 5, 0, 0, -1, 0, 0, 0, 0, 1, 46, 1);
/* this is a 'for'ever loop */
for (;;) {
float t;
for (index=0; index<(NUM_STEPS+1); index++) {
t = index * (1.0 / NUM_STEPS);
/* the first part shows how to animate the cube incorrectly
* using Euler angles
*/
/* create the matrix for the starting orientation */
al_get_rotation_matrix_f(&rotation, e_from.x, e_from.y, e_from.z);
al_matrix_mul_f(&rotation, &camera, &e_from_matrix);
/* create the matrix for the ending orientation */
al_get_rotation_matrix_f(&rotation, e_to.x, e_to.y, e_to.z);
al_matrix_mul_f(&rotation, &camera, &e_to_matrix);
/* use the incorrect method to interpolate between them */
euler_interpolate(&e_from, &e_to, t, &e_in);
al_get_rotation_matrix_f(&rotation, e_in.x, e_in.y, e_in.z);
al_matrix_mul_f(&rotation, &camera, &e_in_matrix);
/* update the lines that make up the Euler orientation path */
al_apply_matrix_f(&rotation, 0, 0, 1.5,
&(e_path_points_1[index][0]),
&(e_path_points_1[index][1]),
&(e_path_points_1[index][2]));
al_apply_matrix_f(&rotation, 0, 0, 2.0,
&(e_path_points_2[index][0]),
&(e_path_points_2[index][1]),
&(e_path_points_2[index][2]));
/* render the results to the Euler sub-bitmap */
al_clear_to_color(euler_buffer, al_palette_color[0]);
render_demo_box(euler_buffer, &e_from_matrix, &e_in_matrix, &e_to_matrix,
al_palette_color[15], al_palette_color[1], al_palette_color[4]);
render_wireframe_object(&camera, euler_buffer, e_path_points_1,
tmp_points, path_edges, index+1, index,
al_palette_color[5]);
render_wireframe_object(&camera, euler_buffer, e_path_points_2,
tmp_points, path_edges, index+1, index,
al_palette_color[5]);
/* here is how to animate the cube correctly using quaternions */
/* create a matrix for the starting orientation. This time
* we create it using quaternions. This is to demonstrate
* that the quaternion gotten with al_get_rotation_quat will
* generate the save matrix as that gotten by al_get_rotation_matrix
*/
al_get_rotation_quat(&q_from, e_from.x, e_from.y, e_from.z);
al_quat_to_matrix(&q_from, &rotation);
al_matrix_mul_f(&rotation, &camera, &q_from_matrix);
/* this is the same as above, but for the ending orientation */
al_get_rotation_quat(&q_to, e_to.x, e_to.y, e_to.z);
al_quat_to_matrix(&q_to, &rotation);
al_matrix_mul_f(&rotation, &camera, &q_to_matrix);
/* al_quat_interpolate is the proper way to interpolate between two
* orientations.
*/
al_quat_interpolate(&q_from, &q_to, t, &q_in);
al_quat_to_matrix(&q_in, &rotation);
al_matrix_mul_f(&rotation, &camera, &q_in_matrix);
/* update the lines that make up the quaternion orientation path */
al_apply_matrix_f(&rotation, 0, 0, 1.5,
&(q_path_points_1[index][0]),
&(q_path_points_1[index][1]),
&(q_path_points_1[index][2]));
al_apply_matrix_f(&rotation, 0, 0, 2.0,
&(q_path_points_2[index][0]),
&(q_path_points_2[index][1]),
&(q_path_points_2[index][2]));
/* render the results to the quaternion sub-bitmap */
al_clear_to_color(quat_buffer, al_palette_color[0]);
render_demo_box(quat_buffer, &q_from_matrix, &q_in_matrix, &q_to_matrix,
al_palette_color[15], al_palette_color[1], al_palette_color[4]);
render_wireframe_object(&camera, quat_buffer, q_path_points_1,
tmp_points, path_edges, index+1, index,
al_palette_color[5]);
render_wireframe_object(&camera, quat_buffer, q_path_points_2,
tmp_points, path_edges, index+1, index,
al_palette_color[5]);
/* update the al_screen */
al_vsync();
al_acquire_bitmap(al_screen);
al_blit(euler_buffer, al_screen, 0, 0, 0, 120, 320, 240);
al_blit(quat_buffer, al_screen, 0, 0, 320, 120, 320, 240);
al_release_bitmap(al_screen);
}
/* handle user input */
for (;;) {
int input = al_read_key() >> 8;
if (input == AL_KEY_R) {
/* skip updating the EULER angles so that the last interpolation
* will repeat
*/
break;
}
else if (input == AL_KEY_SPACE) {
/* make the last ending orientation the starting orientation and
* generate a random new ending orientation
*/
e_from = e_to;
e_to.x = (float)(rand() % 256);
e_to.y = (float)(rand() % 256);
e_to.z = (float)(rand() % 256);
break;
}
else if (input == AL_KEY_ESC) {
/* quit the program */
al_destroy_bitmap(euler_buffer);
al_destroy_bitmap(quat_buffer);
return 0;
}
}
}
}
AL_END_OF_MAIN();
See more files for this project here