Implementing collision detection and response on a transform feedback-based cloth model

In this recipe, we will build on top of the previous recipe and add collision detection and response to the cloth model.

Getting ready

The code for this recipe is contained in the Chapter8/TransformFeedbackClothCollision directory. For this recipe, the setup code and rendering code remains the same as in the previous recipe. The only change is the addition of the ellipsoid/sphere collision code.

How to do it…

Let us start this recipe by following these simple steps:

  1. Generate the geometry and topology for a piece of cloth by creating a set of points and their connectivity. Bind this data to a buffer object as in the previous recipe.
  2. Set up a pair of vertex array objects and buffer objects as in the previous recipe. Also attach buffer textures for easier access to the buffer object memory in the vertex shader.
  3. Generate a transform feedback object and pass the attribute names that will be output from our deformation vertex shader. Make sure to relink the program again:
      glGenTransformFeedbacks(1, &tfID);
      glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tfID); 
      const char* varying_names[]={"out_position_mass", "out_prev_position"};  
      glTransformFeedbackVaryings(massSpringShader.GetProgram(), 2, varying_names, GL_SEPARATE_ATTRIBS);    
      glLinkProgram(massSpringShader.GetProgram());
  4. Generate an ellipsoid object by using a simple 4×4 matrix. Also store the inverse of the ellipsoid's transform. The location of the ellipsoid is stored by the translate matrix, the orientation by the rotate matrix, and the non-uniform scaling by the scale matrix as follows. When applied, the matrices work in the opposite order. The non-uniform scaling causes the sphere to compress in the Z direction first. Then, the rotation orients the ellipsoid such that it is rotated by 45 degrees on the X axis. Finally, the ellipsoid is shifted by 2 units on the Y axis:
      ellipsoid = glm::translate(glm::mat4(1),glm::vec3(0,2,0));
      ellipsoid = glm::rotate(ellipsoid, 45.0f ,glm::vec3(1,0,0));
      ellipsoid = glm::scale(ellipsoid,   
       glm::vec3(fRadius,fRadius,fRadius/2));
      inverse_ellipsoid = glm::inverse(ellipsoid);
  5. In the rendering function, bind the cloth deformation shader (Chapter8/ TransformFeedbackClothCollision/shaders/Spring.vert) and then run a loop. In each iteration, bind the texture buffers, and then bind the update vertex array object. At the same time, bind the previous buffer objects as the transform feedback buffers. Do the ping pong strategy as in the previous recipe.
  6. After the loop is terminated, bind the render VAO and render the cloth:
       glBindVertexArray(vaoRenderID[writeID]);
       glDisable(GL_DEPTH_TEST);
       renderShader.Use();
       glUniformMatrix4fv(renderShader("MVP"), 1, GL_FALSE,    
       glm::value_ptr(mMVP));
       glDrawElements(GL_TRIANGLES, indices.size(), 
       GL_UNSIGNED_SHORT,0);
       renderShader.UnUse();
       glEnable(GL_DEPTH_TEST);
       if(bDisplayMasses) {
          particleShader.Use();
          glUniform1i(particleShader("selected_index"),   
          selected_index);
          glUniformMatrix4fv(particleShader("MV"), 1, GL_FALSE,    
          glm::value_ptr(mMV));  
          glUniformMatrix4fv(particleShader("MVP"), 1, GL_FALSE, 
          glm::value_ptr(mMVP));
          glDrawArrays(GL_POINTS, 0, total_points);    
          particleShader.UnUse(); }
       glBindVertexArray( 0);
  7. In the vertex shader, obtain the current and previous position of the cloth vertex. If the vertex is a pinned vertex, set its mass to 0 so it would not be simulated, otherwise, add an external force based on gravity. Next, loop through all neighbors of the current vertex by looking up the texture buffer and estimate the internal force:
       float m = position_mass.w;
       vec3 pos = position_mass.xyz;
       vec3 pos_old = prev_position.xyz;  
       vec3 vel = (pos - pos_old) / dt;
       float ks=0, kd=0;
       int index = gl_VertexID;
       int ix = index % texsize_x;
       int iy = index / texsize_x;
       if(index ==0 || index == (texsize_x-1))   
          m = 0;
       vec3 F = gravity*m + (DEFAULT_DAMPING*vel);
       for(int k=0;k<12;k++) {
          ivec2 coord = getNextNeighbor(k, ks, kd);
          int j = coord.x;
          int i = coord.y;    
          if (((iy + i) < 0) || ((iy + i) > (texsize_y-1)))
             continue;
          if (((ix + j) < 0) || ((ix + j) > (texsize_x-1)))
             continue;
          int index_neigh = (iy + i) * texsize_x + ix + j;
          vec3 p2 = texelFetchBuffer(tex_position_mass,   
          index_neigh).xyz;
          vec3 p2_last = texelFetchBuffer(tex_prev_position_mass,  
          index_neigh).xyz;
          vec2 coord_neigh = vec2(ix + j, iy + i)*step;
          float rest_length = length(coord*inv_cloth_size);
          vec3 v2 = (p2- p2_last)/dt;
          vec3 deltaP = pos - p2;  
          vec3 deltaV = vel - v2;   
          float dist = length(deltaP);
          float   leftTerm = -ks * (dist-rest_length);
          float  rightTerm = kd * (dot(deltaV, deltaP)/dist);    
          vec3 springForce = (leftTerm + rightTerm)* 
          normalize(deltaP);
          F +=  springForce;  
       }
  8. Using the combined force, calculate the acceleration and then estimate the new position using Verlet integration. Output the appropriate attribute from the shader:
       vec3 acc = vec3(0);
      if(m!=0)
         acc = F/m; 
      vec3 tmp = pos; 
      pos = pos * 2.0 - pos_old + acc* dt * dt;
      pos_old = tmp; 
      pos.y=max(0, pos.y);   
  9. After applying the floor collision, check for collision with an ellipsoid. If there is a collision, modify the position such that the collision is resolved. Finally, output the appropriate attributes from the vertex shader.
      vec4 x0 = inv_ellipsoid*vec4(pos,1); 
      vec3 delta0 = x0.xyz-ellipsoid.xyz;
      float dist2 = dot(delta0, delta0);
      if(dist2<1) {  
        delta0 = (ellipsoid.w - dist2) * delta0 / dist2; 
        vec3 delta;
        vec3 transformInv = vec3(ellipsoid_xform[0].x, 
        ellipsoid_xform[1].x, 
        ellipsoid_xform[2].x);
        transformInv /= dot(transformInv, transformInv);
        delta.x = dot(delta0, transformInv);
        transformInv = vec3(ellipsoid_xform[0].y, 
        ellipsoid_xform[1].y, 
        ellipsoid_xform[2].y);
        transformInv /= dot(transformInv, transformInv);
        delta.y = dot(delta0, transformInv);
        transformInv = vec3(ellipsoid_xform[0].z,     
        ellipsoid_xform[1].z, ellipsoid_xform[2].z);
        transformInv /= dot(transformInv, transformInv);
        delta.z = dot(delta0, transformInv); 
        pos +=  delta ; 
        pos_old = pos;  
      }
      out_position_mass = vec4(pos, m);  
      out_prev_position = vec4(pos_old,m);        
      gl_Position = MVP*vec4(pos, 1); 

