""" Class used to create and control joybox device """ from PandaObject import * from DirectDeviceManager import * import OnscreenText """ TODO: Handle interaction between widget, followSelectedTask and updateTask """ # BUTTONS L_STICK = 0 L_UPPER = 1 L_LOWER = 2 R_STICK = 3 R_UPPER = 4 R_LOWER = 5 # ANALOGS NULL_AXIS = -1 L_LEFT_RIGHT = 0 L_FWD_BACK = 1 L_TWIST = 2 L_SLIDE = 3 R_LEFT_RIGHT = 4 R_FWD_BACK = 5 R_TWIST = 6 R_SLIDE = 7 class DirectJoybox(PandaObject): joyboxCount = 0 xyzMultiplier = 1.0 hprMultiplier = 1.0 def __init__(self, device = 'CerealBox', nodePath = direct.camera): # See if device manager has been initialized if direct.deviceManager == None: direct.deviceManager = DirectDeviceManager() # Set name self.name = 'Joybox-' + `DirectJoybox.joyboxCount` # Get buttons and analogs self.device = device self.analogs = direct.deviceManager.createAnalogs(self.device) self.buttons = direct.deviceManager.createButtons(self.device) self.aList = [0,0,0,0,0,0,0,0] self.bList = [0,0,0,0,0,0,0,0] # For joybox fly mode # Default is joe mode self.mapping = [R_LEFT_RIGHT, R_FWD_BACK, L_FWD_BACK, R_TWIST, L_TWIST, NULL_AXIS] self.modifier = [1,1,1,-1,-1,0] # Initialize time self.lastTime = globalClock.getFrameTime() # Record node path self.nodePath = nodePath # Ref CS for orbit mode self.refCS = direct.cameraControl.coaMarker self.tempCS = direct.group.attachNewNode('JoyboxTempCS') # Text object to display current mode self.readout = OnscreenText.OnscreenText( pos = (-0.9, -0.95) ) # List of functions to cycle through self.modeList = [self.joeMode, self.driveMode, self.orbitMode] # Pick initial mode self.updateFunc = self.joyboxFly self.modeName = 'Joe Mode' # Auxiliary data self.auxData = [] # Button registry self.addButtonEvents() # Spawn update task self.enable() def enable(self): # Kill existing task self.disable() # Accept button events self.acceptSwitchModeEvent() self.acceptUprightCameraEvent() # Update task taskMgr.spawnMethodNamed(self.updateTask, self.name + '-updateTask') def disable(self): taskMgr.removeTasksNamed(self.name + '-updateTask') # Ignore button events self.ignoreSwitchModeEvent() self.ignoreUprightCameraEvent() def destroy(self): self.disable() self.tempCS.removeNode() def addButtonEvents(self): self.breg = ButtonRegistry.ptr() # MRM: Hard coded! for i in range(8): self.buttons.setButtonMap( i, self.breg.getButton(self.getEventName(i))) self.eventThrower = self.buttons.getNodePath().attachNewNode( ButtonThrower()) def setNodePath(self, nodePath): self.nodePath = nodePath def getNodePath(self): return self.nodePath def setRefCS(self, refCS): self.refCS = refCS def getRefCS(self): return self.refCS def getEventName(self, index): return self.name + '-button-' + `index` def setXyzMultiplier(self, multiplier): DirectJoybox.xyzMultiplier = multiplier def getXyzMultiplier(self): return DirectJoybox.xyzMultiplier def setHprMultiplier(self, multiplier): DirectJoybox.hprMultiplier = multiplier def getHprMultiplier(self): return DirectJoybox.hprMultiplier def updateTask(self, state): self.updateVals() self.updateFunc() return Task.cont def updateVals(self): # Update delta time cTime = globalClock.getFrameTime() self.deltaTime = cTime - self.lastTime self.lastTime = cTime # Update analogs for i in range(len(self.analogs)): self.aList[i] = self.analogs.normalizeChannel(i) # Update buttons for i in range(len(self.buttons)): try: self.bList[i] = self.buttons[i] except IndexError: # That channel may not have been updated yet self.bList[i] = 0 def acceptSwitchModeEvent(self, button = R_UPPER): self.accept(self.getEventName(button), self.switchMode) def ignoreSwitchModeEvent(self, button = R_UPPER): self.ignore(self.getEventName(button)) def switchMode(self): try: # Get current mode self.modeFunc = self.modeList[0] # Rotate mode list self.modeList = self.modeList[1:] + self.modeList[:1] # Call new mode self.modeFunc() except IndexError: pass def showMode(self, modeText): def hideText(state, s = self): s.readout.setText('') return Task.done taskMgr.removeTasksNamed(self.name + '-showMode') # Update display self.readout.setText(modeText) t = taskMgr.doMethodLater(3.0, hideText, self.name + '-showMode') t.uponDeath = hideText def acceptUprightCameraEvent(self, button = L_UPPER): self.accept(self.getEventName(button), direct.cameraControl.orbitUprightCam) def ignoreUprightCameraEvent(self, button = L_UPPER): self.ignore(self.getEventName(button)) def setMode(self, func, name): self.disable() self.updateFunc = func self.modeName = name self.showMode(self.modeName) self.enable() def joyboxFly(self): # Do nothing if no nodePath selected if self.nodePath == None: return hprScale = (self.analogs.normalizeChannel(L_SLIDE, 0.1, 100) * DirectJoybox.hprMultiplier) posScale = (self.analogs.normalizeChannel(R_SLIDE, 0.1, 100) * DirectJoybox.xyzMultiplier) def getAxisVal(index, s = self): try: return s.aList[s.mapping[index]] except IndexError: # If it is a null axis return 0 return 0.0 x = getAxisVal(0) * self.modifier[0] y = getAxisVal(1) * self.modifier[1] z = getAxisVal(2) * self.modifier[2] pos = Vec3(x,y,z) * (posScale * self.deltaTime) h = getAxisVal(3) * self.modifier[3] p = getAxisVal(4) * self.modifier[4] r = getAxisVal(5) * self.modifier[5] hpr = Vec3(h,p,r) * (hprScale * self.deltaTime) # Move node path self.nodePath.setPosHpr(self.nodePath, pos, hpr) def joeMode(self): self.mapping = [R_LEFT_RIGHT, R_FWD_BACK, L_FWD_BACK, R_TWIST, L_TWIST, NULL_AXIS] self.modifier = [1,1,1,-1,-1,0] self.setMode(self.joyboxFly, 'Joe Mode') def lucMode(self): self.mapping = [R_LEFT_RIGHT, R_FWD_BACK, L_FWD_BACK, R_TWIST, L_TWIST, L_LEFT_RIGHT] self.modifier = [1,1,1,-1,-1,0] self.setMode(self.joyboxFly, 'Luc Mode') def driveMode(self): self.mapping = [L_LEFT_RIGHT, R_FWD_BACK, R_TWIST, R_LEFT_RIGHT, L_FWD_BACK, NULL_AXIS] self.modifier = [1,1,-1,-1,-1,0] self.setMode(self.joyboxFly, 'Drive Mode') def lookAtMode(self): self.mapping = [R_LEFT_RIGHT, R_TWIST, R_FWD_BACK, L_LEFT_RIGHT, L_FWD_BACK, NULL_AXIS] self.modifier = [1,1,1,-1,1,0] self.setMode(self.joyboxFly, 'Look At Mode') def lookAroundMode(self): self.mapping = [NULL_AXIS, NULL_AXIS, NULL_AXIS, R_LEFT_RIGHT, R_FWD_BACK, NULL_AXIS] self.modifier = [0,0,0,-1,-1,0] self.setMode(self.joyboxFly, 'Lookaround Mode') def demoMode(self): self.mapping = [R_LEFT_RIGHT, R_FWD_BACK, L_FWD_BACK, R_TWIST, NULL_AXIS, NULL_AXIS] self.modifier = [1,1,1,-1,0,0] self.setMode(self.joyboxFly, 'Demo Mode') def hprXyzMode(self): self.mapping = [R_LEFT_RIGHT, R_FWD_BACK, R_TWIST, L_TWIST, L_FWD_BACK, L_LEFT_RIGHT] self.modifier = [1,1,-1,-1,-1,1] self.setMode(self.joyboxFly, 'HprXyz Mode') def walkthruMode(self): self.mapping = [R_LEFT_RIGHT, R_FWD_BACK, L_TWIST, R_TWIST, L_FWD_BACK, L_LEFT_RIGHT] self.modifier = [1,1,-1,-1,-1, 1] self.setMode(self.joyboxFly, 'Walkthru Mode') def spaceMode(self): self.setMode(self.spaceFly, 'Space Mode') def spaceFly(self): # Do nothing if no nodePath selected if self.nodePath == None: return hprScale = (self.analogs.normalizeChannel(L_SLIDE, 0.1, 100) * DirectJoybox.hprMultiplier) posScale = (self.analogs.normalizeChannel(R_SLIDE, 0.1, 100) * DirectJoybox.xyzMultiplier) dr = -1 * hprScale * self.aList[R_TWIST] * self.deltaTime dp = -1 * hprScale * self.aList[R_FWD_BACK] * self.deltaTime dh = -1 * hprScale * self.aList[R_LEFT_RIGHT] * self.deltaTime self.nodePath.setHpr(self.nodePath, dh, dp, dr) dy = posScale * self.aList[L_FWD_BACK] * self.deltaTime self.nodePath.setY(self.nodePath, dy) def planetMode(self, auxData = []): self.auxData = auxData self.setMode(self.planetFly, 'Space Mode') def planetFly(self): # Do nothing if no nodePath selected if self.nodePath == None: return hprScale = (self.analogs.normalizeChannel(L_SLIDE, 0.1, 100) * DirectJoybox.hprMultiplier) posScale = (self.analogs.normalizeChannel(R_SLIDE, 0.1, 100) * DirectJoybox.xyzMultiplier) dr = -1 * hprScale * self.aList[R_TWIST] * self.deltaTime dp = -1 * hprScale * self.aList[R_FWD_BACK] * self.deltaTime dh = -1 * hprScale * self.aList[R_LEFT_RIGHT] * self.deltaTime self.nodePath.setHpr(self.nodePath, dh, dp, dr) dy = posScale * self.aList[L_FWD_BACK] * self.deltaTime dPos = VBase3(0,dy,0) for planet, radius in self.auxData: # Are we within min radius? # How far above planet are we? np2planet = Vec3(self.nodePath.getPos(planet)) # Compute dist offsetDist = np2planet.length() # Above threshold, leave velocity vec as is if offsetDist > (1.2 * radius): pass else: # Getting close, slow things down # Compute normal vector through node Path oNorm = Vec3() oNorm.assign(np2planet) oNorm.normalize() # Xform fly vec to planet space dPlanet = self.nodePath.getMat(planet).xformVec(Vec3(0, dy, 0)) # Compute radial component of fly vec dotProd = oNorm.dot(dPlanet) if dotProd < 0: # Trying to fly below radius, compute radial component radialComponent = oNorm * dotProd # How far above? above = offsetDist - radius # Set sf accordingly sf = max(1.0 - (max(above, 0.0)/(0.2 * radius)), 0.0) # Subtract scaled radial component dPlanet -= radialComponent * (sf * sf) #dPlanet -= radialComponent # Convert back to node path space dPos.assign(planet.getMat(self.nodePath).xformVec(dPlanet)) # Set pos accordingly self.nodePath.setPos(self.nodePath, dPos) def orbitMode(self): self.setMode(self.orbitFly, 'Orbit Mode') def orbitFly(self): # Do nothing if no nodePath selected if self.nodePath == None: return hprScale = (self.analogs.normalizeChannel(L_SLIDE, 0.1, 100) * DirectJoybox.hprMultiplier) posScale = (self.analogs.normalizeChannel(R_SLIDE, 0.1, 100) * DirectJoybox.xyzMultiplier) r = -0.01 * posScale * self.aList[R_FWD_BACK] * self.deltaTime rx = hprScale * self.aList[R_LEFT_RIGHT] * self.deltaTime ry = hprScale * self.aList[R_TWIST] * self.deltaTime x = posScale * self.aList[L_LEFT_RIGHT] * self.deltaTime z = posScale * self.aList[L_FWD_BACK] * self.deltaTime h = -1 * hprScale * self.aList[L_TWIST] * self.deltaTime # Move dcs self.nodePath.setX(self.nodePath, x) self.nodePath.setZ(self.nodePath, z) self.nodePath.setH(self.nodePath, h) self.orbitNode(rx, ry, 0) pos = self.nodePath.getPos(self.refCS) if Vec3(pos).length() < 0.005: pos.set(0,-0.01, 0) # Now move on out pos.assign(pos * (1 + r)) self.nodePath.setPos(self.refCS, pos) def orbitNode(self, h, p, r): # Position the temp node path at the ref CS self.tempCS.setPos(self.refCS, 0, 0, 0) # Orient the temp node path to align with the orbiting node path self.tempCS.setHpr(self.nodePath, 0, 0, 0) # Record the position of the orbiter wrt the helper pos = self.nodePath.getPos(self.tempCS) # Turn the temp node path self.tempCS.setHpr(self.tempCS, h, p, r) # Position the orbiter "back" to its position wrt the helper self.nodePath.setPos(self.tempCS, pos) # Restore the original hpr of the orbiter self.nodePath.setHpr(self.tempCS, 0, 0, 0)