Tuesday 27 December 2011

OpenGL / Allegro 5.1 3D Orbit Primer

So, here again, and still learning (and what is more; still capable of learning, which is always good to know). There is little new with this post, as compared to others. It is more of a confluence of some techniques learned in those other posts to make an animation of a dry earth planet in an elliptical orbit around a shrunken "brown dwarf" style sun. Let's say it is an improbable future scenario?

Here are the textures used, followed by the source code...



#include "allegro5/allegro.h"
#include "allegro5/allegro_image.h"
#include "allegro5/allegro_opengl.h"
#include "GL/glu.h"
#include "math.h"

/*
`pkg-config --libs allegro-5.1 allegro_image-5.1`
-lGL
-lGLU
*/

int SCREEN_X = 800;
int SCREEN_Y = 600;

class planets
{
public:
// a = G (m/r²) : Reference formula
float radius_1;
float radius_2;
float angle_1;
float angle_2;

float grav_const; // = 6.6742e-11; // The "G" in the
formula
float star_mass; // = 5.975e24; // The "m" in the formula

float grav_accel; // The "a" in the formula
float body_x;
float body_z;
float body_y;
float b_vel_x;
float b_vel_z;
float b_vel_y;

planets(void);
~planets(void);

void calc_planet_pos();
};

// Define class functions:

planets::planets(void)
{
grav_const = 6.6742e-11;
star_mass = 5.975e24;
body_x = 1.5e6;
body_z = 5.5e4;
body_y = 1.0e6;
b_vel_x = -3000;
b_vel_z = 11000.0;
b_vel_y = 3000.0;
}

planets::~planets(void)
{
// nothing doing...
}

void planets::calc_planet_pos()
{
angle_1 = atan2f(body_x, body_z);
radius_1 = sqrtf(pow(body_x, 2) + pow(body_z, 2));
angle_2 = atan2f(body_y, radius_1);
radius_2 = sqrtf(pow(body_x, 2) + pow(body_z, 2) + pow(body_y, 2));

grav_accel = (grav_const * (star_mass / pow(radius_2, 2)));

b_vel_x = b_vel_x + ((sin(angle_1) * cos(angle_2)) * (grav_accel));
b_vel_z = b_vel_z + ((cos(angle_1) * cos(angle_2)) * (grav_accel));
b_vel_y = b_vel_y + (sin(angle_2) * (grav_accel));

body_x = body_x - b_vel_x;
body_z = body_z - b_vel_z;
body_y = body_y - b_vel_y;
}

//******* END OF CLASS ********


//******* FUNCTIONS ********

void camera_3D_setup()
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(35.0, (GLdouble)SCREEN_X / (GLdouble)SCREEN_Y, 1.0, 2000.0);
glTranslatef(-80.0f, -70.0f, -650.0f);

}

void camera_2D_setup()
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, (GLdouble)SCREEN_X, (GLdouble)SCREEN_Y, 0.0, 0.0, 1.0);
//glTranslatef(0.0f, 0.0f, 20.0f);
}


void draw_planets(planets *Orbit, GLUquadricObj *q_earth, GLUquadricObj *q_sun, GLuint *o_tex, ALLEGRO_BITMAP *back_gnd, GLfloat e_ang, GLfloat s_ang)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
al_clear_to_color(al_map_rgb(0,0,0));
camera_2D_setup();
al_draw_bitmap(back_gnd, 0.0f, 0.0f, 0);

Orbit->calc_planet_pos();
camera_3D_setup();
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);

//glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
//glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glBindTexture(GL_TEXTURE_2D, *(o_tex));
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glPushMatrix();
glTranslatef(Orbit->body_x / 5000.0f, Orbit->body_y / 5000.0f, Orbit->body_z / 5000.0f);
glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
glRotatef(23.0f, 0.0f, 1.0f, 0.0f);
glRotatef(e_ang, 0.0f, 0.0f, 1.0f);


gluQuadricTexture(q_earth, GL_TRUE);
gluSphere(q_earth,8.0f,20,20);
gluQuadricTexture(q_earth, GL_FALSE);
glPopMatrix();


glPushMatrix();
glBindTexture(GL_TEXTURE_2D, *(o_tex+1));
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTranslatef(0.0f, 0.0f, 0.0f);
glRotatef(-80.0f, 1.0f, 0.0f, 0.0f);
glRotatef(-15.0f, 0.0f, 1.0f, 0.0f);
glRotatef(s_ang, 0.0f, 0.0f, 1.0f);

gluQuadricTexture(q_sun, GL_TRUE);
gluSphere(q_sun,70.0f,28,28);
gluQuadricTexture(q_sun, GL_FALSE);
glPopMatrix();

glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);

glFlush();

}

// ****** END OF FUNCTIONS ******

