using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Runtime.InteropServices; using System.Text; using allegro; using alleggl; public class excamera : AllegGL { /* * Example program for the Allegro library, by Shawn Hargreaves * converted to OpenGL/AllegroGL. * * This program demonstrates how to easily manipulate a camera * in OpenGL to view a 3d world from any position and angle. * Quaternions are used, because they're so easy to work with! */ /* Define M_PI in case the compiler doesn't */ const float M_PI = 3.1415926535897932384626433832795f; /* Define a 3D vector type */ struct VECTOR { public float x, y, z; } /* display a nice 12x12 chessboard grid */ const int GRID_SIZE = 12; /* Parameters controlling the camera and projection state */ static int viewport_w = 320; /* Viewport Width (pixels) */ static int viewport_h = 240; /* Viewport Height (pixels) */ static int fov = 48; /* Field of view (degrees) */ static float aspect = 1; /* Aspect ratio */ /* Define the camera * We need: One position vector, and one orientation QUAT */ struct CAMERA { public VECTOR position; public QUAT orientation; } static CAMERA camera; /* A simple font to display some info on screen */ static FONT agl_font; /* Sets up the viewport to designated values */ static void set_viewport() { OpenGL.glViewport((SCREEN_W - viewport_w) / 2, (SCREEN_H - viewport_h) / 2, viewport_w, viewport_h); } /* Macro to convert radians to degrees */ static float RAD_2_DEG(float x) { return ((x) * 180 / M_PI); } /* Sets up the camera for displaying the world */ static void set_camera() { float theta; /* First, we set up the projection matrix. * Note that SCREEN_W / SCREEN_H = 1.333333, so we need to multiply the * aspect ratio by that value so that the display doesn't get distorted. */ OpenGL.glMatrixMode(OpenGL.GL_PROJECTION); OpenGL.glLoadIdentity(); OpenGLU.gluPerspective(fov, aspect * 1.333333, 1.0, 120.0); OpenGL.glMatrixMode(OpenGL.GL_MODELVIEW); OpenGL.glLoadIdentity(); /* Convert the QUAT to something OpenGL can understand * We can use allegro_gl_apply_quat() here, but I'd just like * to show how it can be done with regular GL code. * * Since we're working with the camera, we have to rotate first, * and then translate. Objects are done the other way around. */ theta = RAD_2_DEG(2 * (float)Math.Acos(camera.orientation.w)); if (camera.orientation.w < 1.0f && camera.orientation.w > -1.0f) { OpenGL.glRotatef(theta, camera.orientation.x, camera.orientation.y, camera.orientation.z); } OpenGL.glTranslatef(-camera.position.x, -camera.position.y, -camera.position.z); } /* Draw the (simple) world * Notice how the camera doesn't affect the positioning. */ static void draw_field() { int i, j; for (j = 0; j < GRID_SIZE; j++) { for (i = 0; i < GRID_SIZE; i++) { OpenGL.glPushMatrix(); OpenGL.glTranslatef(i * 2 - GRID_SIZE + 1, -2, j * 2 - GRID_SIZE + 1); if (((i + j) & 1) > 0) { OpenGL.glColor3ub(255, 255, 0); } else { OpenGL.glColor3ub(0, 255, 0); } OpenGL.glBegin(OpenGL.GL_QUADS); OpenGL.glVertex3f(-1, 0, -1); OpenGL.glVertex3f(-1, 0, 1); OpenGL.glVertex3f(1, 0, 1); OpenGL.glVertex3f(1, 0, -1); OpenGL.glEnd(); OpenGL.glPopMatrix(); } } } /* For display, we'd like to convert the QUAT back to heading, pitch and roll * These don't serve any purpose but to make it look human readable. * Note: Produces incorrect results. */ static unsafe void convert_quat(ref QUAT q, ref float heading, ref float pitch, ref float roll) { MATRIX_f matrix = new MATRIX_f(); quat_to_matrix(ref q, ref matrix); //heading = Math.Atan2(matrix.v[0][2], matrix.v[0][0]); //pitch = Math.Asin(matrix.v[0][1]); //roll = Math.Atan2(matrix.v[2][1], matrix.v[2][0]); heading = (float)Math.Atan2(matrix.v[0 * 3 + 2], matrix.v[0 * 3 + 0]); pitch = (float)Math.Asin(matrix.v[0 * 3 + 1]); roll = (float)Math.Atan2(matrix.v[2 * 3 + 1], matrix.v[2 * 3 + 0]); } /* Draws the overlay over the field. The position of the overlay is * independent of the camera. */ static void draw_overlay() { float heading = 0, pitch = 0, roll = 0; int color; VECTOR v = new VECTOR(); /* Set up the viewport so that it takes up the whole screen */ OpenGL.glViewport(0, 0, SCREEN_W, SCREEN_H); /* Draw a line around the viewport */ allegro_gl_set_projection(); OpenGL.glColor3ub(255, 0, 0); OpenGL.glDisable(OpenGL.GL_DEPTH_TEST); OpenGL.glBegin(OpenGL.GL_LINE_LOOP); OpenGL.glVertex2i((SCREEN_W - viewport_w) / 2, (SCREEN_H - viewport_h) / 2); OpenGL.glVertex2i((SCREEN_W + viewport_w) / 2 - 1, (SCREEN_H - viewport_h) / 2); OpenGL.glVertex2i((SCREEN_W + viewport_w) / 2 - 1, (SCREEN_H + viewport_h) / 2 - 1); OpenGL.glVertex2i((SCREEN_W - viewport_w) / 2, (SCREEN_H + viewport_h) / 2 - 1); OpenGL.glEnd(); /* Overlay some text describing the current situation */ OpenGL.glBlendFunc(OpenGL.GL_SRC_ALPHA, OpenGL.GL_ONE_MINUS_SRC_ALPHA); color = 0; OpenGL.glTranslatef(-0.375f, -0.375f, 0); allegro_gl_printf(agl_font, 0, 0, 0, color, string.Format(CultureInfo.InvariantCulture, "Viewport width: {0:000} pix (w/W changes)", viewport_w)); allegro_gl_printf(agl_font, 0, 8, 0, color, string.Format(CultureInfo.InvariantCulture, "Viewport height: {0:000} pix (h/H changes)", viewport_h)); allegro_gl_printf(agl_font, 0, 16, 0, color, string.Format(CultureInfo.InvariantCulture, "Field Of View: {0:00} deg (f/F changes)", fov)); allegro_gl_printf(agl_font, 0, 24, 0, color, string.Format(CultureInfo.InvariantCulture, "Aspect Ratio: {0:0.00} (a/A changes)", aspect)); allegro_gl_printf(agl_font, 0, 32, 0, color, string.Format(CultureInfo.InvariantCulture, "X position: {0:+0.00;-0.00;+0.00} (x/X changes)", camera.position.x)); allegro_gl_printf(agl_font, 0, 40, 0, color, string.Format(CultureInfo.InvariantCulture, "Y position: {0:+0.00;-0.00;+0.00} (y/Y changes)", camera.position.y)); allegro_gl_printf(agl_font, 0, 48, 0, color, string.Format(CultureInfo.InvariantCulture, "Z position: {0:+0.00;-0.00;+0.00} (z/Z changes)", camera.position.z)); /* Convert the orientation QUAT into heading, pitch and roll to display */ convert_quat(ref camera.orientation, ref heading, ref pitch, ref roll); allegro_gl_printf(agl_font, 0, 56, 0, color, string.Format(CultureInfo.InvariantCulture, "Heading: {0:+0.00;-0.00;+0.00} deg (left/right changes)", heading * 180 / M_PI)); allegro_gl_printf(agl_font, 0, 64, 0, color, string.Format(CultureInfo.InvariantCulture, "Pitch: {0:+0.00;-0.00;+0.00} deg (pgup/pgdn changes)", pitch * 180 / M_PI)); allegro_gl_printf(agl_font, 0, 72, 0, color, string.Format(CultureInfo.InvariantCulture, "Roll: {0:+0.00;-0.00;+0.00} deg (r/R changes)", roll * 180 / M_PI)); apply_quat(ref camera.orientation, 0, 0, -1, ref v.x, ref v.y, ref v.z); allegro_gl_printf(agl_font, 0, 80, 0, color, string.Format(CultureInfo.InvariantCulture, "Front Vector: {0:0.00}, {1:0.00}, {2:0.00}", v.x, v.y, v.z)); apply_quat(ref camera.orientation, 0, 1, 0, ref v.x, ref v.y, ref v.z); allegro_gl_printf(agl_font, 0, 88, 0, color, string.Format(CultureInfo.InvariantCulture, "Up Vector: {0:0.00}, {1:0.00}, {2:0.00}", v.x, v.y, v.z)); allegro_gl_printf(agl_font, 0, 96, 0, color, string.Format(CultureInfo.InvariantCulture, "QUAT: {0:0.000000}, {1:0.000000}, {2:0.000000}, {3:0.000000} ", camera.orientation.w, camera.orientation.x, camera.orientation.y, camera.orientation.z)); allegro_gl_unset_projection(); OpenGL.glBlendFunc(OpenGL.GL_ONE, OpenGL.GL_ZERO); OpenGL.glEnable(OpenGL.GL_DEPTH_TEST); } /* draw everything */ static void render() { set_viewport(); OpenGL.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); OpenGL.glClear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT); set_camera(); draw_field(); draw_overlay(); OpenGL.glFlush(); allegro_gl_flip(); } /* deal with user input */ static void process_input() { QUAT q = new QUAT(); poll_keyboard(); if (key[KEY_W]) { if ((key_shifts & KB_SHIFT_FLAG) > 0) { if (viewport_w < SCREEN_W) viewport_w += 8; } else { if (viewport_w > 16) viewport_w -= 8; } } if (key[KEY_H]) { if ((key_shifts & KB_SHIFT_FLAG) > 0) { if (viewport_h < SCREEN_H) viewport_h += 8; } else { if (viewport_h > 16) viewport_h -= 8; } } if (key[KEY_X]) { if ((key_shifts & KB_SHIFT_FLAG) > 0) camera.position.x += 0.05f; else camera.position.x -= 0.05f; } if (key[KEY_Y]) { if ((key_shifts & KB_SHIFT_FLAG) > 0) camera.position.y += 0.05f; else camera.position.y -= 0.05f; } if (key[KEY_Z]) { if ((key_shifts & KB_SHIFT_FLAG) > 0) camera.position.z += 0.05f; else camera.position.z -= 0.05f; } if (key[KEY_UP]) { VECTOR front = new VECTOR(); /* Note: We use -1 here because Allegro's coordinate system * is slightly different than OpenGL's. */ apply_quat(ref camera.orientation, 0, 0, -1, ref front.x, ref front.y, ref front.z); camera.position.x += front.x / 10; camera.position.y += front.y / 10; camera.position.z += front.z / 10; } if (key[KEY_DOWN]) { VECTOR front = new VECTOR(); apply_quat(ref camera.orientation, 0, 0, -1, ref front.x, ref front.y, ref front.z); camera.position.x -= front.x / 10; camera.position.y -= front.y / 10; camera.position.z -= front.z / 10; } /* When turning right or left, we only want to change the heading. * That is, we only want to rotate around the absolute Y axis */ if (key[KEY_LEFT]) { get_y_rotate_quat(ref q, -1); quat_mul(ref camera.orientation, ref q, out camera.orientation); } if (key[KEY_RIGHT]) { get_y_rotate_quat(ref q, 1); quat_mul(ref camera.orientation, ref q, out camera.orientation); } /* However, when rolling or changing pitch, we do a rotation relative to * the current orientation of the camera. This is why we extract the * 'right' and 'front' vectors of the camera and apply a rotation on * those. */ if (key[KEY_PGUP]) { VECTOR right = new VECTOR(); apply_quat(ref camera.orientation, 1, 0, 0, ref right.x, ref right.y, ref right.z); get_vector_rotation_quat(ref q, right.x, right.y, right.z, -1); quat_mul(ref camera.orientation, ref q, out camera.orientation); } if (key[KEY_PGDN]) { VECTOR right = new VECTOR(); apply_quat(ref camera.orientation, 1, 0, 0, ref right.x, ref right.y, ref right.z); get_vector_rotation_quat(ref q, right.x, right.y, right.z, 1); quat_mul(ref camera.orientation, ref q, out camera.orientation); } if (key[KEY_R]) { VECTOR front = new VECTOR(); apply_quat(ref camera.orientation, 0, 0, 1, ref front.x, ref front.y, ref front.z); if ((key_shifts & KB_SHIFT_FLAG) > 0) get_vector_rotation_quat(ref q, front.x, front.y, front.z, -1); else get_vector_rotation_quat(ref q, front.x, front.y, front.z, 1); quat_mul(ref camera.orientation, ref q, out camera.orientation); } if (key[KEY_F]) { if ((key_shifts & KB_SHIFT_FLAG) > 0) { if (fov < 96) fov++; } else { if (fov > 16) fov--; } } if (key[KEY_A]) { if ((key_shifts & KB_SHIFT_FLAG) > 0) { aspect += 0.05f; if (aspect > 2) aspect = 2; } else { aspect -= 0.05f; if (aspect < .1) aspect = .1f; } } } static int Main() { allegro_init(); install_allegro_gl(); install_keyboard(); install_timer(); /* Initialise the camera */ camera.orientation = identity_quat; camera.position.x = 0; camera.position.y = 0; camera.position.z = 4; /* Set up AllegroGL */ allegro_gl_clear_settings(); allegro_gl_set(AGL_COLOR_DEPTH, 16); allegro_gl_set(AGL_Z_DEPTH, 16); allegro_gl_set(AGL_DOUBLEBUFFER, 1); allegro_gl_set(AGL_RENDERMETHOD, 1); allegro_gl_set(AGL_WINDOWED, TRUE); allegro_gl_set(AGL_SUGGEST, AGL_Z_DEPTH | AGL_DOUBLEBUFFER | AGL_RENDERMETHOD | AGL_WINDOWED | AGL_COLOR_DEPTH); if (set_gfx_mode(GFX_OPENGL, 640, 480, 0, 0) != 0) { set_gfx_mode(GFX_TEXT, 0, 0, 0, 0); allegro_message(string.Format("Error setting OpenGL graphics mode:\n{0}\n" + "Allegro GL error : {1}\n", allegro_error, allegro_gl_error)); return 1; } /* Set up OpenGL */ OpenGL.glEnable(OpenGL.GL_DEPTH_TEST); OpenGL.glCullFace(OpenGL.GL_BACK); OpenGL.glEnable(OpenGL.GL_CULL_FACE); OpenGL.glEnable(OpenGL.GL_TEXTURE_2D); OpenGL.glEnable(OpenGL.GL_BLEND); OpenGL.glShadeModel(OpenGL.GL_SMOOTH); /* Build the font we'll use to display info */ agl_font = allegro_gl_convert_allegro_font_ex(font, AGL_FONT_TYPE_TEXTURED, -1.0f, (int)OpenGL.GL_ALPHA8); OpenGL.glBindTexture(OpenGL.GL_TEXTURE_2D, 0); /* Run the example program */ while (!key[KEY_ESC]) { render(); process_input(); rest(2); } allegro_gl_destroy_font(agl_font); return 0; } }