Gravitator programmer's guide

Tools used

Gravitator is developped in C. It uses the GTK library for it's GUI, OpenGL for 3D rendering to the gtkglarea widget and glib for some data structures. It was developped in KDevelop IDE, the GUI was made by Glade.

Basic Ideas

Gravitator is intended to simulate large quantities of objects. This is made possible by the fact that the objects are of 2 types: heavy and light. Heavy objects affect other objects with their gravity force, light object don't. This means that for every object it has to calculate the gravitational force only to every heavy object. The typical simulation would simulate mostly tens of heavy objects and thousands of light objects.


The program can be separated into 2 parts: the GUI and the backend. The GUI uses the backend to operate the world, work with files etc. Here is a picture of the design:

Gravitator architecture design

The source files for the GUI are:

The source files for the backend are:

Other source files are:

Every function from a source file which is used somewhere else has it's prototype in a header file named the same as the file in which it's declared. The global data structures are defined in main.h

Data structures

There are 2 global pointers to global data : world and gui. world holds information about the objects in the world, gui holds the information about GUI, e.g. in which state it is, which objects are selected etc.. Detailed description of the data strucures is in main.c


gui pointer has 5 subpointers, in new_obj there is information about the created object, in selection there is information about selected objects and metaobjects, in mode there is information about what is going on (e.g. user is dragging a selection), in view there are informations about position and rotation in the world in the views on the world and in misc there are other miscelaneous information which don't fall into any previous category.


The world pointer holds 5 subpointers: to the list of metaobjects containing light objects, to the list of metaobjects containing heavy objects, to the list of metaobjects containing light objects in the simulation, to the list of metaobjects containing heavy objects in the simulation and to a temporary list containing heavy objects in simulation.

The lists of metaobjects are pointers to the typeT_ METAOBJECT_LIST. This is a single linked list, so every member of the list contains information about itself, pointer to it's objects and pointer to next metaobject. The information about objects is also stored in a single linked list which nodes are of OBJECT_LIST type. They contain information about themselves and pointers to next object.

General program working

The program of course begins in the main function. First, it inicializes itself by calling init, which allocates global data structures, sets their default values, loads config and adds gtkglareas to the application. Then the main loop of the GTK library is started. All of the actions performed by user are handled by callbacks in callbacks.c and gui.c (here are callbacks for gtkglareas). The gtkglareas are drawn by the editor_draw function, which is set to be permanently called by gtk_idle_add function. The simulation gtkglarea is redrawn by the sim_draw function and the script gtkglarea is redrawn by the script_draw function.

Most important functions



