Contents
Introduction
Author: Movania Muhammad Mobeen
Hello readers, In this article, we will learn how to port the 11th tutorial on Physics Dynamics, Acceleration and Force in OpenGL 3.3. We saw in the last tutorial how to use the SDL framework to handle our window management. We will put the greater flexibility offered by SDL to good use by doing realtime physics calculation. The OpenGL rendering code, the geometry management and shaders for the meshes are identical to the last tutorial so we would not discuss them again here. The new additions to this tutorial are at two places:
- Modification of the main loop to enable realtime physics
- Functions to apply dynamics on the object
Modification of the main loop to enable realtime physics
Timing is critical for any simulation software. Realizing this, we have added new variables to main.cpp:
// FPS calculation and time based physics unsigned int fps_physics=0; unsigned int fps_rendering=0; unsigned int set_fps_physics=100; unsigned int remaining_time=0; unsigned long fps_count_physics=0; unsigned long fps_count_rendering=0; unsigned long last_ticks=0; int msecxdframe=1000/set_fps_physics;
These variables and their purpose are given in the following table:
New variables and their purpose
Variable | Purpose |
---|---|
fps_physics | For timing of physics calcualtion |
fps_rendering | For rendering of fps on display |
set_fps_physics | For sustained physics performance |
remaining_time | Remaining time for time based physics |
fps_count_physics | FPS for physics engine |
fps_count_rendering | FPS for rendering engine |
last_ticks | Number of ticks since last frame so that delta time could be obtained |
msecxdframe | Milliseconds we want for each physics frame |
The main loop is modified by adding new variables for storing the current, last and elapsed time:
void MainLoop(void) { int i; // Counters unsigned long l_start_time; // Start time for time based physics unsigned long l_elapsed_time; // Elapsed time for time based physics unsigned int l_frame_time; // Frame time for time based physics
Next, the frameworks events are handled:
// Events FrameworkEvents(); // Process incoming events
This ensures that the application remains responsive to the user's input as well as the common window events and it does not stall. Next, the current time is obtained (using Framework_GetTicks function) and the display function (RenderDisplay) is called. Then, the elapsed time is calculated. This also gives us the time taken for the current frame (l_frame_time) and the remaining time (remaining_time) for physics calculation:
// Elapsed time calculation l_elapsed_time=Framework_GetTicks()-l_start_time+remaining_time; // Elapsed time (we add also the previous remaining time) l_frame_time=l_elapsed_time / msecxdframe; // Frames quantity we must cycle for physics remaining_time=l_elapsed_time % msecxdframe; // Get the remaining time because we are working with integer values
Next, the physics loop is run which loops until all of the current frame's render time is used. It increases the current physics fps. Then, it loops through each object and applies drag and dynamics forces on it:
// Physics while (l_frame_time-->0) // Now do physics as many times as we need to match the elapsed time of the rendering phase { fps_count_physics++; // Increase physics FPS counter for(i=0;i< obj_qty;i++) // { ObjDrag(&object[i]); // Add Drag ObjDynamics(&object[i],(float)1.0/(float)set_fps_physics); // Do dynamics } }
Finally, the fps for display and physics calculation are recalculated and the counters are reset for the next frame:
// Rendering and Physics FPS calculation if ((Framework_GetTicks()-last_ticks)>=1000) // Every second { last_ticks = Framework_GetTicks(); // Save the current ticks to catch the next second // Assings the current FPS count to the global variables fps_physics=fps_count_physics; fps_rendering=fps_count_rendering; // Clear the local counters fps_count_physics = 0; fps_count_rendering = 0; }
Note that all of the functions calculating the physics like ObjDrag, ObjDynamics etc. are based on physical laws and we are not discussing them here since this information is well desribed in elementary physics text.
Handling of the object's axially aligned bounding box (AABB)
When the mesh is loaded, the object's axially aligned bounding box (AABB) is calculated in the ObjCalcBSphere function. This function is called from ObjLoadFromIni function at initialization. The AABB is stored as an object attribute along with the object's other attributes like the object's transformation matrix. After the aabb has been calculated, we create space to store the aabb so that we may render them later. As before, VAOs come to our rescue. We create a new VAO id for each object and store it in a vector called vaoAABBIDs since we may not know the actual number of objects. Similarly we create vboAABBIDs for storing the aabb vertex positions:
//allocate space for the vaos and vbos vaoIDs.resize(obj_qty); vboIDs.resize(obj_qty*4); vaoAABBIDs.resize(obj_qty); vboAABBIDs.resize(obj_qty);
Once we have successfully generated our vectors, we call the InitVAO function. This function as before loads the vertex positions, texture coordinates and normals into the VBO. In addition, now it also binds the new AABB vao (vaoAABBIDs) and pushes the AABB vertex positions. Note that these vertex positions are in object space:
//now attach the AABB vao and dump the aabb vertices glBindVertexArray(vaoAABBIDs[i]); glBindBuffer (GL_ARRAY_BUFFER, vboAABBIDs[i]); glBufferData (GL_ARRAY_BUFFER, sizeof(GLfloat)*3*8, &object[i].aabb, GL_STATIC_DRAW); glEnableVertexAttribArray(aabb_shader["vVertex"]); glVertexAttribPointer (aabb_shader["vVertex"], 3, GL_FLOAT, GL_FALSE,stride,0); glBindVertexArray(0);
Note that we have a new shader (discussed later) for our AABB since we do not want to texture the AABB vertices and are only interested to color them with a constant color.
AABB shaders
The vertex shader for the AABB is exactly the same as the previous tutorials and it should be easy to undertand now:
//Vertex shader #version 330 in vec3 vVertex; uniform mat4 MVP; void main() { gl_Position = MVP*vec4(vVertex,1); }
The fragment shader has been simplified as follows:
//Fragment shader #version 330 uniform vec4 Color; out vec4 vFragColor; void main(void) { vFragColor = Color; }
We pass a new uniform (Color) which is passed when the shader is used. This way we can modify the color from the client code to assign a different color as we need.
AABB shader loading
As earlier, we create a new GLSLShader object (aabb_shader) and then we load the shader using the aabb_shader.LoadFromFile function as follows:
aabb_shader.LoadFromFile(GL_VERTEX_SHADER, "shaders/aabb_shader.vert"); aabb_shader.LoadFromFile(GL_FRAGMENT_SHADER, "shaders/aabb_shader.frag");
Once the aabb_shader is successfully compiled and linked, we set the shader attributes and uniforms:
aabb_shader.CreateAndLinkProgram(); aabb_shader.Use(); aabb_shader.AddAttribute("vVertex"); aabb_shader.AddUniform("MVP"); aabb_shader.AddUniform("Color"); aabb_shader.UnUse();
In the rendering code, after the ith mesh is rendered, its AABB is rendered as follows:
//draw the aabb points aabb_shader.Use(); #ifdef USE_GLM glUniformMatrix4fv(aabb_shader("MVP"), 1, GL_FALSE, glm::value_ptr(MVP)); #else glUniformMatrix4fv(aabb_shader("MVP"), 1, GL_FALSE, &MVP[0][0]); #endif glUniform4fv(aabb_shader("Color"),1, white); glBindVertexArray(vaoAABBIDs[i]); glDrawArrays(GL_POINTS, 0, 8); glBindVertexArray(0); aabb_shader.UnUse();
The first few lines use the aabb_shader and then set the combined modelview projection matrix based on the current math library being used. Then, the AABB shader's color uniform is set to a white color (defined earlier as GLfloat white[4]={1,1,1,1}). Next, the AABB's VAO is bound and then 8 GL_POINTS are rendered. Finally, the VAO is unbound and the shader is un used.
Choosing between GLUT framework or SDL framework
The code in this and the subsequent tutorials has two frameworks to choose from. The glut framework that we developed in the last tutorial and the SDL framework that we developed in this tutorial. To use GLUT framework, you need to define the preprocessor FRAMEWORK_GLUT in your compiler setting. In Visual Studio 2008, you can go to Project -> Properties -> Configuration Properties-> C/C++ ->Preprocessor and add FRAMEWORK_GLUT to the preprocessor definitions. To enable SDL framework, define FRAMEWORK_SDL. Note that you can only use one framework at a time and not both. In addition, if you want to use the GLM matrix library, add the preprocessor USE_GLM otherwise the spacesimulator.net's matrix library is used. Running the code gives us the following output. You may press the 'w','a','s','d' keys along with several other keys to transform the camera and see the result:
SOURCE CODE
The Source Code of this lesson can be downloaded from the Tutorials Main Page