Physics collision example with contact information¶
This example demonstrates how to use the physics service (vrPhysicsService
) to detect collisions between objects and how to get information about the contact points.
In the example we have a sphere rotating around an axis and a cylinder moving up and down. The cylinder moves through the slices of the sphere and
we want to display the contact points between the two objects.
The sphere is divided into slices, each slice has a different color. The sphere is registered as a kinematic object in the physics service with a convex hull configuration that does not merge geometries. This way we get a separate convex hull for each colored slice of the sphere. Without the ‘merge geometries’ option set to ‘False’, we would get a single convex hull for the entire sphere.
Not merging the child geometries together creates a hierarchy of convex hulls. When a collision happens, the physics service returns a vrdPhysicsInfo
object
that contains which child node of the sphere collided with the cylinder. The child node can be queried with the getCollidingNode1() or getCollidingNode2()
methods, while getCollidingRootNode1() and getCollidingRootNode2() would return the sphere node itself.
The contact points are stored in the vrdPhysicsInfo object and can be accessed with the getContactPoints() method. The contact points are in world coordinates and are stored as QVector3D objects. We use the first contact point to position an annotation node in the scene and display the coordinates of the contact point. The example also uses the diffuse color of the colliding child node to color the cylinder and the annotation node.
The example consists of two variants, one to start the simulation and one to stop it. The start variant sets up the scene, registers the collision callbacks and starts the rotation and movement of the sphere and cylinder. The stop variant stops the rotation and movement of the sphere and cylinder.
Sphere-Start¶
1 # Find objects for Collision and Interpolators
2 sphere = vrNodeService.findNode('Sphere')
3 axis = vrNodeService.findNode('Axis')
4 shape = vrNodeService.findNode('Cylinder')
5 trans = vrNodeService.findNode('Trans')
6 calcVertexNormals()
7
8 # Set up the rotation of the sphere around the axis
9 rotInt = vrInterpolator()
10 rotSlide = vrRotationAxisSlide(sphere, axis, 0, 360, 6.0)
11 rotInt.add(rotSlide)
12 rotInt.setActive(True)
13
14 # Set up the movement of the cylinder
15 cylTrans = vrInterpolator()
16 cylSlideUp = vrTranslationSlide(trans, 0,0,25, 0,0,500, 3.0)
17 cylSlideDn = vrTranslationSlide(trans, 0,0,500, 0,0,25, 3.0)
18 cylTrans.add(cylSlideUp)
19 cylTrans.add(cylSlideDn)
20 cylTrans.setActive(True)
21
22 # Switch on physics to activate the collision detection pipeline
23 vrPhysicsService.setActive(True)
24
25 # Create convex hulls as collision shapes for the sphere and the cylinder.
26 # We set 'merge geometries' to False, so that we get a separate convex hull
27 # for each child node instead of a single convex hull for the entire node.
28 hullConfig = vrdPhysicsHullConfig()
29 hullConfig.setMergeGeometries(False)
30
31 # Add the two nodes as kinematic objects so that we can move them in the scene
32 vrPhysicsService.addKinematicObject(sphere, hullConfig)
33 vrPhysicsService.addKinematicObject(shape, hullConfig)
34
35
36 # Fetch the annotation node to display the collision point info
37 note = vrAnnotationService.findAnnotation('CollisionPoint')
38
39 # Helper function to get the correct colliding child node of the sphere.
40 # The order of the colliding nodes is not guaranteed, so we have to check which
41 # of the two nodes is the sphere's child.
42 # The collision system always returns pairs, we look here for the
43 # sphere's children. We know they are named 'Tri' followed by a number, so we can
44 # use this to identify the correct child node.
45 # The sphere node is merged into slices where every slice has the same color.
46 #
47 # Note: we have to use getCollidingNode1() to get the colliding child node
48 # since we disabled 'merge geometries' on the convex hull config and
49 # therefore have a hull for each child.
50 # Using getCollidingRootNode1() would return the sphere, the node we had actually
51 # registered for collision.
52 def get_sphere_collision_node(nodeInfo):
53 if nodeInfo.getCollidingNode1().getName().startswith("Tri"):
54 return nodeInfo.getCollidingNode1()
55 else:
56 return nodeInfo.getCollidingNode2()
57
58 # This is the function we call when a collision happens.
59 # It fetches the sphere's colliding child node and applies it's
60 # diffuse color to the cylinder and the annotation node.
61 # Additonally we fetch the contact points from the collision
62 # and position the annotation to the first reported contact point.
63 # Since we already know that a collision has happened, there
64 # has to be at least one contact.
65 def update_collision(nodeInfo):
66 vrAnnotationService.setShowAnnotations(True)
67 collidingNode = get_sphere_collision_node(nodeInfo)
68 mat = collidingNode.getMaterial()
69 diffuse = mat.getDiffuseColor()
70
71 vrMaterialService.applyMaterialToNodes(mat, [shape])
72 backgroundColor = QColor();
73 backgroundColor.setRgbF(diffuse.x(), diffuse.y(), diffuse.z())
74 note.setLineColor(backgroundColor)
75 note.setFontColor(backgroundColor)
76 point = nodeInfo.getContactPoints()[0]
77 numPoints = len(nodeInfo.getContactPoints())
78
79 note.setPosition(point)
80 note.setText(f'Contact points: {numPoints}\nFirst contact point:\nx {point.x():.2f}\ny {point.y():.2f}\nz {point.z():.2f}')
81
82
83 # Register callbacks for the collisions. We also need to register to
84 # 'continues' to always get updated contact points while the cylinder is
85 # moving through the slices of the sphere.
86 def collisionStart(nodeInfo):
87 update_collision(nodeInfo)
88
89 def collisionStopped(nodeInfo):
90 vrAnnotationService.setShowAnnotations(False)
91
92 def collisionContinues(nodeInfo):
93 update_collision(nodeInfo)
94
95 # Always make sure that the signals are not already connected when registering
96 # them here as part of a variant. If they are not disconnected, the callbacks
97 # would be registered again every time the variant is activated leading to
98 # multiple activations of the callback functions.
99 try:
100 vrPhysicsService.collisionStarted.disconnect(start)
101 vrPhysicsService.collisionStarted.disconnect(stop)
102 vrPhysicsService.collisionStarted.disconnect(cont)
103 except:
104 pass
105
106 start = vrPhysicsService.collisionStarted.connect(collisionStart)
107 stop = vrPhysicsService.collisionStopped.connect(collisionStopped)
108 cont = vrPhysicsService.collisionContinues.connect(collisionContinues)