ShowBase.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. # This module redefines the builtin import function with one
  2. # that prints out every import it does in a hierarchical form
  3. # Annoying and very noisy, but sometimes useful
  4. # import VerboseImport
  5. from PandaModules import *
  6. from DirectNotifyGlobal import *
  7. from MessengerGlobal import *
  8. from TaskManagerGlobal import *
  9. from EventManagerGlobal import *
  10. from PythonUtil import *
  11. from ParticleManagerGlobal import *
  12. from PhysicsManagerGlobal import *
  13. import Task
  14. import EventManager
  15. import math
  16. import sys
  17. import Transitions
  18. import Loader
  19. import time
  20. import FSM
  21. import State
  22. globalClock = ClockObject.getGlobalClock()
  23. class ShowBase:
  24. notify = directNotify.newCategory("ShowBase")
  25. def __init__(self):
  26. # Get the dconfig object
  27. self.config = ConfigConfigureGetConfigConfigShowbase
  28. # Store dconfig variables
  29. self.wantTk = self.config.GetBool('want-tk', 0)
  30. self.wantAnySound = self.config.GetBool('want-sound', 1)
  31. self.wantSfx = self.config.GetBool('audio-sfx-active', 1)
  32. self.wantMusic = self.config.GetBool('audio-music-active', 1)
  33. self.wantFog = self.config.GetBool('want-fog', 1)
  34. if not (self.wantSfx or self.wantMusic):
  35. self.wantAnySound = None
  36. if not self.wantAnySound:
  37. self.wantSfx = None
  38. self.wantMusic = None
  39. self.wantDIRECT = self.config.GetBool('want-directtools', 0)
  40. self.wantStats = self.config.GetBool('want-stats', 0)
  41. taskMgr.taskTimerVerbose = self.config.GetBool('task-timer-verbose', 0)
  42. taskMgr.pStatsTasks = self.config.GetBool('pstats-tasks', 0)
  43. # Set up the TaskManager to reset the PStats clock back
  44. # whenever we resume from a pause. This callback function is
  45. # a little hacky, but we can't call it directly from within
  46. # the TaskManager because he doesn't know about PStats (and
  47. # has to run before libpanda is even loaded).
  48. taskMgr.resumeFunc = PStatClient.resumeAfterPause
  49. fsmRedefine = self.config.GetBool('fsm-redefine', 0)
  50. State.FsmRedefine = fsmRedefine
  51. self.renderTop = NodePath(NamedNode('renderTop'))
  52. self.render = self.renderTop.attachNewNode('render')
  53. # Set a default "off color" (i.e. use poly color) for color transitions
  54. self.render.setColorOff()
  55. self.hidden = NodePath(NamedNode('hidden'))
  56. self.dataRoot = NodePath(NamedNode('dataRoot'), DataRelation.getClassType())
  57. # Cache the node so we do not ask for it every frame
  58. self.dataRootNode = self.dataRoot.node()
  59. self.dataUnused = NodePath(NamedNode('dataUnused'), DataRelation.getClassType())
  60. self.pipe = makeGraphicsPipe()
  61. chanConfig = makeGraphicsWindow(self.pipe, self.render.arc())
  62. self.win = chanConfig.getWin()
  63. # Now that we've assigned a window, assign an exitfunc.
  64. self.oldexitfunc = getattr(sys, 'exitfunc', None)
  65. sys.exitfunc = self.exitfunc
  66. # cameraList is a list of camera group nodes. There may
  67. # be more than one display region/camera node beneath each
  68. # one.
  69. self.cameraList = []
  70. for i in range(chanConfig.getNumGroups()):
  71. self.cameraList.append(self.render.attachNewNode(
  72. chanConfig.getGroupNode(i)))
  73. # this is how we know which display region cameras belong to which
  74. # camera group. display region i belongs to group self.groupList[i]
  75. self.groupList = []
  76. for i in range(chanConfig.getNumDrs()):
  77. self.groupList.append(chanConfig.getGroupMembership(i))
  78. self.camera = self.cameraList[0]
  79. # This is a placeholder for a CollisionTraverser. If someone
  80. # stores a CollisionTraverser pointer here, we'll traverse it
  81. # in the igloop task.
  82. self.cTrav = 0
  83. # This is a list of cams associated with the display region's cameras
  84. self.camList = []
  85. for camera in self.cameraList:
  86. self.camList.append( camera.find('**/+Camera') )
  87. # Set the default camera
  88. self.cam = self.camera.find('**/+Camera')
  89. # If you need to use the camera node, use camNode instead
  90. # of calling cam.node() to save the FFI overhead
  91. self.camNode = self.cam.node()
  92. # Set up a 2-d layer for drawing things behind Gui labels.
  93. self.render2d = NodePath(setupPanda2d(self.win, "render2d"))
  94. # The normal 2-d layer has an aspect ratio that matches the
  95. # window, but its coordinate system is square. This means
  96. # anything we parent to render2d gets stretched. For things
  97. # where that makes a difference, we set up aspect2d, which
  98. # scales things back to the right aspect ratio.
  99. # For now, we assume that the window will have an aspect ratio
  100. # matching that of a traditional PC screen.
  101. self.aspectRatio = 4.0 / 3.0
  102. self.aspect2d = self.render2d.attachNewNode("aspect2d")
  103. self.aspect2d.setScale(1.0 / self.aspectRatio, 1.0, 1.0)
  104. # It's important to know the bounds of the aspect2d screen.
  105. self.a2dTop = 1.0
  106. self.a2dBottom = -1.0
  107. self.a2dLeft = -self.aspectRatio
  108. self.a2dRight = self.aspectRatio
  109. # Set up an auxiliary 3-d layer for rendering floating heads
  110. # or other 3-d objects on top of text or widgets in the 2-d
  111. # layer. We set it up with a camera that specifically shares
  112. # the projection with the default camera, so that when we
  113. # change the default camera's parameters, it changes this one
  114. # too.
  115. self.renderAux = NodePath(NamedNode('renderAux'))
  116. self.camAux = self.renderAux.attachNewNode(Camera('camAux'))
  117. self.camAux.node().shareProjection(self.cam.node().getProjection())
  118. addRenderLayer(self.win, self.renderAux.node(), self.camAux.node())
  119. # We create both a MouseAndKeyboard object and a MouseWatcher object
  120. # for the window. The MouseAndKeyboard generates mouse events and
  121. # mouse button/keyboard events; the MouseWatcher passes them through
  122. # unchanged when the mouse is not over a 2-d button, and passes
  123. # nothing through when the mouse *is* over a 2-d button. Therefore,
  124. # objects that don't want to get events when the mouse is over a
  125. # button, like the driveInterface, should be parented to
  126. # mouseWatcher, while objects that want events in all cases, like the
  127. # chat interface, should be parented to mak.
  128. self.mak = self.dataRoot.attachNewNode(MouseAndKeyboard(self.win, 0, 'mak'))
  129. self.mouseWatcherNode = MouseWatcher('mouseWatcher')
  130. self.mouseWatcher = self.mak.attachNewNode(self.mouseWatcherNode)
  131. # We also create a DataValve object above the trackball/drive
  132. # interface, which will allow us to switch some of the mouse
  133. # control, without switching all of it, to another object
  134. # later (for instance, to enable OOBE mode--see oobe(),
  135. # below.)
  136. self.mouseValve = self.mouseWatcher.attachNewNode(DataValve('mouseValve'))
  137. # This Control object can be used to turn on and off mouse &
  138. # keyboard messages to the DriveInterface.
  139. self.mouseControl = DataValve.Control()
  140. self.mouseValve.node().setControl(0, self.mouseControl)
  141. # This Control object is always kept on, handy to have.
  142. self.onControl = DataValve.Control()
  143. # Now we have the main trackball & drive interfaces.
  144. # useTrackball() and useDrive() switch these in and out; only
  145. # one is in use at a given time.
  146. self.trackball = self.dataUnused.attachNewNode(Trackball('trackball'))
  147. self.drive = self.dataUnused.attachNewNode(DriveInterface('drive'))
  148. self.mouse2cam = self.dataUnused.attachNewNode(Transform2SG('mouse2cam'))
  149. self.mouse2cam.node().setArc(self.camera.arc())
  150. self.useDrive()
  151. self.buttonThrower = self.mouseWatcher.attachNewNode(ButtonThrower())
  152. self.loader = Loader.Loader(self)
  153. self.eventMgr = eventMgr
  154. self.messenger = messenger
  155. self.taskMgr = taskMgr
  156. # Particle manager
  157. self.particleMgr = particleMgr
  158. self.particleMgr.setFrameStepping(1)
  159. self.particleMgrEnabled = 0
  160. # Physics manager
  161. self.physicsMgr = physicsMgr
  162. integrator = LinearEulerIntegrator()
  163. self.physicsMgr.attachLinearIntegrator(integrator)
  164. self.physicsMgrEnabled = 0
  165. self.physicsMgrAngular = 0
  166. self.createAudioManager()
  167. self.createStats()
  168. # Transition effects (fade, iris, etc)
  169. self.transitions = Transitions.Transitions(self.loader)
  170. import __builtin__
  171. __builtin__.base = self
  172. __builtin__.render2d = self.render2d
  173. __builtin__.aspect2d = self.aspect2d
  174. __builtin__.render = self.render
  175. __builtin__.hidden = self.hidden
  176. __builtin__.camera = self.camera
  177. __builtin__.loader = self.loader
  178. __builtin__.taskMgr = self.taskMgr
  179. __builtin__.eventMgr = self.eventMgr
  180. __builtin__.messenger = self.messenger
  181. __builtin__.config = self.config
  182. __builtin__.run = self.run
  183. # Tk
  184. if self.wantTk:
  185. import TkGlobal
  186. if self.wantDIRECT:
  187. import DirectSession
  188. direct.enable()
  189. else:
  190. __builtin__.direct = self.direct = None
  191. self.restart()
  192. def exitfunc(self):
  193. """exitfunc(self)
  194. This should be assigned to sys.exitfunc to be called just
  195. before Python shutdown. It guarantees that the Panda window
  196. is closed cleanly, so that we free system resources, restore
  197. the desktop and keyboard functionality, etc.
  198. """
  199. self.win.closeWindow()
  200. del self.win
  201. del self.pipe
  202. if self.oldexitfunc:
  203. self.oldexitfunc()
  204. def addAngularIntegrator(self):
  205. """addAngularIntegrator(self)"""
  206. if (self.physicsMgrAngular == 0):
  207. self.physicsMgrAngular = 1
  208. integrator = AngularEulerIntegrator()
  209. self.physicsMgr.attachAngularIntegrator(integrator)
  210. def enableParticles(self):
  211. """enableParticles(self)"""
  212. self.particleMgrEnabled = 1
  213. self.physicsMgrEnabled = 1
  214. self.taskMgr.removeTasksNamed('manager-update')
  215. self.taskMgr.spawnTaskNamed(Task.Task(self.updateManagers),
  216. 'manager-update')
  217. def disableParticles(self):
  218. """enableParticles(self)"""
  219. self.particleMgrEnabled = 0
  220. self.physicsMgrEnabled = 0
  221. self.taskMgr.removeTasksNamed('manager-update')
  222. def toggleParticles(self):
  223. if self.particleMgrEnabled == 0:
  224. self.enableParticles()
  225. else:
  226. self.disableParticles()
  227. def isParticleMgrEnabled(self):
  228. return self.particleMgrEnabled
  229. def isPhysicsMgrEnabled(self):
  230. return self.physicsMgrEnabled
  231. def updateManagers(self, state):
  232. """updateManagers(self)"""
  233. dt = min(globalClock.getDt(), 0.1)
  234. if (self.particleMgrEnabled == 1):
  235. self.particleMgr.doParticles(dt)
  236. if (self.physicsMgrEnabled == 1):
  237. self.physicsMgr.doPhysics(dt)
  238. return Task.cont
  239. def createStats(self):
  240. # You must specify a pstats-host in your configrc
  241. # The default is localhost
  242. if self.wantStats:
  243. PStatClient.connect()
  244. def createAudioManager(self):
  245. if self.wantAnySound:
  246. if self.wantSfx:
  247. self.sfxManager = AudioManager.createAudioManager()
  248. if not self.sfxManager.isValid():
  249. self.wantSfx=None
  250. if self.wantMusic:
  251. self.musicManager = AudioManager.createAudioManager()
  252. if not self.musicManager.isValid():
  253. self.wantMusic=None
  254. if not (self.wantSfx or self.wantMusic):
  255. self.wantAnySound=None
  256. def loadSfx(self, name):
  257. if (name and base.wantSfx):
  258. sound=self.sfxManager.getSound(name)
  259. if sound == None:
  260. self.notify.warning("Could not load sound file %s." % name)
  261. return sound
  262. def loadMusic(self, name):
  263. if (name and base.wantMusic):
  264. sound=self.musicManager.getSound(name)
  265. if sound == None:
  266. self.notify.warning("Could not load music file %s." % name)
  267. return sound
  268. def unloadSfx(self, sfx):
  269. if sfx:
  270. del sfx
  271. def unloadMusic(self, music):
  272. if music:
  273. del music
  274. def playSfx(self, sfx, looping = 0, interupt = 1, volume = None,
  275. time = 0.):
  276. if (sfx and base.wantSfx):
  277. if volume != None:
  278. sfx.setVolume(volume)
  279. if interupt or (sfx.status() != AudioSound.PLAYING):
  280. sfx.setTime(time)
  281. sfx.setLoop(looping)
  282. sfx.play()
  283. def playMusic(self, music, looping = 0, interupt = 1, volume = None,
  284. restart = None, time = 0.):
  285. if (music and base.wantMusic):
  286. if volume != None:
  287. music.setVolume(volume)
  288. if interupt or (music.status() != AudioSound.PLAYING):
  289. music.setTime(time)
  290. music.setLoop(looping)
  291. music.play()
  292. if restart:
  293. restart[0].accept("restart-music", restart[1])
  294. def stopSfx(self, sfx):
  295. if (sfx and base.wantSfx):
  296. sfx.stop()
  297. def stopMusic(self, music, restart = None):
  298. if (music and base.wantMusic):
  299. music.stop()
  300. if restart:
  301. restart[0].ignore("restart-music")
  302. def dataloop(self, state):
  303. # traverse the data graph. This reads all the control
  304. # inputs (from the mouse and keyboard, for instance) and also
  305. # directly acts upon them (for instance, to move the avatar).
  306. traverseDataGraph(self.dataRootNode)
  307. return Task.cont
  308. def igloop(self, state):
  309. # run the collision traversal if we have a
  310. # CollisionTraverser set.
  311. if self.cTrav:
  312. self.cTrav.traverse(self.render)
  313. # Finally, render the frame.
  314. self.win.update()
  315. return Task.cont
  316. def restart(self):
  317. self.shutdown()
  318. # give the igloop task a reasonably "late" priority,
  319. # so that it will get run after most tasks
  320. self.taskMgr.spawnTaskNamed(Task.Task(self.igloop, 50), 'igloop')
  321. # give the dataloop task a reasonably "early" priority,
  322. # so that it will get run before most tasks
  323. self.taskMgr.spawnTaskNamed(Task.Task(self.dataloop, -50), 'dataloop')
  324. self.eventMgr.restart()
  325. def shutdown(self):
  326. self.taskMgr.removeTasksNamed('igloop')
  327. self.taskMgr.removeTasksNamed('dataloop')
  328. self.eventMgr.shutdown()
  329. def toggleBackface(self):
  330. return toggleBackface(self.render.arc())
  331. def backfaceCullingOn(self):
  332. if self.toggleBackface():
  333. self.toggleBackface()
  334. def backfaceCullingOff(self):
  335. if not self.toggleBackface():
  336. self.toggleBackface()
  337. def toggleTexture(self):
  338. return toggleTexture(self.render.arc())
  339. def textureOn(self):
  340. if not self.toggleTexture():
  341. self.toggleTexture()
  342. def textureOff(self):
  343. if self.toggleTexture():
  344. self.toggleTexture()
  345. def toggleWireframe(self):
  346. return toggleWireframe(self.render.arc())
  347. def wireframeOn(self):
  348. if not self.toggleWireframe():
  349. self.toggleWireframe()
  350. def wireframeOff(self):
  351. if self.toggleWireframe():
  352. self.toggleWireframe()
  353. def disableMouse(self):
  354. """
  355. Temporarily disable the mouse control of the camera, either
  356. via the drive interface or the trackball, whichever is
  357. currently in use.
  358. """
  359. # We don't reparent the drive interface or the trackball;
  360. # whichever one was there before will remain in the data graph
  361. # and active. This way they won't lose button events while
  362. # the mouse is disabled. However, we do move the mouse2cam
  363. # object out of there, so we won't be updating the camera any
  364. # more.
  365. self.mouse2cam.reparentTo(self.dataUnused)
  366. def enableMouse(self):
  367. """
  368. Reverse the effect of a previous call to disableMouse().
  369. useDrive() also implicitly enables the mouse.
  370. """
  371. self.mouse2cam.reparentTo(self.mouseInterface)
  372. def setMouseOnArc(self, newArc):
  373. self.mouse2cam.node().setArc(newArc)
  374. def useDrive(self):
  375. """
  376. Switch mouse action to drive mode
  377. """
  378. # Get rid of the trackball
  379. self.trackball.reparentTo(self.dataUnused)
  380. # Update the mouseInterface to point to the drive
  381. self.mouseInterface = self.drive
  382. self.mouseInterfaceNode = self.mouseInterface.node()
  383. self.drive.node().reset()
  384. # Hookup the drive to the camera. Make sure it is first in
  385. # the list of children of the mouseValve.
  386. self.drive.reparentTo(self.mouseValve, 0)
  387. self.mouse2cam.reparentTo(self.drive)
  388. # Set the height to a good eyeheight
  389. self.drive.node().setZ(4.0)
  390. def useTrackball(self):
  391. """
  392. Switch mouse action to trackball mode
  393. """
  394. # Get rid of the drive
  395. self.drive.reparentTo(self.dataUnused)
  396. # Update the mouseInterface to point to the trackball
  397. self.mouseInterface = self.trackball
  398. self.mouseInterfaceNode = self.mouseInterface.node()
  399. # Hookup the trackball to the camera. Make sure it is first
  400. # in the list of children of the mouseValve.
  401. self.trackball.reparentTo(self.mouseValve, 0)
  402. self.mouse2cam.reparentTo(self.trackball)
  403. def oobe(self):
  404. """
  405. Enable a special "out-of-body experience" mouse-interface
  406. mode. This can be used when a "god" camera is needed; it
  407. moves the camera node out from under its normal node and sets
  408. the world up in trackball state. Button events are still sent
  409. to the normal mouse action node (e.g. the DriveInterface), and
  410. mouse events, if needed, may be sent to the normal node by
  411. holding down the Control key.
  412. This is different than useTrackball(), which simply changes
  413. the existing mouse action to a trackball interface. In fact,
  414. OOBE mode doesn't care whether useDrive() or useTrackball() is
  415. in effect; it just temporarily layers a new trackball
  416. interface on top of whatever the basic interface is. You can
  417. even switch between useDrive() and useTrackball() while OOBE
  418. mode is in effect.
  419. This is a toggle; the second time this function is called, it
  420. disables the mode.
  421. """
  422. # If oobeMode was never set, set it to false and create the
  423. # structures we need to implement OOBE.
  424. try:
  425. self.oobeMode
  426. except:
  427. self.oobeMode = 0
  428. self.oobeCamera = self.hidden.attachNewNode('oobeCamera')
  429. self.oobeCameraTrackball = self.oobeCamera.attachNewNode('oobeCameraTrackball')
  430. self.oobeControl = DataValve.Control()
  431. self.mouseValve.node().setControl(1, self.oobeControl)
  432. self.oobeTrackball = self.mouseValve.attachNewNode(Trackball('oobeTrackball'), 1)
  433. self.oobe2cam = self.oobeTrackball.attachNewNode(Transform2SG('oobe2cam'))
  434. self.oobe2cam.node().setArc(self.oobeCameraTrackball.arc())
  435. self.oobeButtonEventsType = TypeRegistry.ptr().findType('ButtonEvents_ButtonEventDataTransition')
  436. self.oobeVis = loader.loadModelOnce('models/misc/camera')
  437. if self.oobeVis:
  438. self.oobeVis.arc().setFinal(1)
  439. self.oobeCullFrustum = None
  440. # Make sure the MouseValve is monitoring the Control key.
  441. mods = ModifierButtons(self.mouseValve.node().getModifierButtons())
  442. mods.addButton(KeyboardButton.control())
  443. self.mouseValve.node().setModifierButtons(mods)
  444. if self.oobeMode:
  445. # Disable OOBE mode.
  446. if self.oobeCullFrustum != None:
  447. # First, disable OOBE cull mode.
  448. self.oobeCull()
  449. self.oobeControl.setOff()
  450. self.mouseControl.setOn()
  451. if self.oobeVis:
  452. self.oobeVis.reparentTo(self.hidden)
  453. self.cam.reparentTo(self.camera)
  454. self.oobeCamera.reparentTo(self.hidden)
  455. self.oobeMode = 0
  456. else:
  457. # Enable OOBE mode.
  458. mods = ModifierButtons(self.mouseValve.node().getModifierButtons())
  459. # We're in OOBE control mode without the control key.
  460. mods.allButtonsUp()
  461. self.oobeControl.setButtons(mods)
  462. # We're in traditional control mode with the control key.
  463. mods.buttonDown(KeyboardButton.control())
  464. self.mouseControl.setButtons(mods)
  465. # However, keyboard buttons always make it through to the
  466. # traditional controller, regardless of the control key.
  467. self.mouseValve.node().setFineControl(0, self.oobeButtonEventsType, self.onControl)
  468. # Make oobeCamera be a sibling of wherever camera is now.
  469. cameraParent = NodePath(self.camera)
  470. cameraParent.shorten(1)
  471. self.oobeCamera.reparentTo(cameraParent)
  472. self.oobeCamera.clearMat()
  473. # Set our initial OOB position to be just behind the camera.
  474. mat = Mat4.translateMat(0, -10, 3) * self.camera.getMat(cameraParent)
  475. mat.invertInPlace()
  476. self.oobeTrackball.node().setMat(mat)
  477. self.cam.reparentTo(self.oobeCameraTrackball)
  478. if self.oobeVis:
  479. self.oobeVis.reparentTo(self.camera)
  480. self.oobeMode = 1
  481. def oobeCull(self):
  482. """
  483. While in OOBE mode (see above), cull the viewing frustum as if
  484. it were still attached to our original camera. This allows us
  485. to visualize the effectiveness of our bounding volumes.
  486. """
  487. # First, make sure OOBE mode is enabled.
  488. try:
  489. if not self.oobeMode:
  490. self.oobe()
  491. except:
  492. self.oobe()
  493. if self.oobeCullFrustum == None:
  494. # Enable OOBE culling.
  495. pnode = ProjectionNode('oobeCull')
  496. pnode.setProjection(self.camNode.getProjection())
  497. self.oobeCullFrustum = self.camera.attachNewNode(pnode)
  498. # Assign each DisplayRegion shared by the camera to use
  499. # this cull frustum.
  500. numDrs = self.camNode.getNumDrs()
  501. for d in range(0, numDrs):
  502. dr = self.camNode.getDr(d)
  503. dr.setCullFrustum(pnode)
  504. else:
  505. # Disable OOBE culling.
  506. # Assign each DisplayRegion shared by the camera to use
  507. # the default cull frustum, the camera itself.
  508. numDrs = self.camNode.getNumDrs()
  509. for d in range(0, numDrs):
  510. dr = self.camNode.getDr(d)
  511. dr.setCullFrustum(self.camNode)
  512. self.oobeCullFrustum.removeNode()
  513. self.oobeCullFrustum = None
  514. def screenshot(self, namePrefix='screenshot'):
  515. # Get the current date and time to uniquify the image (down to the second)
  516. date = time.ctime(time.time())
  517. # Get the current frame count to uniqify it even more
  518. frameCount = globalClock.getFrameCount()
  519. # Replace spaces with dashes because unix does not like spaces in the filename
  520. date = date.replace(' ', '-')
  521. date = date.replace(':', '-')
  522. imageName = (namePrefix + '-' + date + '-' + str(frameCount) + '.bmp')
  523. self.notify.info("Taking screenshot: " + imageName)
  524. takeSnapshot(self.win, imageName)
  525. def DisableAudio(self):
  526. if self.wantSfx:
  527. self.sfxManager.setActive(0)
  528. if self.wantMusic:
  529. self.musicManager.setActive(0)
  530. self.notify.debug("Disabling audio")
  531. def EnableAudio(self):
  532. if self.wantSfx:
  533. self.sfxManager.setActive(1)
  534. if self.wantMusic:
  535. self.musicManager.setActive(1)
  536. self.notify.debug("Enabling audio")
  537. def run(self):
  538. self.taskMgr.run()