Hey all,

For the starting programmers among us, here is a fun project for you to do. This workshop is written for programmers that have some basic knowledge about lite-c, structures and pointers (you don't need too much knowledge wink ). I hope my explanations give you sufficient understanding in how to implement a concept or idea into working code. Feel free to give feedback and show your results! laugh. This is part 01, as I plan to write more if you find it useful.


Let's research 01: Gravity simulation!


Goal:
an application that shows n bodies moving around with various masses, positions and initial velocities according to the laws of physics.


Aproach:
We'll construct three source files:
  • gravity_simulation.c has the main, and a while loop to control the timelapse of the simulation
  • space_gravity.c does all the calculations, controlling the mass bodies in the universe
  • list.c is a linkedlist datastructure


We will use MDL_SPHERE entities as stars, and generate n entities with random mass and positions. We want to try and avoid using arrays as they have a fixed size and are not easily expandable if we want more stars than anticipated. Rather, we use a linkedlist to keep track of all our mass bodies. A linkedlist has a dynamic size; we don't need to worry about how many bodies we already made and are still able to walk through the list.

Here is the linkedlist code:
Click to reveal..
Code:
//list.c

//a list (node) structure, containing a subject (of type void*) and a pointer to the next list node
typedef struct List
{
	void* object;
	struct List* next;
} List;


//this method adds an object to the list, by creating a new list node and prepending it to the root list node
void list_add_object( List* root, void* object )
{
	List* list = sys_malloc( sizeof( List ) );
	list->object = object;
	list->next = root->next;
	root->next = list;
}

//removes a node containing the given object from the list
void list_remove_object( List* root, void* object )
{
	List* prev;
	while( root->next )
	{
		prev = root;
		root = root->next;
		
		if( root->object == object )
		{
			prev->next = root->next;
			sys_free( root );
		}
	}
}




Next up is gravity_simulation.c.
  • Create a main where you load an empty level (level_load(NULL)).
  • Put your camera above the level (about 3000 quants), and tilt it downwards so you look down on the nullvector.
  • Now create a for-loop with 200 iterations.
  • In there, use: ent_create( SPHERE_MDL, nullvector, NULL );
  • Give this entity a random position on x and y with a range of -500 to 500 (optionally also z for a cube of stars instead of a 2D plane).
  • Create am infinite while loop with a wait(1);. For now it'll stay empty.


Right, this should give us an empty level with 200 SPHERE_MDL models on random positions. So how are we going to give them a mass and calculate their heading according to gravity laws?


On to space_gravity.c!
We will make 3 functions in space_gravity.c. I precede function names in here with sg_, so we are reminded which source file the functions originate from.

3 functions:
  • sg_init initializes the list of mass objects
  • sg_add_mass_object adds a new mass object to the list
  • sg_update updates all mass objects in the list with a new position


There's also one structure that acts as the mass object:
Code:
typedef struct
{
	int mass;
	VECTOR* pos;
	VECTOR velocity;
	VECTOR acceleration; //helps in the calculation to determine change in speed	
} SgMassObject;



This structure contains an integer for the mass, a reference to a position to be updated, a velocity vector to determine speed, and an acceleration vector acting as the change in speed.


There's one last global variable to make in space_gravity.c: a list of mass objects. Under the structure we write: List sg_mass_list;. This List variable must be initialised before starting the simulation, and that's where we use sg_init() for.
In sg_init() we write:
Code:
sg_mass_list.object = 0;
sg_mass_list.next = 0;


That is all we do in sg_init(). Call sg_init() in your main function before the for-loop.


The next function is sg_add_mass_object( VECTOR* pos, int mass ). As the arguments suspect, we give a position for the algorithm to alter, and a mass to calculate with. Additionally we can add an initial velocity too in the arguments. Experiment with it later laugh.
In sg_add_mass_object:
  • Use SgMassObject mass_object = sys_malloc( sizeof( SgMassObject ) ); to allocate a new structure of SgMassObject.
  • Set mass and pos to the given arguments
  • Set velocity and acceleration on nullvector using vec_set
  • Now add our mass_object to the list by writing: list_add_object( &sg_mass_list, mass_object );

That's all we need to do in the sg_mass_add_object function.

We can now go back to our main function in gravity_simulator.c. In the for loop where we create the entities, call sg_add_mass_object like this: sg_add_mass_object( &(entity->x), mass ); where mass can be a random number (even negative!). We use the ampersand to create a reference to the x position of the entity, treating it correctly as a vector. We now have the entity's position stored in sg_mass_list, and can control it just using that vector without having the entity at all!

Back to space_gravity.c, we now need to create the hardest part. sg_update() will do the calculations of each mass object in the list and control the position according to the result. We plan the next steps:
  • Walk through the list and retrieve the mass object as mass_object (see below)
  • In the loop, walk through the list again from start and retrieve the mass object as mass2_object
  • if both objects are equal, skip code and continue to the next object in the (second) list
  • With the two objects, we use the gravitation formula F = ( G * M1 * M2 ) / ( r * r )
  • G = 6.674, M1 and M2 is the mass of each mass_object, r = distance between the two mass_objects (vec_dist), F = force
  • only do this if r > 4, where 4 can be adjusted to control peek speeds when objects come too close
  • Now we have F, we can calculate the acceleration using F = M * A, translated A = F / M where M = mass of the first object and A = an acceleration variable.
  • Determine the direction from the first mass object to the second, and normalize it using the acceleration variable (see below).
  • Add the resulting vector to the acceleration vector (of the struct) of the first mass entity
  • keep repeating these points until all mass objects in the list have their acceleration vector calculated


Click to reveal..

We can walk through the list like this:
Code:
List* mass_list = &sg_mass_list;
while( mass_list->next )
{
	mass_list = mass_list->next;
	
	SgMassObject* mass_object = mass_list->object;
	...
}



We can determine the direction of one position to the other like this:
Code:
VECTOR* tempvec = vector( 0, 0, 0 );
vec_set( tempvec, mass2_object->pos );
vec_sub( tempvec, mass_object->pos );

//we normalize the direction to get a unit direction vector (values -1 to 1), and multiply it by the acceleration
vec_normalize( tempvec, force / mass_object->mass ); //A = F / M




After the while loops of the algorithm, we make yet another while loop to walk through the entire list again. In this loop we add the acceleration vector of each mass object to its velocity vector using vec_add. Then we add the velocity vector to the mass object's position after multiplying by time_step:
Code:
mass_object->pos->x += mass_object->velocity.x * time_step;
//Same for y and z


Last thing to do in space_gravity.c: reset the acceleration vector of the mass object to nullvector using vec_set.


One more thing to do in our main while loop... and that's calling sg_update();!


It should work now if you did everything right laugh.
You learn best by trying yourself, type your own code and don't copy too much. Nevertheless, if you're completely stuck on something here are the working source files:
gravity_simulation.c, space_gravity.c, list.c
But consider it cheating using them wink.

Have fun!
Joozey




Click and join the 3dgs irc community!
Room: #3dgs