In this recipe, we will build on top of the previous recipe and add collision detection and response to the cloth model.
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.
Let us start this recipe by following these simple steps:
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());
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);
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.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);
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; }
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);
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);
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); } }
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; }
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:
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.
3.138.116.20