Code:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#if defined(__APPLE__) && defined(__MACH__)
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#elif defined(__linux__)
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#else
#error Unsupported operating system.
#endif
#define UNUSED __attribute__((unused))
typedef struct {
GLdouble x; /* i component */
GLdouble y; /* j component */
GLdouble z; /* k component */
GLdouble w; /* Scalar component */
} rotationq;
typedef struct {
GLdouble x; /* Rotation axis x component */
GLdouble y; /* Rotation axis y component */
GLdouble z; /* Rotation axis z component */
GLdouble r; /* Radians per half a second */
} vernier;
typedef enum {
CONTROL_WORLD = 0,
CONTROL_PITCH_POS,
CONTROL_PITCH_NEG,
CONTROL_ROLL_POS,
CONTROL_ROLL_NEG,
CONTROL_YAW_POS,
CONTROL_YAW_NEG,
CONTROL_THRUST,
CONTROL_COUNT
} control;
/* Keyboard controls */
static unsigned char control_state[CONTROL_COUNT] = { 0U };
static unsigned char control_keyboard[CONTROL_COUNT] = { 0U };
static int control_special[CONTROL_COUNT] = { 0U };
/* Ship properties */
static GLdouble ship_location[3] = { 100.0, 100.0, 0.0 };
static GLdouble ship_matrix[16] = { 0 };
static GLdouble ship_inverse[16] = { 0 };
static rotationq ship_orientation = { 0.0, 0.0, 0.0, 1.0 };
static vernier ship_vernier[CONTROL_COUNT] = {{0}};
static GLdouble ship_thrust = 100.0; /* Coordinate units per second */
static void define_matrix(GLdouble *const matrix, const rotationq *const rq, const GLdouble *const center)
{
const GLdouble origin[3] = { center[0], center[1], center[2] };
/* Column-major order:
* matrix[0] matrix[4] matrix[8] matrix[12]
* matrix[1] matrix[5] matrix[9] matrix[13]
* matrix[2] matrix[6] matrix[10] matrix[14]
* matrix[3] matrix[7] matrix[11] matrix[15]
*/
matrix[0] = 1.0 - 2.0 * (rq->y * rq->y + rq->z * rq->z);
matrix[1] = 2.0 * (rq->x * rq->y + rq->z * rq->w);
matrix[2] = 2.0 * (rq->x * rq->z - rq->y * rq->w);
matrix[3] = 0.0;
matrix[4] = 2.0 * (rq->x * rq->y - rq->z * rq->w);
matrix[5] = 1.0 - 2.0 * (rq->x * rq->x + rq->z * rq->z);
matrix[6] = 2.0 * (rq->y * rq->z + rq->x * rq->w);
matrix[7] = 0.0;
matrix[8] = 2.0 * (rq->x * rq->z + rq->y * rq->w);
matrix[9] = 2.0 * (rq->y * rq->z - rq->x * rq->w);
matrix[10] = 1.0 - 2.0 * (rq->x * rq->x + rq->y * rq->y);
matrix[11] = 0.0;
matrix[12] = -(matrix[0] * origin[0] + matrix[4] * origin[1] + matrix[8] * origin[2]);
matrix[13] = -(matrix[1] * origin[0] + matrix[5] * origin[1] + matrix[9] * origin[2]);
matrix[14] = -(matrix[2] * origin[0] + matrix[6] * origin[1] + matrix[10]* origin[2]);
matrix[15] = 1.0;
}
static void define_inverse(GLdouble *const matrix, const rotationq *const rq, const GLdouble *const center)
{
/* Inverting the non-scalar components in the rotation quaternion inverts the rotation.
* This also moves the origin to the center. Thus, it is the opposite of define_matrix().
*/
matrix[0] = 1.0 - 2.0 * (rq->y * rq->y + rq->z * rq->z);
matrix[1] = 2.0 * (rq->x * rq->y - rq->z * rq->w);
matrix[2] = 2.0 * (rq->x * rq->z + rq->y * rq->w);
matrix[3] = 0.0;
matrix[4] = 2.0 * (rq->x * rq->y + rq->z * rq->w);
matrix[5] = 1.0 - 2.0 * (rq->x * rq->x + rq->z * rq->z);
matrix[6] = 2.0 * (rq->y * rq->z - rq->x * rq->w);
matrix[7] = 0.0;
matrix[8] = 2.0 * (rq->x * rq->z - rq->y * rq->w);
matrix[9] = 2.0 * (rq->y * rq->z + rq->x * rq->w);
matrix[10] = 1.0 - 2.0 * (rq->x * rq->x + rq->y * rq->y);
matrix[11] = 0.0;
matrix[12] = center[0];
matrix[13] = center[1];
matrix[14] = center[2];
matrix[15] = 1.0;
}
static void apply_vernier(rotationq *const rq, const vernier *const v, const GLdouble duration)
{
const double vn = sqrt(v->x * v->x + v->y*v->y + v->z*v->z);
if (vn > 0.0) {
const GLdouble s = sin(v->r * duration) / vn;
const GLdouble c = cos(v->r * duration);
const rotationq r1 = { s * v->x, s * v->y, s * v->z, c };
const rotationq r2 = *rq;
const rotationq r = { r1.w*r2.x + r1.x*r2.w + r1.y*r2.z - r1.z*r2.y,
r1.w*r2.y - r1.x*r2.z + r1.y*r2.w + r1.z*r2.x,
r1.w*r2.z + r1.x*r2.y - r1.y*r2.x + r1.z*r2.w,
r1.w*r2.w - r1.x*r2.x - r1.y*r2.y - r1.z*r2.z };
const double rn = sqrt(r.x*r.x + r.y*r.y + r.z*r.z + r.w*r.w);
if (rn > 0.0) {
rq->x = r.x / rn;
rq->y = r.y / rn;
rq->z = r.z / rn;
rq->w = r.w / rn;
} else {
rq->x = 0.0;
rq->y = 0.0;
rq->z = 0.0;
rq->w = 1.0;
}
}
}
static void display_window(void)
{
const int world_mode = control_state[CONTROL_WORLD];
static struct timespec prev_update = { 0 };
static struct timespec curr_update = { 0 };
double duration; /* Frame duration in seconds */
prev_update = curr_update;
#ifdef CLOCK_MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &curr_update);
#else
clock_gettime(CLOCK_REALTIME, &curr_update);
#endif
duration = (double)(curr_update.tv_sec - prev_update.tv_sec)
+ (double)(curr_update.tv_nsec - prev_update.tv_nsec) / 1000000000.0;
if (duration < 0.0 || duration > 1.0)
duration = 0.0;
/* Inertialess drive. Eh. */
if (duration > 0.0) {
size_t i = CONTROL_COUNT;
while (i-->0)
if (control_state[i])
apply_vernier(&ship_orientation, ship_vernier + i, duration);
if (control_state[CONTROL_THRUST]) {
const double effect = duration * ship_thrust;
ship_location[0] -= effect * ship_matrix[2];
ship_location[1] -= effect * ship_matrix[6];
ship_location[2] -= effect * ship_matrix[10];
}
}
define_matrix(ship_matrix, &ship_orientation, ship_location);
define_inverse(ship_inverse, &ship_orientation, ship_location);
if (world_mode) {
glClearColor(0.0, 0.0, 0.125, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
} else {
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixd(ship_matrix);
}
/*
* Draw the space stuff
*/
glPushMatrix();
glTranslatef(-100.0f, -200.0f, -1000.0f);
glColor3f(0.5f, 0.5f, 0.5f);
glutWireSphere(400.0, 16, 16);
glPopMatrix();
/*
* Add the ship locating mark if in world mode.
*/
if (world_mode) {
glLoadIdentity();
glLoadMatrixd(ship_inverse);
glBegin(GL_LINES);
glColor3f( 1.0f, 0.0f, 0.0f);
glVertex3f( 0.0f, 0.0f, 0.0f);
glVertex3f( 0.0f, 0.0f, 50.0f);
glColor3f( 0.0f, 1.0f, 0.0f);
glVertex3f( 0.0f, -25.0f, 0.0f);
glVertex3f( 0.0f, 50.0f, 0.0f);
glColor3f( 0.0f, 0.0f, 1.0f);
glVertex3f(-25.0f, 0.0f, 0.0f);
glVertex3f( 50.0f, 0.0f, 0.0f);
glColor3f( 1.0f, 1.0f, 1.0f);
glVertex3f( 0.0f, 0.0f, 0.0f);
glVertex3f( 0.0f, 0.0f,-50.0f);
glEnd();
}
glFinish();
glutSwapBuffers();
glutPostRedisplay();
}
static void reshape_window(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0f, (GLdouble)width / (GLdouble)height, 1.0, 1048576.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glShadeModel(GL_FLAT);
}
static void special_down(int key, int x UNUSED, int y UNUSED)
{
if (key) {
size_t i = CONTROL_COUNT;
while (i-->0)
if (key == control_special[i])
control_state[i] = 1;
}
}
static void special_up(int key, int x UNUSED, int y UNUSED)
{
if (key) {
size_t i = CONTROL_COUNT;
while (i-->0)
if (key == control_special[i])
control_state[i] = 0;
}
}
static void keyboard_down(unsigned char key, int x UNUSED, int y UNUSED)
{
if (key) {
size_t i = CONTROL_COUNT;
while (i-->0)
if (key == control_keyboard[i])
control_state[i] = 1;
}
}
static void keyboard_up(unsigned char key, int x UNUSED, int y UNUSED)
{
if (key) {
size_t i = CONTROL_COUNT;
while (i-->0)
if (key == control_keyboard[i])
control_state[i] = 0;
}
}
static void define_vernier(vernier *const v,
const double x, const double y, const double z,
const double degrees_per_second)
{
v->x = x;
v->y = y;
v->z = z;
v->r = degrees_per_second * 3.14159265358979323846 / 360.0;
}
int main(int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(768, 384);
glutInitWindowPosition(128, 128);
glutCreateWindow("Example");
glutDisplayFunc(display_window);
glutReshapeFunc(reshape_window);
glutSpecialFunc(special_down);
glutSpecialUpFunc(special_up);
glutKeyboardFunc(keyboard_down);
glutKeyboardUpFunc(keyboard_up);
glutIgnoreKeyRepeat(1);
control_special[CONTROL_PITCH_POS] = GLUT_KEY_UP;
control_special[CONTROL_PITCH_NEG] = GLUT_KEY_DOWN;
control_special[CONTROL_YAW_POS] = GLUT_KEY_RIGHT;
control_special[CONTROL_YAW_NEG] = GLUT_KEY_LEFT;
control_keyboard[CONTROL_ROLL_POS] = 'z';
control_keyboard[CONTROL_ROLL_NEG] = 'x';
control_keyboard[CONTROL_THRUST] = ' ';
control_keyboard[CONTROL_WORLD] = '\r';
fprintf(stdout, "Controls:\n");
fprintf(stdout, "\tUp, Down - Pitch\n");
fprintf(stdout, "\tLeft, Right - Yaw\n");
fprintf(stdout, "\tZ, X - Roll\n");
fprintf(stdout, "\tSpace - Thrust\n");
fprintf(stdout, "\tEnter - World view (blue)\n");
fflush(stdout);
define_vernier(ship_vernier + CONTROL_PITCH_POS, 1.0, 0.0, 0.0, +90.0);
define_vernier(ship_vernier + CONTROL_PITCH_NEG, 1.0, 0.0, 0.0, -90.0);
define_vernier(ship_vernier + CONTROL_YAW_POS, 0.0, 1.0, 0.0, +90.0);
define_vernier(ship_vernier + CONTROL_YAW_NEG, 0.0, 1.0, 0.0, -90.0);
define_vernier(ship_vernier + CONTROL_ROLL_POS, 0.0, 0.0, 1.0, -90.0);
define_vernier(ship_vernier + CONTROL_ROLL_NEG, 0.0, 0.0, 1.0, +90.0);
define_vernier(ship_vernier + CONTROL_THRUST, 0.0, 0.0, 0.0, 0.0);
define_vernier(ship_vernier + CONTROL_WORLD, 0.0, 0.0, 0.0, 0.0);
glutMainLoop();
return EXIT_SUCCESS;
}
This one uses inertialess drive, and automatic verniers. That is, the ship will only move forward if you press Space, and rotate while you press Up, Down, Left, Right, Z, or X, for pitch, yaw, and roll, respectively.