H3DAPI has a DeformableShape node that models a geometry that is deformable on contact with a haptics device. This tutorial shows a simple example of DeformableShape.
<!!-- deform.x3d --> <Group> <Collision enabled="false"> <Transform rotation="1 0 0 -0.5"> <DeformableShape DEF="DEFORM_SHAPE" > <CoordinateDeformer> <GaussianFunction width="0.05" containerField="distanceToDepth"/> </CoordinateDeformer> <Appearance DEF="LOCAL_APP" > <ImageTexture url="plastic_2.jpg" /> <Material/> <SmoothSurface stiffness="0.1" /> </Appearance> <Rectangle2D size="0.4 0.4" DEF="HAPTIC_GEOM" solid="FALSE" containerField="hapticGeometry" /> </DeformableShape> </Transform> </Collision>
This code first disables X3D collision. It then adds a Transform node just to rotate our DeformableShape. Here, there are three children nodes of the DeformableShape: CoordinateDeformer, Appearance and Rectangle2D. DeformableShape is an X3DShapeNode, and so takes an Appearance node to modify the appearance of its geometry. It used Rectangle2D as its hapticGeometry. One main difference between other X3DShapeNodes and the Deformable shape is the presence of the deformer field, which specifies an H3DCoordinateDeformerNode that determines how the coordinates should be deformed on contact. Here, the CoordinateDeformer is used as the deformer. CoordinateDeformer uses a GaussianFunction to define the depth of deformation on contact.
There is another major difference between other X3DShapeNodes and the DeformableShape: the geometry field of a DeformableShape requires an X3DComposedGeometry node. We do not explicitly specify this for our example, but will do so in a Python script. geometry will be used to graphically model the shape's deformation.
<!!-- deform.x3d --> <PythonScript DEF="PS" url="deform.py" > <DeformableShape USE="DEFORM_SHAPE" containerField="references" /> </PythonScript> </Group>
To include our script in the scene a PythonScript node is added. We will need to refer to the DeformableShape node, and so it is passed as a reference to the script by setting it as PythonScript's references value.
The Python script is use for the sole purpose of creating and setting an IndexedTriangleSet node as the DeformableShape's geometry.
#deform.py from H3DInterface import * di = getActiveDeviceInfo() if( di ): devices = di.device.getValue() for d in devices: d.proxyWeighting.setValue( 0 )
H3DAPI is built on the haptics API HAPI that uses proxy-based algorithm for haptics rendering. In our example, we want the proxy to be drawn where the tracker is. The code exerpt above shows how this is done. We call the function getActiveDeviceInfo that returns the DeviceInfo of our scene. If the DeviceInfo is valid then we get a list of all the devices related to the DeviceInfo. The list is then iterated through, and for every device, the proxyWeighting is set to zero so that the position of the proxy is always the same as the tracker.
#deform.py columns = 31 rows = 31 size = Vec2f( 0.4, 0.4 ) coords =  tex_coords =  index =  step_c = size.x / (columns-1) step_r = size.y / (rows-1) tc_step_c = 1.0/ (columns-1) tc_step_r = 1.0/ (rows-1)
We then prepare the variables that we will use in our code.
- columns and rows are the numbers of coordinates that we want to have horizontally and vertically in the rectangular IndexedTriangleSet that we are creating.
- size is the size of the rectangle that we want
- coords is a list that will later contain the points for the IndexedTriangleSet's coord
- tex_coords is a list that will later contain the points for IndexedTriangleSet's texCoord
- index is a list that will later contain integers for IndexedTriangleSet's index
- step_c and step_r are the horizontal and vertical widths between each point in coords
- tc_step_c and tc_step_r are the horizontal and vertical width fractions between each point in tex_coords
#deform.py for c in range( columns ): for r in range( rows ): coords.append( Vec3f( step_c * c - size.x / 2, step_r * r - size.y/2, 0 ) ) tex_coords.append( Vec2f( tc_step_c * c, tc_step_r * r ) ) for c in range( columns - 1 ): for r in range( rows - 1 ): v0 = r * columns + c v1 = r * columns + c+1 v2 = (r+1) * columns + c+1 v3 = (r+1) * columns + c index = index + [v0, v1, v2, v0, v2, v3 ]
The two for loops do the calculations and create necessary values for coords, tex_coords and index. These will then be set as values to the corresponding fields.
#deform.py deform_node, = references.getValue() its = createX3DNodeFromString( "<IndexedTriangleSet solid=\"FALSE\" />" ) coord = createX3DNodeFromString( "<Coordinate />" ) coord.point.setValue( coords ) tex_coord = createX3DNodeFromString( "<TextureCoordinate />" ) tex_coord.point.setValue( tex_coords )
The DeformableShape reference that we passed into the script is obtained by the expression references.getValue().
We then create an IndexedTriangleSet by calling the function createX3DNodeFromString and store the resulting node in its.
We call the same function to create the Coordinate node, which we store in coord. The point field of coord is then set to coords
Similarly, we create a TextureCoordinate node and set the value of its point.
#deform.py its.index.setValue( index ) its.coord.setValue( coord ) its.texCoord.setValue( tex_coord ) deform_node.geometry.setValue( its )
Then we set the index list and the newly created Coordinate and TextureCoordinate nodes as values to the index, coord and texCoord fields of the IndexedTriangleSet.
Finally we add the IndexedTriangleSet as the DeformableShape's geometry.
In this tutorial we see that a python script is used to generate the needed points and indices for the IndexedTriangleSet. We could have done the example without the script and explicitly create the IndexedTriangleSet in X3D but with the number of points needed it is obvious that it would clutter the entire file and is impractical!