The basic idea behind calculating the gravity is that there are 2 kinds of objects, light and heavy. Only heavy objects apply gravity on other objects. So the algorithm basically works like this: for every light object calculate how is it attracted by every heavy object, then copy the heavy objects to a temporary position (so you don't calculate how are they attracted by an already moved heavy objects), calculate how are they attracted by heavy objects (and store the position change to the temporary position), and finally copy the position change from the temporary position to the original one. Part of the code looks like this (this part applies gravity only on the light objects):

  // Aply gravity on every light object in the world
  light_meta_list = world->sim_light_obj_list;
  while(light_meta_list != NULL){
    light_obj = light_meta_list->objects;

    while(light_obj != NULL){ // NULL means the end of the object list
    	//The acceleration is 0 at the begining
      x_accel = 0;
      y_accel = 0;
      z_accel = 0;

      // Aply gravity from every heavy object to the current object
      heavy_meta_list = world->sim_heavy_obj_list;
      while(heavy_meta_list != NULL){
        heavy_obj = heavy_meta_list->objects;

        while (heavy_obj != NULL){ // NULL means the end of the object list
	  // Apply gravity from every object of the heavy metaobject
          // Calculate the direction vector from the light object to the heavy object
          x_vect = heavy_obj->x - light_obj->x;
          y_vect = heavy_obj->y - light_obj->y;
          z_vect = heavy_obj->z - light_obj->z;

          // Calculate the distance between them
          dist = (double) sqrt((double) (x_vect*x_vect + y_vect*y_vect + z_vect*z_vect));
          if (dist < 0.03) dist = 0.03;	// Watch out if they get too close

          // Normalize the vector
          x_vect = x_vect / dist;
          y_vect = y_vect / dist;
          z_vect = z_vect / dist;

          // Calculate the gravity force
          force = ((light_obj->mass + heavy_obj->mass) / (dist*dist)) / factor;

          // Aply the gravity on the vector
          x_vect = x_vect * force / light_obj->mass;
          y_vect = y_vect * force / light_obj->mass;
          z_vect = z_vect * force / light_obj->mass;

          // Add the gravity vector to the acceleration of the light object
          x_accel += x_vect;
          y_accel += y_vect;
          z_accel += z_vect;

          heavy_obj = heavy_obj->next;	// Go to the next heavy object

        heavy_meta_list = heavy_meta_list->next;	// Go the the next heavy metaobject

      //All heavy objects applied their gravity on the light one, change it's position
      // Modify the speed of the light object
      light_obj->x_vel += x_accel;
      light_obj->y_vel += y_accel;
      light_obj->z_vel += z_accel;

      // Modify the position of the light object
      light_obj->x +=light_obj->x_vel / factor;
      light_obj->y +=light_obj->y_vel / factor;
      light_obj->z +=light_obj->z_vel / factor;

      // Modify the position of the metaobject in which is the light object
      light_meta_list->x = light_obj->x;
      light_meta_list->y = light_obj->y;
      light_meta_list->z = light_obj->z;
      light_obj = light_obj->next;	// Go on to the next object

    light_meta_list = light_meta_list->next;	// Go on to the next metaobject with light objects

Calculating stable orbit

Calculating a stable orbit is essential for Gravitator. Without it it couldn't generate galaxies. The algorithm is fairly simple. At first, it must calculate the vector of the orbit axis from the orbit axis angle (the orbit axis is stored using the angles which are between it and world axis). Then if objects are selected it calculates the stable orbit for every one of the selected objects. If a metaobject is selected, 2 cases must be considered. If the objects in the metaobject are bound with gravity to one object in the metaobject (for example to the centre of galaxy), it calculates the stable orbit velocity only for the "cetral" object and changes the other objects' velocity according to this. If the objects in the galaxy are free (not gravity bound) then it calculates the stable orbit independently for every one of them. Here is the part of the code calculating the stable orbit:

	// Calculate the vector from the object to the orbited object
        obj_vector_x = obj->x - gui->misc->orbited_object->x;
        obj_vector_y = obj->y - gui->misc->orbited_object->y;
        obj_vector_z = obj->z - gui->misc->orbited_object->z;
	// Calculate the distance to the orbited object
        l = sqrt(obj_vector_x * obj_vector_x
                + obj_vector_y * obj_vector_y
                + obj_vector_z * obj_vector_z);
        r = l; // Remember the distance

	// Normalize the vector from the object to the orbited object
        obj_vector_x = obj_vector_x / l;
        obj_vector_y = obj_vector_y / l;
        obj_vector_z = obj_vector_z / l;

	// The direction vector must be perpendicular to the axis and to the vector from the object to the orbited object, so calculate it as 
	// a cross product
        dir_vector_x = obj_vector_y * axis_vector_z - axis_vector_y * obj_vector_z;
        dir_vector_y = obj_vector_z * axis_vector_x - axis_vector_z * obj_vector_x;
        dir_vector_z = obj_vector_x * axis_vector_y - axis_vector_x * obj_vector_y;

	// Normalize the direction vector
        l = sqrt(dir_vector_x * dir_vector_x
                + dir_vector_y * dir_vector_y
                + dir_vector_z * dir_vector_z);

        dir_vector_x = dir_vector_x / l;
        dir_vector_y = dir_vector_y / l;
        dir_vector_z = dir_vector_z / l;

        // Calculate the speed for a stable orbit
        vel = sqrt((gui->misc->orbited_object->mass + obj->mass)/ (obj->mass * r));

	// Change the direction vector to be as long as the speed
        dir_vector_x = dir_vector_x * vel;
        dir_vector_y = dir_vector_y * vel;
        dir_vector_z = dir_vector_z * vel;

	// Add the direction vector to object's velocity
        dir_vector_x = dir_vector_x + gui->misc->orbited_object->x_vel;
        dir_vector_y = dir_vector_y + gui->misc->orbited_object->y_vel;
        dir_vector_z = dir_vector_z + gui->misc->orbited_object->z_vel;

	// Set the object's velocity
        obj->x_vel = dir_vector_x;
        obj->y_vel = dir_vector_y;
        obj->z_vel = dir_vector_z;

Generating objects

Generating metaobjects in some form is done by simply randomly placing some objects and checking if they're inside the requested form (e.g. cylinder). More complex metaobjects as galaxies are created using the simpler ones. A disk galaxy is composed of 2 ellipsoids, one creates the centre of the galaxy and the second one creates the disk. An ellipsoid galaxy is just an ellipsoid. A spiral galaxy's center is created from an ellipsoid, and it's spirals are created from cylinders starting from the center with their diameter decreasing to the end. Every such a cylinder is called a segment. After generating the galaxy's shape, stable orbits are calculated for the galaxy's objects relatively to the galaxy center.