Contact using lagrange multiplier for deformable body #4415
-
|
Hi Hugo, @hugtalbot Thanks for your post in #3812. I have further question to your answer in this post about getting the contact force. Constraint matrix H is the constraint Jacobian matrix containing the constraint directions, i.e. the contact directions. This matrix is the Jacobian of the transformation between the world coordinate system and the constraint space. SO BY runing your code, I can get the contact force F. This code works for the rigid cube in #4225. I have successfully tested it. However, when it comes to a soft deformable object contact, the Jocobian matrix H has more than one contact direction for each element in λ. For example, if the λ is [1, 2, 3] N, you have the following Jocobian matrix in #3812. For each row, it is a constraint direction, but it has two array for each row, e.g. 1st line, it has [0.487691 -0.305337 0.146266], [ 0.333767 -0.208967 0.100102]. Which array you are using to multiply λ[1], which is [1]N? For 2nd row, which array you are using to multiply λ[2], which is [2] N? The code constraint = self.rootNode.Cube.collision.MechanicalObject.constraint.value
dt = self.rootNode.dt.value
constraintMatrixInline = np.fromstring(constraint, sep=' ')
pointId = []
constraintDirections = []
index = 0
i = 0
forcesNorm = self.rootNode.GCS.constraintForces.value
contactforce_x = 0
contactforce_y = 0
contactforce_z = 0
constraintDirections = np.zeros((1,1))
if len(constraintMatrixInline) > 0:
constraintDirections = np.zeros((int(len(constraintMatrixInline)/6),3))
while index < len(constraintMatrixInline):
nbConstraint = constraintMatrixInline[index+1]
pointId = np.append(pointId, constraintMatrixInline[index+2])
constraintDirections[i][0] = constraintMatrixInline[index+3]
constraintDirections[i][1] = constraintMatrixInline[index+4]
constraintDirections[i][2] = constraintMatrixInline[index+5]
index = index + 6
i = i + 1
nbDofs = len(self.rootNode.Cube.collision.MechanicalObject.position.value)
forces = np.zeros((nbDofs,3))
print('pointid', pointId)
for i in range(int(len(constraintMatrixInline)/6)):
indice = int(pointId[i])
forces[indice][0] = forces[indice][0] + constraintDirections[i][0] * forcesNorm[i] / dt
forces[indice][1] = forces[indice][1] + constraintDirections[i][1] * forcesNorm[i] / dt
forces[indice][2] = forces[indice][2] + constraintDirections[i][2] * forcesNorm[i] / dt
print('indice', i, indice)
for i in range (nbDofs):
contactforce_x += forces[i][0]
contactforce_y += forces[i][1]
contactforce_z += forces[i][2]
print('nbDof', nbDofs)
print('force', forces)
print('contactforce', contactforce_x, contactforce_y, contactforce_z)
if len(constraintMatrixInline) > 0:
self.rootNode.drawNode.drawForceFF.indices.value = list(range(0,nbDofs,1))
self.rootNode.drawNode.drawForceFF.forces.value = forces
self.rootNode.drawNode.drawPositions.position.value = self.rootNode.Cube.collision.MechanicalObject.position.valueonly considers the first array for each row, which would not be applicable if there are two array in each raw. Thanks for your time. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 7 replies
-
|
The main difference between the soft body contact and the rigid example given by @hugtalbot is that soft bodies collision models are sometimes more than just points. Some times you want to use higher degrees elements such as edges, triangles, quats. In those cases, the contact point may arise inside of the element and not exactly on one of its points. When this happens, in order to ensure a mechanically sound behavior, the contact force is distributed on every points of the element (three for the triangle, two for the edge etc...). This distribution is not homogeneous but take into account the distance between the application point and each point of the element using barycentric coordinates. Here in your example, because you have two points affected by each constrain, you are certainly using edges for you collision. The two normals given by constraints are the normals used to apply the contact force to the corresponding point of the element. As I told you, the forces are distributed, in fact the two normal are collinear and have the same direction, only the norms differs and are related to the barycentric coordinates. But you don't mind about that, what is important is that, the force applied at the point 58 by the constraint 0 in your example is Now for the script, a more generic way of going through the H matrix would be to take into account the number of point affected by the constraint and loop over it. It would look more like that (I didn't run it on my side, but it should work fine) : constraint = self.rootNode.Cube.collision.MechanicalObject.constraint.value
dt = self.rootNode.dt.value
constraintMatrixInline = np.fromstring(constraint, sep=' ')
pointId = []
constraintId = []
constraintDirections = []
index = 0
i = 0
forcesNorm = self.rootNode.GCS.constraintForces.value
contactforce_x = 0
contactforce_y = 0
contactforce_z = 0
while index < len(constraintMatrixInline):
nbConstraint = int(constraintMatrixInline[index+1])
currConstraintID = int(constraintMatrixInline[index])
for pts in range(nbConstraint):
currIDX = index+2+pts*4
pointId = np.append(pointId, constraintMatrixInline[currIDX])
constraintId.append(currConstraintID)
constraintDirections.append([constraintMatrixInline[currIDX+1],constraintMatrixInline[currIDX+2],constraintMatrixInline[currIDX+3]])
index = index + 2 + nbConstraint*4
nbDofs = len(self.rootNode.Cube.collision.MechanicalObject.position.value)
forces = np.zeros((nbDofs,3))
print('pointid', pointId)
print('constraintId', constraintId)
for i in range(len(pointId)):
indice = int(pointId[i])
forces[indice][0] = forces[indice][0] + constraintDirections[i][0] * forcesNorm[constraintId[i]] / dt
forces[indice][1] = forces[indice][1] + constraintDirections[i][1] * forcesNorm[constraintId[i]] / dt
forces[indice][2] = forces[indice][2] + constraintDirections[i][2] * forcesNorm[constraintId[i]] / dt
print('indice', i, indice)
for i in range (nbDofs):
contactforce_x += forces[i][0]
contactforce_y += forces[i][1]
contactforce_z += forces[i][2]
print('nbDof', nbDofs)
print('force', forces)
print('contactforce', contactforce_x, contactforce_y, contactforce_z)
if len(constraintMatrixInline) > 0:
self.rootNode.drawNode.drawForceFF.indices.value = list(range(0,nbDofs,1))
self.rootNode.drawNode.drawForceFF.forces.value = forces
self.rootNode.drawNode.drawPositions.position.value = self.rootNode.Cube.collision.MechanicalObject.position.valueDon't hesitate to give me feedback on how it worked and if you had to fix it. |
Beta Was this translation helpful? Give feedback.
-
|
Hello, to something like this: So I tried to update the code accordingly, however, I have a different output, the force vectors are drawn in the wrong position and the contact force values are different. ` def parse_constraint_data(constraint_str): ` what am I doing wrong? |
Beta Was this translation helpful? Give feedback.
-
|
Hi guys @shizi-19 @zhanglepy @bhattner143 @Slykkey For your information, I just created an example as a PR in the SofaPython3 repository describing how to easily access (and visualize) contact forces, when using the response method "FrictionContactConstraint". See ➡️ https://github.com/sofa-framework/SofaPython3/pull/514/files |
Beta Was this translation helpful? Give feedback.
The main difference between the soft body contact and the rigid example given by @hugtalbot is that soft bodies collision models are sometimes more than just points. Some times you want to use higher degrees elements such as edges, triangles, quats. In those cases, the contact point may arise inside of the element and not exactly on one of its points. When this happens, in order to ensure a mechanically sound behavior, the contact force is distributed on every points of the element (three for the triangle, two for the edge etc...). This distribution is not homogeneous but take into account the distance between the application point and each point of the element using barycentric coordinates.