How it works…

The cloth deformation vertex shader has some additional lines of code to enable collision detection and response. For detection of collision with a plane, we can simply put the current position in the plane equation to find the distance of the current vertex from the plane. If it is less than 0, we have passed through the plane, in which case, we can move the vertex back in the plane's normal direction.

void planeCollision(inout vec3 x,  vec4 plane) {
   float dist = dot(plane.xyz,x)+ plane.w;
   if(dist<0) {
     x += plane.xyz*-dist;
   }
}

Simple geometric primitive, like spheres and ellipsoids, are trivial to handle. In case of collision with the sphere, we check the distance of the current position from the center of the sphere. If this distance is less than the sphere's radius, we have a collision. Once we have a collision, we push the position in the normal direction based on the amount of penetration.

void sphereCollision(inout vec3 x, vec4 sphere)
{
   vec3 delta = x - sphere.xyz;
   float dist = length(delta);
   if (dist < sphere.w) {
       x = sphere.xyz + delta*(sphere.w / dist);
   }
}

Tip

Note that in the preceding calculation, we can avoid the square root altogether by comparing against the squared distance. This can provide significant performance gain when a large number of vertices are there.

For an arbitrarily oriented ellipsoid, we first move the point into the ellipsoid's object space by multiplying with the inverse of the ellipsoid's transform. In this space, the ellipsoid is a unit sphere, hence we can then determine collision by simply looking at the distance between the current vertex and the ellipsoid. If it is less than 1, we have a collision. In this case, we then transform the point to the ellipsoids world space to find the penetration depth. This is then used to displace the current position out in the normal direction.

vec4 x0 = inv_ellipsoid*vec4(pos,1); 
vec3 delta0 = x0.xyz-ellipsoid.xyz;
float dist2 = dot(delta0, delta0);
if(dist2<1) {  
delta0 = (ellipsoid.w - dist2) * delta0 / dist2;
vec3 delta;
vec3 transformInv = vec3(ellipsoid_xform[0].x, ellipsoid_xform[1].x, ellipsoid_xform[2].x);
transformInv /= dot(transformInv, transformInv);
delta.x = dot(delta0, transformInv);
transformInv = vec3(ellipsoid_xform[0].y, ellipsoid_xform[1].y, ellipsoid_xform[2].y);
transformInv /= dot(transformInv, transformInv);
delta.y = dot(delta0, transformInv);
transformInv = vec3(ellipsoid_xform[0].z, ellipsoid_xform[1].z, ellipsoid_xform[2].z);
transformInv /= dot(transformInv, transformInv);
delta.z = dot(delta0, transformInv); 
pos +=  delta ; 
pos_old = pos;  
}

There's more…

The demo application implementing this recipe renders a piece of cloth fixed at two points and is allowed to fall under gravity. In addition, there is an oriented ellipsoid with which the cloth collides as shown in the following figure:

There's more…

Although we have touched upon basic collision primitives, like spheres, oriented ellipsoids, and plane, more complex primitives can be implemented with the combination of these basic primitives. In addition, polygonal primitives can also be implemented. We leave that as an exercise for the reader.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.116.20