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>
#include <mach/clock.h>
#include <mach/mach.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 */
#if defined(__APPLE__) && defined(__MACH__)
static clock_serv_t system_clock;
#endif
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];
double duration; /* Frame duration in seconds */
#if defined(__APPLE__) && defined(__MACH__)
static mach_timespec_t prev_update = { 0 };
static mach_timespec_t curr_update = { 0 };
prev_update = curr_update;
clock_get_time(system_clock, &curr_update);
duration = (double)(curr_update.tv_sec - prev_update.tv_sec)
+ (double)(curr_update.tv_nsec - prev_update.tv_nsec) / 1000000000.0;
#else
static struct timespec prev_update = { 0 };
static struct timespec curr_update = { 0 };
prev_update = curr_update;
clock_gettime(CLOCK_MONOTONIC, &curr_update);
duration = (double)(curr_update.tv_sec - prev_update.tv_sec)
+ (double)(curr_update.tv_nsec - prev_update.tv_nsec) / 1000000000.0;
#endif
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);
}
glPushMatrix();
glTranslatef(-100.0f, -200.0f, -1000.0f);
glColor3f(0.5f, 0.5f, 0.5f);
glutWireSphere(400.0, 16, 16);
glPopMatrix();
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");
#if defined(__APPLE__) && defined(__MACH__)
host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &system_clock);
#endif
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();
#if defined(__APPLE__) && defined(__MACH__)
mach_port_deallocate(mach_task_self(), system_clock );
#endif
return EXIT_SUCCESS;
}