GELATIN

The goal of this project was to extend the "Fabricator" project into more rigid bodies. The general idea is that ALL 3d Objects could be treated as particle systems, and so ALL could be, in some way, considered to be a 'soft body'. Next step would be to treat LIGHT as a particle system, and build a Verlet-style raytracer . . .
Note: For basics of Verlet Integration and Per-Face drawing please see the "fabricator" example.

.obj Import

In order to mess around with object vertices I need to import 3D objects. A .obj file is a great candidate because it is just a text file with the following information (amongst other things like materials which I am ignoring):


v - vertex x, y, and z coordinates
vn - vertex normals
vt - texture coordinates for each vertex
f - face index -- each number refers to a vertex

I created an OBJ class that parses this file and stores the information in separate vectors.

class Obj : public ParticleSystem {
  vector verts;
  vector normals;
  vector faces;
  vector facemap;
  vector facenorm;
  vector map;
  Vector3f *n;
  POLY *f;
  UV *m;

I still need to work on making appropriate use of the "facenorm" and "facemap" vectors -- these would, in principle, help in texture-mapping and lighting. They, like the "faces" vector, are merely four integers that relate to the index of values in the "verts" and "normals" vectors. Okay, but in the meantime we don't need them.
The "*n" and "*f" and "*m" members of the OBJ class are arrays that I will transfer the vectors into after parsing (for faster data manipulation later)
Here's the rest of the class -- it's public members:


public:
  Obj();
  void loadObjFile(string filename);
  void setPinVertices();
  void draw(int mode);

  void jiggle();
  void inflate();
  void experiment();
  void reset();
  void averagePin();
  void starterPin();
  void scale(float d);
  void inflate2();
};

'loadObjFile' does the parsing -- and one other thing which I'll get to. All those 'jiggle', 'inflate', and 'experiment' type functions mess around with the values in the "v" (vertex values) array of Vector3f. The "pinning" functions: 'setPinVertices', 'averagePin', and 'starterPin' all effect WHICH vertex number I consistently pin after going through the Verlet Integration technique. For instance, 'averagePin' allows me to change the percentage of vertices that are pinned in an object, and thereby affect its jiggly-ness. 'reset' resets the array back to the original vector (yup, it sticks around).

Obj :: loadObjFile(string filename)

Alright something else happens inside this function after we've loaded the object vertex coordinates into an array of Vector3fs, and after we've created an array of faces with four integers that specify indexes within the vertex array (I am assuming ALL faces are quads). Basically, I want to store the distance between the vertexes of each face. Why? So I can apply that distance as a constraint between the two particles (er, vertexes). For each face I calculate the distance between vertex a and b, b and c, c and d, and d and a. For each calculation, I create a constraint. So, for each FACE[i] I do this:

int a = f[i].a;
int b = f[i].b;
int c = f[i].c;
int d = f[i].d;
Vector3f cVec;
cVec.dVector(&cur_pos[ a ], &cur_pos[ b ]);
float con = cVec.vLength();

constraints[it].pA = a;
constraints[it].pB = b;
constraints[it].length = con;
it++;

cVec.dVector(&cur_pos[ f[i].b ], &cur_pos[ f[i].c ]);
con = cVec.vLength();

constraints[it].pA = b;
constraints[it].pB = c;
constraints[it].length = con;
it++;

cVec.dVector(&cur_pos[c], &cur_pos[d]);
con = cVec.vLength();
constraints[it].pA = c;
constraints[it].pB = d;
constraints[it].length = con;
it++;

cVec.dVector(&cur_pos[d], &cur_pos[a]);
con = cVec.vLength();
constraints[it].pA = d;
constraints[it].pB = a;
constraints[it].length = con;
it++;

I hope that helps . . .

Pinning

Here's how I pin different vertices each time I call the averagePin function. Notice I clear the "pinned vertices" vector called 'pinvert' every time.

void Obj :: averagePin(){
 static int step = 0;
 if ( step < verts.size() ) step++;
 else step = 1;
 pinvert.clear();
 for (int i = 1; i < verts.size(); ++i){
   if ( ( i % step ) == 0 ) pinvert.push_back(i);
 }
 setPinVertices();
}

and setPinVertices looks like this:

void Obj :: setPinVertices(){
 if (pv) delete pv;
 if (pvc) delete pvc;
 //'pv' is the array of vertice INDEX numbers.
 //'pvc' is there actual position.

 num_pinned = pinvert.size();
pv = new int[num_pinned];
 pvc = new Vector3f[num_pinned];

 for(int i = 0; i < num_pinned; ++i){
   pv[i] = pinvert[i];
   pvc[i] = cur_pos[ pinvert[i] ];
 }
}

does that help? if not email me . . . the pinning of vertices is tricky and I could implement it better. I don't like having two arrays -- one of indexes and another of values. This should be collapsed into one, I think.

Keyboard Control

Finally, I should say something about the keyboard control. To understand the best way I found of implementing this, consider the begging of the Obj::draw(int mode) function. By the way, the (int mode) part of the draw function is just to check whether the program should draw quads, lines, points, or whatever.
//Glut's Keyboard Listener excerpt: //note that 's.' is 'Scene s'
Switch(key){
. . .
 case 'E':
 case 'e':
  s.generalListener(3);
  break;
. . .
}
//From the Scene Class:
void Scene :: generalListener(int code){
 ParticleSystem* p = vecObj [ activeObject ];
 switch (code) {
...
   case 3:
    if ( p -> experimenting == 1 ) p -> experimenting = 0;
    else p -> experimenting = 1;
    break;
...
}

Some Related Studies/Jokes

This one's from FREEPATENTSONLINE.COM "The present invention relates to a dairy product and more particularly to dairy products containing soft inlays of substantially non-melting low solids hydrocolloid gels designed to simulate the texture of gelatin but not to dissolve and/or leak color and flavor into the dairy product when subjected to elevated temperature and to processes for preparing such inlays and resulting dairy products."