For my project, I decided to implement metaballs!
When watching the many exciting demos in class, I was most intrigued by the concept of metaballs and, therefore, wanted to explore them in my final project. I was able to implement a
CubeGrid, which constituted the main part of the underlying code of these metaballs. In particular, the concept of implicit surfaces was not yet something we had explored in mathematic depth, so when reading the sources I used to prepare for implementation (see "Sources I Used" below), it was quite fascinating to learn exactly how to introduce this into the scenegraph that we'd built over several past assignments.
I wanted to incorporate as many additional features are possible so a user can interact with the metaballs and see how they act under different parameters. Besides the implementation of the metaballs, which was, of course, the trickiest part of this project, here are a few other features I implemented.
Implementing this feature was very similar to Asst 7. I used the same animation technique with
glutTimerFunc to update positions of each metaball. Their position was determined based on trigonometric functions. You an increase the speed of the animation using the key command + and slow it down using the key command -.
This was a feature I at first thought I wouldn't be able to implement, given the nature of the
g_gridCubeGeometry being treated as one shape to draw. However, I discovered that I could pass an individual color into a custom fragment shader to make it appear as if each metaball were separate from the next, even if they were draw using the same material.
I created a vertex shader and fragment shader pair called
metaball-gl3.fshader respectively, which acted similar to the
specular-gl3.fshader. Then I created a new material
g_balltexMat that used these shaders, similar to
g_shinyMat. However, it takes in the variable
aColor, which it pulls from a new geometry
SimpleGeometryPNC, made up of a new type of vertex
VertexPNC. This matched the format of
VertexPNX except instead of taking in texture coordinates, it takes a
Cvec3 color directly in as a property.
Then, as I built the
g_cubeGridGeometry from the surface vertices of the
CubeGrid I calculated what the closest metaball origin to that particular surface vertex, using a simple distance formula. From this, I created a relationship between the vertex and a specific metaball index and determined the color to pass to the shaders from this.
This was not particularly complicated. I just had a global variable,
g_radius, which could be incremented using the keyboard command r to increase adn e to decrease. When this was triggered, the metaballs had to be updated and re-rendered, so the
CubeGrid would be called gain and interpreted to render the implicit surfaces.
Since I saved the metaballs in a global vector of
Metaball, they could vary in number quite easily using the C++ std::vector functions. I capped them at 10, otherwise the program begins to run pretty slowly. Similar to the radius-changing, this particular feature required the entire
CubeGrid to be called again, so that the implicit surfaces could be calculated and re-drawn.
This was particular confusing because I could not use the
Picker from the previous assignments to simply pick a metaball. This, of course, is due to the fact that metaballs are implicit surfaces derived from the
CubeGrid, so they do not actually exist as separate entities in the scenegraph. My workaround included the idea that you must first pause the animation, or "pulsation" as I call it, in order to select an individual metaball using the key command q. This immediately shows the arcball over the first metaball. In order to cycle through the metaballs to select the one of your liking, you use the key command p, which will move the metaball. Rotation is obsolete in this scenario with the metaballs. Instead, it is only relevant to translate the metaballs because we are essentially only moving the metaball center, defined as
Metaball.position. Therefore the right mouse click and middle mouse click (or spacebar-left click) are the only actions that garner response from the metaballs. You can move the metaball to a new location and then press q to restart the animation.
I thought it would be fun to change the texture of the floor, so I downloaded a texture from a blog (see "Sources I Used") and put that on the ground plane.
One thing I tried but then avoided because I did not want to mess up the project I already had before submission was the implementation of texture mapping onto the metaballs. I did a lot of research on the matter, and I wasn't able to find much about UV mapping onto 3d metaballs. I did find such for a sphere, but it did not work when I tried it out, so I decided to abandon this task.
I also was trying to create a system where there was better blending of color between two metaballs. In particular, I wanted to track the second closest metaball, just as I tracked the first closest. Then I would create a weighted ratio between the two that would make for a blended color for that particular vertex. I have commented out a few lines of trial code that did not quite work for me. (What ended up happening was all the metaballs would just gradually turn yellow, which didn't seem really exciting, though I guess they were blending.)
If I had more time, I wanted to explore the concept of refraction. In particular, I believe refracting metaballs would be an amazing thing to implement in the future because they would resemble bubbles!
I thoroughly enjoyed the process of this project. It was certainly a bit of a daunting task because I didn't want to start anything too ambitious and end up with nothing to submit; therefore, I was a bit scared to tackle metaballs, which was a foreign topic that I was not sure I could handle. Luckily, it worked out in the end.
Reading through articles on metaballs and marching cubes were perhaps where I learned the most. It was fascinating to see how such an organic object can be built from something as rigid as a 3-dimensional lattice.
As mentioned earlier, it was definitely a challenge dealing with something that appeared visually like it fit in with other objects we'd built in the past but in reality was quite different because of the nature of its derivation.
While researching things to try and implement, like texture mapping to the metaballs or color blending, I learned about these concepts a bit more as well.
In my final-project folder, you can run the program with the following commands. (Note: I built this on Mac OS X.)
$ make clean $ make OPT=1 $ ./final
Once you have the program running, you can use the following keyboard commands to interact with the metaballs:
|h||Show Help Menu|
|a||Toggle Arcball Display|
|m||Add 1 Metaball|
|n||Remove 1 Metaball|
|r||Increase Metaball Radius|
|e||Decrease Metaball Radius|
|q||Start/Stop Metaball Pulsation|
|p||Toggle Between Metaball Picking|
|+||Speed Up Animation|
|-||Slow Down Animation|