int main(int argc, char *argv[])
{
ALLEGRO_DISPLAY *display = NULL;
ALLEGRO_EVENT_QUEUE *event_queue = NULL;
ALLEGRO_TIMER *timer = NULL;
ALLEGRO_BITMAP *bmp[3];

planets A5_Orbit;

int i = 0;
float FPS = 60.0f;
bool loop = true;
bool draw = false;

GLfloat e_rot_angle = 0.0f;
GLfloat s_rot_angle = 0.0f;

GLUquadricObj *quad_earth;
GLUquadricObj *quad_sun;

GLuint ogl_tex[2];

quad_earth = gluNewQuadric();
quad_sun = gluNewQuadric();

// Note; no error trapping as yet...
al_init();
al_init_image_addon();

al_set_new_display_flags(ALLEGRO_OPENGL);
al_set_new_display_option(ALLEGRO_VSYNC, 1, ALLEGRO_SUG
GEST);
display = al_create_display(SCREEN_X, SCREEN_Y);
event_queue = al_create_event_queue();
timer = al_create_timer(1.0f/FPS);

al_set_new_bitmap_flags(ALLEGRO_MIPMAP | ALLEGRO_MIN_LINEAR);
bmp[0] = al_load_bitmap("img_1.jpg");
bmp[1] = al_load_bitmap("img_2.jpg");
al_set_new_bitmap_flags(ALLEGRO_CONVERT_BITMAP);
bmp[2] = al_load_bitmap("img_stars.jpg");

for(i = 0; i < 2; i++)
{
ogl_tex[i] = al_get_opengl_texture(bmp[i]);
}

al_register_event_source(event_queue, al_get_display_event_source(display));
al_register_event_source(event_queue, al_get_timer_event_source(timer));

al_start_timer(timer);

while(loop == true)
{
ALLEGRO_EVENT event;
al_wait_for_event(event_queue, &event);

switch(event.type)
{
case ALLEGRO_EVENT_DISPLAY_CLOSE:
loop = false;
break;

case ALLEGRO_EVENT_TIMER:
draw = true;
break;

default:
break;

}

if(draw == true && al_event_queue_is_empty(event_queue))
{
e_rot_angle += 0.75f;
s_rot_angle -= 0.15f;
draw = false;
draw_planets(&A5_Orbit, quad_earth, quad_sun, ogl_tex, bmp[2], e_rot_angle, s_rot_angle);

al_flip_display();

}
}

for(i = 0; i < 3; i++)
al_destroy_bitmap(bmp[i]);

glDeleteTextures(2, ogl_tex);
gluDeleteQuadric(quad_earth);
gluDeleteQuadric(quad_sun);

al_flush_event_queue(event_queue);
al_destroy_event_queue(event_queue);
al_stop_timer(timer);
al_destroy_timer(timer);
al_destroy_display(display);

return 0;
}

So, this mini project uses some code from a previous post, here. What's different? The 3D orbit code is put into a handy class (see here for a refresher). I then use two gluSphere() objects, as used previously, to make the two astral bodies, each with their own mip-map textures, loaded by Allegro 5.1. I also use a bitmap 2D star field background, the technique for which has already been discussed in this post, the only difference being that I used the bitmap as an underlay instead of an overlay (I draw the 2D bitmap first, each cycle of the animation, and the GL objects onto it afterward).

Incidentally, to get the newest version of Allegro 5.1 (thanks elias!!!), use SVN as such, in Linux terminal;

svn co https://alleg.svn.sourceforge.net/svnroot/alleg/allegro/branches/5.1

The main new thing used here is glPushMatrix() and glPopMatrix(). I will be doing a separate post explaining myself the use of these to functions. I saw a great deal of explanations on the web on how these are functions are employed (mostly confusing), but only really "sussed" it out by trying it myself.

Apart from all that, there are a few clarifications with the use of glTexParameterf(). It became apparent that this cannot just be set once (for use of mip-maps) and be expected to be applicable for the next object. It needs to be set every time glBindTexture() is used, or OpenGL reverts to default texture behavior, it seems.

There is also a requirement to use this line, now;

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


...or the GL_DEPTH_TEST does not work right during the animation. Also, Allegro-wise, a certain initial "glitchiness" in the animation was definately improved by using this line in the code...

al_set_new_display_option(ALLEGRO_VSYNC, 1, ALLEGRO_SUGGEST);


Normally, al_flip_display() should wait for the vertical sync (what in the old days we used to call a "vertical blank"), but it cannot be relied on entirely. The smoothness of the animation can be much improved by using al_set_new_display_option(), before creating the Allegro display, and employing glFlush(), after the drawing of GL objects on each cycle.

And, yes, finally, there is a certain importance to the order of placing glTranslatef() and glRotatef() functions in the code. You can rotate or translate first to get different actions of your GL models. What is supremely important to remember is that these functions, when applied to your object, operate in a local sense (that is, relative to the object). For example, translating (negative) first on the Z axis will send the object into the screen (farther away, analogous to the world's Z axis). But, if you rotate the object first, then translate on the Z axis, it will translate along the new orientation of the objects own Z axis, not the world's Z axis. Not difficult and fairly straightforward, but does need pointing out. And, of course, there will be another post covering this issue, soon. That said, the above applies for rotations on two axes. Something odd happens when you rotate on all three axes simultaneously, which I have yet to fathom out.

Here's a screen shot of the finished product...
And that is all there is to say, this time around, but while I am on the OpenGL subject in this blog, here is another good OpenGL set of tutorials and lessons. JAVA, maybe, but the idea is still the same where OpenGL is concerned...

OpenGL Tutorials and Lessons.

No comments:

Post a Comment