Extend the default teleport to multi user teleport

This script implements group teleport functionality. It uses the default teleport interaction and implements an additional group teleport interaction. How to combine default and custom interactions can be seen in the “Combine a custom and a default device interaction” example.

When GroupTeleport is initialized, two virtual buttons are defined on the touchpad of a VR controller. The upper half is one button and the lower half another. How virtual buttons work is shown in the “Define and use virtual buttons on the touchpad of a VR controller” example.

The actions of the default teleport are now mapped to the lower virtual button. Further information about remapping the default interactions can also be found in the “Combine a custom and a default device interaction” example.

A new interaction is created for the group teleport. This handles the switching between the regular and the group teleport, by using the upper button. If group teleport is active, all participants in the session will also be teleported to near the target position.

After all signals are connected to their corresponding methods, the geometry positioning is set up by using a parent constraint.

82self.leftConstraint = vrConstraintService.createParentConstraint([self.leftController.getNode()], self.leftDisk, True)
83self.rightConstraint = vrConstraintService.createParentConstraint([self.rightController.getNode()], self.rightDisk, True)

This geometry is positioned on top of the touchpad of the controller. It visualizes the current state. The upper button will show “Single” if the regular teleport is active and “Group” if group teleport is active. The buttons will be highlighted, when the user presses them. The setting of which geometry is visible is handled in the corresponding methods. The actual group teleport is done by syncing the camera node with all other users.

129cameraNode = vrCameraService.getActiveCamera()
130vrSessionService.syncNode(cameraNode)

For further information about syncing nodes refer to the “Synchronization during a collaboration session” example.

vr/groupTeleport.py
  1# © 2024 Autodesk, Inc. All rights reserved.
  2
  3# Class for the group teleport
  4class GroupTeleport:
  5    def __init__(self):        
  6        self.isActive = False        
  7        self.setupButtons()
  8        self.setupInteraction()
  9        self.setupVisualization()           
 10
 11    def setupButtons(self):
 12        # Create touchpad layout. Upper half of pad is one button ...
 13        self.padUp = vrdVirtualTouchpadButton("padup", 0.0, 1.0, 270.0, 90.0)
 14        # ... and the lower half is another button
 15        self.padDown = vrdVirtualTouchpadButton("paddown", 0.0, 1.0, 90.0, 270.0)
 16
 17        # Get the controllers
 18        self.leftController = vrDeviceService.getVRDevice("left-controller")
 19        self.rightController = vrDeviceService.getVRDevice("right-controller")
 20
 21        # Assign the virtual touchpad buttons to the left ...
 22        self.leftController.addVirtualButton(self.padUp, "Touchpad")
 23        self.leftController.addVirtualButton(self.padDown, "Touchpad")
 24        # ... and to the right controller
 25        self.rightController.addVirtualButton(self.padUp, "Touchpad")
 26        self.rightController.addVirtualButton(self.padDown, "Touchpad")
 27
 28        # Map the default teleport interaction top the lower button
 29        self.teleportInteraction = vrDeviceService.getInteraction("Teleport")
 30        self.teleportInteraction.setControllerActionMapping("prepare", "any-paddown-touched")
 31        self.teleportInteraction.setControllerActionMapping("execute", "any-paddown-pressed")
 32        self.teleportInteraction.setControllerActionMapping("abort", "any-paddown-untouched")
 33
 34    def setupInteraction(self):
 35        # Create an interaction for the group teleport
 36        self.groupTeleportInteraction = vrDeviceService.createInteraction("GroupTeleport")
 37
 38        # Map the toggle active to the upper pad button
 39        self.beginToggleAction = self.groupTeleportInteraction.createControllerAction("any-padup-pressed")
 40        self.toggleActiveAction = self.groupTeleportInteraction.createControllerAction("any-padup-released")        
 41        # Get the execute action of the teleport
 42        self.teleportExecuteAction = self.teleportInteraction.getControllerAction("execute")
 43        # Map the pad down for some visual indicators
 44        self.teleportExecuteFinishedAction = self.groupTeleportInteraction.createControllerAction("any-paddown-released")
 45
 46        # Connect the signals
 47        self.beginToggleAction.signal().triggered.connect(self.beginToggle)
 48        self.toggleActiveAction.signal().triggered.connect(self.toggleActive)
 49        self.teleportExecuteAction.signal().triggered.connect(self.execute)
 50        self.teleportExecuteFinishedAction.signal().triggered.connect(self.executeFinished)
 51
 52    def setupVisualization(self):
 53        # Load geometry for controller touchpads
 54        loadGeometry("$VRED_EXAMPLES/vr/GroupTeleportPad.osb")
 55        # Find the touchpad geometry in the scene that shows the virtual buttons
 56        oldLeftDisk = findNode("ControllerDisk")        
 57
 58        # Create a lookup for the different visualization states
 59        self.diskVisualizations = dict()
 60
 61        # Check if the touchpad geometry has been found
 62        if oldLeftDisk.isValid():
 63            # Geometry is also needed for the right controller, therefore clone it
 64            oldRightDisk = cloneNode(oldLeftDisk, False)
 65            # Convert to new vrdNode
 66            self.leftDisk = vrNodeService.getNodeFromId(oldLeftDisk.getID())
 67            self.rightDisk = vrNodeService.getNodeFromId(oldRightDisk.getID())
 68            
 69            # Setup all touchpad geometries for the left hand            
 70            self.diskVisualizations["leftSingle"] = self.leftDisk.getChild(0)
 71            self.diskVisualizations["leftGroup"] = self.leftDisk.getChild(1)
 72            self.diskVisualizations["leftUp"] = self.leftDisk.getChild(2)
 73            self.diskVisualizations["leftSingleDown"] = self.leftDisk.getChild(3)
 74            self.diskVisualizations["leftGroupDown"] = self.leftDisk.getChild(4)
 75
 76            # Setup all touchpad geometries for the right hand
 77            self.diskVisualizations["rightSingle"] = self.rightDisk.getChild(0)
 78            self.diskVisualizations["rightGroup"] = self.rightDisk.getChild(1)
 79            self.diskVisualizations["rightUp"] = self.rightDisk.getChild(2)
 80            self.diskVisualizations["rightSingleDown"] = self.rightDisk.getChild(3)
 81            self.diskVisualizations["rightGroupDown"] = self.rightDisk.getChild(4)
 82
 83            # Use a constraint to position the touchpad geometry correctly
 84            self.leftConstraint = vrConstraintService.createParentConstraint([self.leftController.getNode()], self.leftDisk, True)
 85            self.rightConstraint = vrConstraintService.createParentConstraint([self.rightController.getNode()], self.rightDisk, True)            
 86
 87            self.initialized = True
 88        else:
 89            self.initialized = False
 90
 91        # Set the visualization of the devices to controller instead of hands
 92        self.leftController.setVisualizationMode(0)
 93        self.rightController.setVisualizationMode(0)
 94
 95        # Set the visualization state of the touchpad disk
 96        self.showSingleDisk()
 97    
 98    def toggleActive(self, action, device):    
 99        self.isActive = not self.isActive
