Clay with deformable shape
In the previous Deformable shape example, the geometry returns to its original shape when there is no contact with the haptics device. This behaviour can be changed by adjusting the plasticity field of the CoordinateDeformer node. As indicated by its name, the field controls the plasticity of deformation. The default plasticity value of 0 defines a non-plastic deformation while 1 defines a fully plastic deformation.
DeformableShape has three fields which are related to the description of plasticity: origCoord which contains the coordinates of the geometry before deformation, specified by the coordinates of DeformableShape's geometry, restingCoord which contains the final coordinates of the geometry after deformation, and deformedCoord which are the coordinates of the geometry as the deformation occurs.
A non-plastic deformation would ensure that the values of restingCoord are always the same as that of origCoord, while in a full plastic deformation, restingCoord is always the same as deformedCoord. The extent to which the restingCoord falls back to the origCoord is adjusted with plasticity value between 0 and 1.
Having this background knowledge we will look at an example of a plastic deformation.
- differentiating graphics geometry from haptics geometry
- GlobalSettings, HapticsOptions nodes
- useBoundTree and maxDistance fields
<Scene> <GlobalSettings> <HapticsOptions maxDistance="0.1" useBoundTree="false" /> </GlobalSettings> <Collision enabled="false"> <DeformableShape DEF="D"> <CoordinateDeformer plasticity="0.3"> <GaussianFunction containerField="distanceToDepth" center="0" amplitude="1" width="0.012"/> </CoordinateDeformer> <Appearance> <Material diffuseColor="0.543 0.273 0.121"/> <FrictionalSurface stiffness="0.5" staticFriction="0.7" dynamicFriction="0.5" /> </Appearance> </DeformableShape> </Collision>
We first include a GlobalSettings node to define the default settings for the scene. Here we set the maxDistance of HapticsOptions to 0.1 so that haptics is rendered within 0.1m radius of the device tracker. useBoundTree is set to false to disable use of bound tree in extraction of haptics triangles. When bound tree is not used, the haptics triangles are extracted with OpenGL instead. The use of bound tree is disabled in this example to optimize on speed.
We disable collision detection for the DeformableShape, which is DEF-ed D. As with the previous Deformable shape example a CoordinateDeformer and Appearance node are specified for the DeformableShape. The difference here is that the plasticity field is set to 0.3, defining a plastic deformation.
Both the geometry and hapticGeometry for this example will be created in a Python script.
<PythonScript moduleName="CUBE" url="ITSCube.py" /> <PythonScript url="script.py"> <DeformableShape USE="D" containerField="references" /> </PythonScript> </Scene>
The remaining X3D code adds these Python scripts to the scene graph. The first script, with moduleName CUBE, contains functions for creating a box with IndexedTriangleSet. The moduleName is important when importing this python script into some other python script. The ITSCube.py file is included at the end of this tutorial since it is quite long. We will use a function from this script in the second script, contained in script.py. This second script takes a reference to the DeformableShape.
# script.py from H3DInterface import * import CUBE if getActiveDeviceInfo(): for h in getActiveDeviceInfo().device.getValue(): h.proxyWeighting.setValue( 0 ) d, = references.getValue() size = Vec3f(0.15, 0.15, 0.15) d.geometry.setValue( CUBE.createConstantITSCube(size, 25, 25) ) h = CUBE.createConstantITSCube(size, 25, 25) d.hapticGeometry.setValue( h )
In script.py we import H3DInterface as usual to obtain all the H3DAPI python functions. We also import the first Python script with the statement import CUBE.
We then check whether there is an active DeviceInfo, and if there is, we iterate through all the active haptics devices and set the proxyWeighting to 0. We store the reference to DeformableShape in d and a 3 dimensional vector as size.
The next three lines set the geometry and hapticGeometry for the DeformableShape. We use the createConstantITSCube function that we have imported from CUBE to create the needed cubes from IndexedTriangleSet. The createConstantITSCube function takes as parameters the size of the box, and the number of horizontal and vertical coordinates to generate for each face of box.
If you run the code that we have discussed up to now you should be able to deform the clay. However, if you use H3DViewer and draw the haptic triangles (go to Rendering >> Settings >> Debug >> Draw haptic triangles) you will notice that although the graphical triangles are deformed (because we have set a non-zero plasticity), the haptic triangles are still rendered on the original IndexedTriangleSet cube.
Adjusting haptic coordinates
Only one line is added to remedy this problem.
# script.py d.restingCoord.route( h.coord )
We route DeformableShape's restingCoord to the coordinates of its hapticGeometry. Now the haptic triangles are drawn from the updated coordinates.
from H3DInterface import * startAtCoord = 0 # function to generate a cube from triangle set # param size: actual size of cube # param columns: num of horizontal coords # param rows: num of vertical coords # param do_tex_coord: true or false, whether to generate texture coords as well # returns an IndexedTriangleSet node set with with coord, index and if param do_tex_coord was true, the texture coordinates def createConstantITSCube( size, columns, rows, do_tex_coord="False" ): info =  for i in range(6): if i%2: info.append(rows) else: info.append(columns) return createITSCube( size, info, do_tex_coord ) # function to generate a cube from triangle set # param size: actual size of cube # param coordInfo: a list of 6 integers denoting number of coords for each face of cube # i.e. [ <num of horizontal coords for face XY> , < num vertical coords for face XY>, # <num of horizontal coords for face ZY> , < num vertical coords for face ZY>, # <num of horizontal coords for face XZ> , < num vertical coords for face XZ>] # param do_tex_coord: true or false, whether to generate texture coords as well # returns an IndexedTriangleSet node set with with coord, index and if param do_tex_coord was true, the texture coordinates def createITSCube( size, coord_info, do_tex_coord="False" ): coords =  tex_coords =  index =  # create coordinates and index for sides of cube in plane x-y createTrianglesOnCubeFaces( "xy", size, coord_info, coord_info, do_tex_coord, coords, tex_coords, index ) # create coordinates and index for sides of cube in plane y-z createTrianglesOnCubeFaces( "yz", size, coord_info, coord_info, do_tex_coord, coords, tex_coords, index ) # create coordinates and index for sides of cube in plane x-z createTrianglesOnCubeFaces( "xz", size, coord_info, coord_info, do_tex_coord, coords, tex_coords, index ) its = createX3DNodeFromString( "<IndexedTriangleSet solid=\"FALSE\" />" ) coord = createX3DNodeFromString( "<Coordinate />" ) coord.point.setValue( coords ) its.index.setValue( index ) its.coord.setValue( coord ) if do_tex_coord: tex_coord = createX3DNodeFromString( "<TextureCoordinate />" ) tex_coord.point.setValue( tex_coords ) its.texCoord.setValue( tex_coord ) global startAtCoord startAtCoord = 0 return its # function to create vector of coordinates and index for two parallel faces of a cube # param faces: indicates the faces of a cube for coordinates generation i.e "xy", "yz", "xz" # param SFloat num_col, num_row: number of coordinate points to generate of horizontally and vertically # param SFVec3f size: actual dimension of cube def createTrianglesOnCubeFaces( faces, size, num_col, num_row, do_tex_coord, coords, tex_coords, index ): global startAtCoord col_size = size.x row_size = size.y depth = size.z if faces == "xz" or faces == "zx": col_size = size.x row_size = size.z depth = size.y elif faces == "yz" or faces == "zy": col_size = size.z row_size = size.y depth = size.x step_c = col_size/(num_col - 1) step_r = row_size/(num_row - 1) if do_tex_coord: tc_step_c = 1.0/(num_col - 1) tc_step_r = 1.0/(num_row - 1) # create coordinates for d in [depth/2, -depth/2]: for c in range( num_col ): for r in range( num_row ): if faces == "xy" or faces == "yx": coords.append( Vec3f( step_c * c - col_size/2, step_r * r - row_size/2, d ) ) if do_tex_coord: tex_coords.append( Vec2f( tc_step_c * c, tc_step_r * r ) ) elif faces == "xz" or faces == "zx": coords.append( Vec3f( step_c * c - col_size/2, d, step_r * r - row_size/2 ) ) if do_tex_coord: tex_coords.append( Vec2f( tc_step_c * c, tc_step_r * r ) ) elif faces == "yz" or faces == "zy": coords.append( Vec3f( d, step_r * r - row_size/2, step_c * c - col_size/2 ) ) if do_tex_coord: tex_coords.append( Vec2f( tc_step_c * c, tc_step_r * r ) ) # create index for i in [startAtCoord, startAtCoord + num_col*num_row]: for c in range( num_col - 1 ): for r in range( num_row - 1 ): v0 = i + c * num_row + r v1 = i + c * num_row + r+1 v2 = i + (c+1) * num_row + r+1 v3 = i + (c+1) * num_row + r index.extend([v0, v1, v2, v0, v2, v3 ]) startAtCoord = startAtCoord + 2*(num_col)*(num_row)