100        if self.isActive:            
101            # Show the touchpad geometry used for group teleport
102            self.showGroupDisk()
103        else:            
104            # Show the touchpad geometry used for regular teleport
105            self.showSingleDisk()
106
107
108    def beginToggle(self, action, device):
109        left = True
110        # Check if the right or the left controller triggered this
111        if 'right' in device.getName():
112            left = False
113        # Highlights the upper button
114        self.showDiskUp(left)
115
116
117    def execute(self, action, device):
118        left = True
119        # Check if the right or the left controller triggered this
120        if 'right' in device.getName():
121            left = False
122
123        # Highlight the lower button
124        self.showDiskDown(left)
125
126        # If inactive just return as the regular teleport will work as usual
127        if not self.isActive:
128            return        
129
130        # Sync the active camera with all participants in the session to teleport them, too.
131        cameraNode = vrCameraService.getActiveCamera()
132        vrSessionService.syncNode(cameraNode)
133
134
135    def executeFinished(self, action, device):
136        if self.isActive:
137            # Show 'Group' on the upper button
138            self.showGroupDisk()
139        else:
140            # Show 'Single' on the upper button
141            self.showSingleDisk()
142
143
144    def hideAllDisks(self):
145        if not self.initialized:
146            return
147
148        # Iterate over the lookup to set all geometries to invisible
149        for name, disk in self.diskVisualizations.items():
150            disk.setVisibilityFlag(False)
151
152
153    def hideDisksOneSided(self, left):
154        if not self.initialized:
155            return
156
157        side = 'right'
158        if left:
159            side = 'left'
160
161        # Iterate over the lookup to set all geometries of one side to invisible
162        for name, disk in self.diskVisualizations.items():
163            if side in name:
164                disk.setVisibilityFlag(False)
165
166
167    def showGroupDisk(self):
168        if not self.initialized:
169            return
170
171        # Hide all touchpad geometries first to ensure only the correct ones will be shown.
172        self.hideAllDisks()
173        # Show the touchpad geometry with 'Group' on the upper button.
174        # Do this for both sides as this state is for both hands
175        self.diskVisualizations["leftGroup"].setVisibilityFlag(True)
176        self.diskVisualizations["rightGroup"].setVisibilityFlag(True)
177
178
179    def showSingleDisk(self):
180        if not self.initialized:
181            return
182
183        # Hide all touchpad geometries first to ensure only the correct ones will be shown.
184        self.hideAllDisks()
185        # Show the touchpad geometry with 'Single' on the upper button.
186        # Do this for both sides as this state is for both hands
187        self.diskVisualizations["leftSingle"].setVisibilityFlag(True)
188        self.diskVisualizations["rightSingle"].setVisibilityFlag(True)
189
190
191    def showDiskUp(self, left):
192        if not self.initialized:
193            return
194
195        # Hide all touchpad geometries for the given side first to ensure only the correct ones will be shown.
196        self.hideDisksOneSided(left)
197
198        # Show the highlighted geometry for the lower button for the given hand.
199        if left:            
200            self.diskVisualizations["leftUp"].setVisibilityFlag(True)
201        else:            
202            self.diskVisualizations["rightUp"].setVisibilityFlag(True)
203
204
205    def showDiskDown(self, left):
206        if not self.initialized:
207            return
208
209        # Hide all touchpad geometries for the given side first to ensure only the correct ones will be shown.
210        self.hideDisksOneSided(left)
211
212        # Show the highlighted geometry for the upper button for the given hand
213        # It also needs to be distinhuished which mode is currently active to show the correct highlighted geometry.        
214        if left:
215            if self.isActive:
216                self.diskVisualizations["leftGroupDown"].setVisibilityFlag(True)
217            else:
218                self.diskVisualizations["leftSingleDown"].setVisibilityFlag(True)
219        else:
220            if self.isActive:
221                self.diskVisualizations["rightGroupDown"].setVisibilityFlag(True)
222            else:
223                self.diskVisualizations["rightSingleDown"].setVisibilityFlag(True)
224    
225
226groupTeleport = GroupTeleport()