ShowBase.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  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. import __builtin__
  23. __builtin__.FADE_SORT_INDEX = 1000
  24. __builtin__.NO_FADE_SORT_INDEX = 2000
  25. globalClock = ClockObject.getGlobalClock()
  26. class ShowBase:
  27. notify = directNotify.newCategory("ShowBase")
  28. def __init__(self):
  29. # Get the dconfig object
  30. self.config = ConfigConfigureGetConfigConfigShowbase
  31. # Store dconfig variables
  32. self.wantTk = self.config.GetBool('want-tk', 0)
  33. self.wantAnySound = self.config.GetBool('want-sound', 1)
  34. self.wantSfx = self.config.GetBool('audio-sfx-active', 1)
  35. self.wantMusic = self.config.GetBool('audio-music-active', 1)
  36. self.wantFog = self.config.GetBool('want-fog', 1)
  37. if not (self.wantSfx or self.wantMusic):
  38. self.wantAnySound = None
  39. if not self.wantAnySound:
  40. self.wantSfx = None
  41. self.wantMusic = None
  42. self.musicManager = None
  43. self.sfxManager = None
  44. self.wantDIRECT = self.config.GetBool('want-directtools', 0)
  45. self.wantStats = self.config.GetBool('want-stats', 0)
  46. taskMgr.taskTimerVerbose = self.config.GetBool('task-timer-verbose', 0)
  47. taskMgr.pStatsTasks = self.config.GetBool('pstats-tasks', 0)
  48. # Set up the TaskManager to reset the PStats clock back
  49. # whenever we resume from a pause. This callback function is
  50. # a little hacky, but we can't call it directly from within
  51. # the TaskManager because he doesn't know about PStats (and
  52. # has to run before libpanda is even loaded).
  53. taskMgr.resumeFunc = PStatClient.resumeAfterPause
  54. fsmRedefine = self.config.GetBool('fsm-redefine', 0)
  55. State.FsmRedefine = fsmRedefine
  56. self.renderTop = NodePath(NamedNode('renderTop'))
  57. self.render = self.renderTop.attachNewNode('render')
  58. # Set a default "off color" (i.e. use poly color) for color transitions
  59. self.render.setColorOff()
  60. self.hidden = NodePath(NamedNode('hidden'))
  61. self.dataRoot = NodePath(NamedNode('dataRoot'), DataRelation.getClassType())
  62. # Cache the node so we do not ask for it every frame
  63. self.dataRootNode = self.dataRoot.node()
  64. self.dataUnused = NodePath(NamedNode('dataUnused'), DataRelation.getClassType())
  65. self.pipe = makeGraphicsPipe()
  66. chanConfig = makeGraphicsWindow(self.pipe, self.render.arc())
  67. self.win = chanConfig.getWin()
  68. # Now that we've assigned a window, assign an exitfunc.
  69. self.oldexitfunc = getattr(sys, 'exitfunc', None)
  70. sys.exitfunc = self.exitfunc
  71. # cameraList is a list of camera group nodes. There may
  72. # be more than one display region/camera node beneath each
  73. # one.
  74. self.cameraList = []
  75. for i in range(chanConfig.getNumGroups()):
  76. self.cameraList.append(self.render.attachNewNode(
  77. chanConfig.getGroupNode(i)))
  78. # this is how we know which display region cameras belong to which
  79. # camera group. display region i belongs to group self.groupList[i]
  80. self.groupList = []
  81. for i in range(chanConfig.getNumDrs()):
  82. self.groupList.append(chanConfig.getGroupMembership(i))
  83. self.camera = self.cameraList[0]
  84. # This is a placeholder for a CollisionTraverser. If someone
  85. # stores a CollisionTraverser pointer here, we'll traverse it
  86. # in the igloop task.
  87. self.cTrav = 0
  88. # This is a list of cams associated with the display region's cameras
  89. self.camList = []
  90. for camera in self.cameraList:
  91. self.camList.append( camera.find('**/+Camera') )
  92. # Set the default camera
  93. self.cam = self.camera.find('**/+Camera')
  94. # If you need to get a handle to the camera node itself, use
  95. # self.camNode.
  96. self.camNode = self.cam.node()
  97. # If you need to adjust camera parameters, like fov or
  98. # near/far clipping planes, use self.camLens
  99. self.camLens = self.camNode.getLens()
  100. # Set up a 2-d layer for drawing things behind Gui labels.
  101. self.render2d = NodePath(setupPanda2d(self.win, "render2d"))
  102. # The normal 2-d layer has an aspect ratio that matches the
  103. # window, but its coordinate system is square. This means
  104. # anything we parent to render2d gets stretched. For things
  105. # where that makes a difference, we set up aspect2d, which
  106. # scales things back to the right aspect ratio.
  107. # For now, we assume that the window will have an aspect ratio
  108. # matching that of a traditional PC screen.
  109. self.aspectRatio = 4.0 / 3.0
  110. self.aspect2d = self.render2d.attachNewNode(PGTop("aspect2d"))
  111. self.aspect2d.setScale(1.0 / self.aspectRatio, 1.0, 1.0)
  112. # And let's enforce that aspect ratio on the camera.
  113. self.camLens.setAspectRatio(self.aspectRatio)
  114. # It's important to know the bounds of the aspect2d screen.
  115. self.a2dTop = 1.0
  116. self.a2dBottom = -1.0
  117. self.a2dLeft = -self.aspectRatio
  118. self.a2dRight = self.aspectRatio
  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. mb = self.mouseWatcherNode.getModifierButtons()
  132. mb.addButton(KeyboardButton.shift())
  133. mb.addButton(KeyboardButton.control())
  134. mb.addButton(KeyboardButton.alt())
  135. self.mouseWatcherNode.setModifierButtons(mb)
  136. # We also create a DataValve object above the trackball/drive
  137. # interface, which will allow us to switch some of the mouse
  138. # control, without switching all of it, to another object
  139. # later (for instance, to enable OOBE mode--see oobe(),
  140. # below.)
  141. self.mouseValve = self.mouseWatcher.attachNewNode(DataValve('mouseValve'))
  142. # This Control object can be used to turn on and off mouse &
  143. # keyboard messages to the DriveInterface.
  144. self.mouseControl = DataValve.Control()
  145. self.mouseValve.node().setControl(0, self.mouseControl)
  146. # This Control object is always kept on, handy to have.
  147. self.onControl = DataValve.Control()
  148. # Now we have the main trackball & drive interfaces.
  149. # useTrackball() and useDrive() switch these in and out; only
  150. # one is in use at a given time.
  151. self.trackball = self.dataUnused.attachNewNode(Trackball('trackball'))
  152. self.drive = self.dataUnused.attachNewNode(DriveInterface('drive'))
  153. self.mouse2cam = self.dataUnused.attachNewNode(Transform2SG('mouse2cam'))
  154. self.mouse2cam.node().setArc(self.camera.arc())
  155. self.useDrive()
  156. self.buttonThrower = self.mouseWatcher.attachNewNode(ButtonThrower())
  157. # Set up gui mouse watcher
  158. self.aspect2d.node().setMouseWatcher(self.mouseWatcherNode)
  159. self.mouseWatcherNode.addRegion(PGMouseWatcherBackground())
  160. self.loader = Loader.Loader(self)
  161. self.eventMgr = eventMgr
  162. self.messenger = messenger
  163. self.taskMgr = taskMgr
  164. # Particle manager
  165. self.particleMgr = particleMgr
  166. self.particleMgr.setFrameStepping(1)
  167. self.particleMgrEnabled = 0
  168. # Physics manager
  169. self.physicsMgr = physicsMgr
  170. integrator = LinearEulerIntegrator()
  171. self.physicsMgr.attachLinearIntegrator(integrator)
  172. self.physicsMgrEnabled = 0
  173. self.physicsMgrAngular = 0
  174. self.createAudioManager()
  175. self.createStats()
  176. # Transition effects (fade, iris, etc)
  177. self.transitions = Transitions.Transitions(self.loader)
  178. self.AppHasAudioFocus = 1
  179. __builtin__.base = self
  180. __builtin__.render2d = self.render2d
  181. __builtin__.aspect2d = self.aspect2d
  182. __builtin__.render = self.render
  183. __builtin__.hidden = self.hidden
  184. __builtin__.camera = self.camera
  185. __builtin__.loader = self.loader
  186. __builtin__.taskMgr = self.taskMgr
  187. __builtin__.eventMgr = self.eventMgr
  188. __builtin__.messenger = self.messenger
  189. __builtin__.config = self.config
  190. __builtin__.run = self.run
  191. __builtin__.ostream = Notify.out()
  192. __builtin__.directNotify = directNotify
  193. # Tk
  194. if self.wantTk:
  195. import TkGlobal
  196. if self.wantDIRECT:
  197. import DirectSession
  198. direct.enable()
  199. else:
  200. __builtin__.direct = self.direct = None
  201. self.restart()
  202. def exitfunc(self):
  203. """exitfunc(self)
  204. This should be assigned to sys.exitfunc to be called just
  205. before Python shutdown. It guarantees that the Panda window
  206. is closed cleanly, so that we free system resources, restore
  207. the desktop and keyboard functionality, etc.
  208. """
  209. self.win.closeWindow()
  210. del self.win
  211. del self.pipe
  212. if self.oldexitfunc:
  213. self.oldexitfunc()
  214. def getAlt(self):
  215. return base.mouseWatcherNode.getModifierButtons().isDown(
  216. KeyboardButton.alt())
  217. def getShift(self):
  218. return base.mouseWatcherNode.getModifierButtons().isDown(
  219. KeyboardButton.shift())
  220. def getControl(self):
  221. return base.mouseWatcherNode.getModifierButtons().isDown(
  222. KeyboardButton.control())
  223. def addAngularIntegrator(self):
  224. """addAngularIntegrator(self)"""
  225. if (self.physicsMgrAngular == 0):
  226. self.physicsMgrAngular = 1
  227. integrator = AngularEulerIntegrator()
  228. self.physicsMgr.attachAngularIntegrator(integrator)
  229. def enableParticles(self):
  230. """enableParticles(self)"""
  231. self.particleMgrEnabled = 1
  232. self.physicsMgrEnabled = 1
  233. self.taskMgr.removeTasksNamed('manager-update')
  234. self.taskMgr.spawnTaskNamed(Task.Task(self.updateManagers),
  235. 'manager-update')
  236. def disableParticles(self):
  237. """enableParticles(self)"""
  238. self.particleMgrEnabled = 0
  239. self.physicsMgrEnabled = 0
  240. self.taskMgr.removeTasksNamed('manager-update')
  241. def toggleParticles(self):
  242. if self.particleMgrEnabled == 0:
  243. self.enableParticles()
  244. else:
  245. self.disableParticles()
  246. def isParticleMgrEnabled(self):
  247. return self.particleMgrEnabled
  248. def isPhysicsMgrEnabled(self):
  249. return self.physicsMgrEnabled
  250. def updateManagers(self, state):
  251. """updateManagers(self)"""
  252. dt = min(globalClock.getDt(), 0.1)
  253. if (self.particleMgrEnabled == 1):
  254. self.particleMgr.doParticles(dt)
  255. if (self.physicsMgrEnabled == 1):
  256. self.physicsMgr.doPhysics(dt)
  257. return Task.cont
  258. def createStats(self):
  259. # You must specify a pstats-host in your configrc
  260. # The default is localhost
  261. if self.wantStats:
  262. PStatClient.connect()
  263. def createAudioManager(self):
  264. if self.wantAnySound:
  265. if self.wantSfx:
  266. self.sfxManager = AudioManager.createAudioManager()
  267. if not self.sfxManager.isValid():
  268. self.wantSfx=None
  269. if self.wantMusic:
  270. self.musicManager = AudioManager.createAudioManager()
  271. # Turn down the music globally
  272. # Eventually we may want to control this in the options page
  273. self.musicManager.setVolume(0.7)
  274. if not self.musicManager.isValid():
  275. self.wantMusic=None
  276. if not (self.wantSfx or self.wantMusic):
  277. self.wantAnySound=None
  278. def loadSfx(self, name):
  279. if (name and base.wantSfx):
  280. sound=self.sfxManager.getSound(name)
  281. if sound == None:
  282. self.notify.warning("Could not load sound file %s." % name)
  283. return sound
  284. def loadMusic(self, name):
  285. if (name and base.wantMusic):
  286. sound=self.musicManager.getSound(name)
  287. if sound == None:
  288. self.notify.warning("Could not load music file %s." % name)
  289. return sound
  290. def unloadSfx(self, sfx):
  291. if sfx:
  292. del sfx
  293. def unloadMusic(self, music):
  294. if music:
  295. del music
  296. def playSfx(self, sfx, looping = 0, interupt = 1, volume = None,
  297. time = 0.):
  298. if (sfx and base.wantSfx):
  299. if volume != None:
  300. sfx.setVolume(volume)
  301. if interupt or (sfx.status() != AudioSound.PLAYING):
  302. sfx.setTime(time)
  303. sfx.setLoop(looping)
  304. sfx.play()
  305. def playMusic(self, music, looping = 0, interupt = 1, volume = None,
  306. time = 0.0):
  307. if (music and base.wantMusic):
  308. if volume != None:
  309. music.setVolume(volume)
  310. if interupt or (music.status() != AudioSound.PLAYING):
  311. music.setTime(time)
  312. music.setLoop(looping)
  313. music.play()
  314. def stopSfx(self, sfx):
  315. if (sfx and base.wantSfx):
  316. sfx.stop()
  317. def stopMusic(self, music):
  318. if (music and base.wantMusic):
  319. music.stop()
  320. def dataloop(self, state):
  321. # traverse the data graph. This reads all the control
  322. # inputs (from the mouse and keyboard, for instance) and also
  323. # directly acts upon them (for instance, to move the avatar).
  324. traverseDataGraph(self.dataRootNode)
  325. return Task.cont
  326. def igloop(self, state):
  327. # run the collision traversal if we have a
  328. # CollisionTraverser set.
  329. if self.cTrav:
  330. self.cTrav.traverse(self.render)
  331. # Finally, render the frame.
  332. self.win.update()
  333. globalClock.tick()
  334. return Task.cont
  335. def restart(self):
  336. self.shutdown()
  337. # give the igloop task a reasonably "late" priority,
  338. # so that it will get run after most tasks
  339. self.taskMgr.spawnTaskNamed(Task.Task(self.igloop, 50), 'igloop')
  340. # give the dataloop task a reasonably "early" priority,
  341. # so that it will get run before most tasks
  342. self.taskMgr.spawnTaskNamed(Task.Task(self.dataloop, -50), 'dataloop')
  343. self.eventMgr.restart()
  344. def shutdown(self):
  345. self.taskMgr.removeTasksNamed('igloop')
  346. self.taskMgr.removeTasksNamed('dataloop')
  347. self.eventMgr.shutdown()
  348. def toggleBackface(self):
  349. return toggleBackface(self.render.arc())
  350. def backfaceCullingOn(self):
  351. if self.toggleBackface():
  352. self.toggleBackface()
  353. def backfaceCullingOff(self):
  354. if not self.toggleBackface():
  355. self.toggleBackface()
  356. def toggleTexture(self):
  357. return toggleTexture(self.render.arc())
  358. def textureOn(self):
  359. if not self.toggleTexture():
  360. self.toggleTexture()
  361. def textureOff(self):
  362. if self.toggleTexture():
  363. self.toggleTexture()
  364. def toggleWireframe(self):
  365. return toggleWireframe(self.render.arc())
  366. def wireframeOn(self):
  367. if not self.toggleWireframe():
  368. self.toggleWireframe()
  369. def wireframeOff(self):
  370. if self.toggleWireframe():
  371. self.toggleWireframe()
  372. def disableMouse(self):
  373. """
  374. Temporarily disable the mouse control of the camera, either
  375. via the drive interface or the trackball, whichever is
  376. currently in use.
  377. """
  378. # We don't reparent the drive interface or the trackball;
  379. # whichever one was there before will remain in the data graph
  380. # and active. This way they won't lose button events while
  381. # the mouse is disabled. However, we do move the mouse2cam
  382. # object out of there, so we won't be updating the camera any
  383. # more.
  384. self.mouse2cam.reparentTo(self.dataUnused)
  385. def enableMouse(self):
  386. """
  387. Reverse the effect of a previous call to disableMouse().
  388. useDrive() also implicitly enables the mouse.
  389. """
  390. self.mouse2cam.reparentTo(self.mouseInterface)
  391. def setMouseOnArc(self, newArc):
  392. self.mouse2cam.node().setArc(newArc)
  393. def useDrive(self):
  394. """
  395. Switch mouse action to drive mode
  396. """
  397. # Get rid of the trackball
  398. self.trackball.reparentTo(self.dataUnused)
  399. # Update the mouseInterface to point to the drive
  400. self.mouseInterface = self.drive
  401. self.mouseInterfaceNode = self.mouseInterface.node()
  402. self.drive.node().reset()
  403. # Hookup the drive to the camera. Make sure it is first in
  404. # the list of children of the mouseValve.
  405. self.drive.reparentTo(self.mouseValve, 0)
  406. self.mouse2cam.reparentTo(self.drive)
  407. # Set the height to a good eyeheight
  408. self.drive.node().setZ(4.0)
  409. def useTrackball(self):
  410. """
  411. Switch mouse action to trackball mode
  412. """
  413. # Get rid of the drive
  414. self.drive.reparentTo(self.dataUnused)
  415. # Update the mouseInterface to point to the trackball
  416. self.mouseInterface = self.trackball
  417. self.mouseInterfaceNode = self.mouseInterface.node()
  418. # Hookup the trackball to the camera. Make sure it is first
  419. # in the list of children of the mouseValve.
  420. self.trackball.reparentTo(self.mouseValve, 0)
  421. self.mouse2cam.reparentTo(self.trackball)
  422. def oobe(self):
  423. """
  424. Enable a special "out-of-body experience" mouse-interface
  425. mode. This can be used when a "god" camera is needed; it
  426. moves the camera node out from under its normal node and sets
  427. the world up in trackball state. Button events are still sent
  428. to the normal mouse action node (e.g. the DriveInterface), and
  429. mouse events, if needed, may be sent to the normal node by
  430. holding down the Control key.
  431. This is different than useTrackball(), which simply changes
  432. the existing mouse action to a trackball interface. In fact,
  433. OOBE mode doesn't care whether useDrive() or useTrackball() is
  434. in effect; it just temporarily layers a new trackball
  435. interface on top of whatever the basic interface is. You can
  436. even switch between useDrive() and useTrackball() while OOBE
  437. mode is in effect.
  438. This is a toggle; the second time this function is called, it
  439. disables the mode.
  440. """
  441. # If oobeMode was never set, set it to false and create the
  442. # structures we need to implement OOBE.
  443. try:
  444. self.oobeMode
  445. except:
  446. self.oobeMode = 0
  447. self.oobeCamera = self.hidden.attachNewNode('oobeCamera')
  448. self.oobeCameraTrackball = self.oobeCamera.attachNewNode('oobeCameraTrackball')
  449. self.oobeLens = PerspectiveLens()
  450. self.oobeLens.setAspectRatio(self.aspectRatio)
  451. self.oobeLens.setNearFar(0.1, 10000.0)
  452. self.oobeLens.setFov(52.0)
  453. self.oobeControl = DataValve.Control()
  454. self.mouseValve.node().setControl(1, self.oobeControl)
  455. self.oobeTrackball = self.mouseValve.attachNewNode(Trackball('oobeTrackball'), 1)
  456. self.oobe2cam = self.oobeTrackball.attachNewNode(Transform2SG('oobe2cam'))
  457. self.oobe2cam.node().setArc(self.oobeCameraTrackball.arc())
  458. self.oobeButtonEventsType = TypeRegistry.ptr().findType('ButtonEvents_ButtonEventDataTransition')
  459. self.oobeVis = loader.loadModelOnce('models/misc/camera')
  460. if self.oobeVis:
  461. self.oobeVis.arc().setFinal(1)
  462. self.oobeCullFrustum = None
  463. self.oobeCullFrustumVis = None
  464. # Make sure the MouseValve is monitoring the Control key.
  465. mods = ModifierButtons(self.mouseValve.node().getModifierButtons())
  466. mods.addButton(KeyboardButton.control())
  467. self.mouseValve.node().setModifierButtons(mods)
  468. if self.oobeMode:
  469. # Disable OOBE mode.
  470. if self.oobeCullFrustum != None:
  471. # First, disable OOBE cull mode.
  472. self.oobeCull()
  473. self.oobeControl.setOff()
  474. self.mouseControl.setOn()
  475. if self.oobeVis:
  476. self.oobeVis.reparentTo(self.hidden)
  477. self.cam.reparentTo(self.camera)
  478. self.camNode.setLens(self.camLens)
  479. self.oobeCamera.reparentTo(self.hidden)
  480. self.oobeMode = 0
  481. else:
  482. # Enable OOBE mode.
  483. mods = ModifierButtons(self.mouseValve.node().getModifierButtons())
  484. # We're in OOBE control mode without the control key.
  485. mods.allButtonsUp()
  486. self.oobeControl.setButtons(mods)
  487. # We're in traditional control mode with the control key.
  488. mods.buttonDown(KeyboardButton.control())
  489. self.mouseControl.setButtons(mods)
  490. # However, keyboard buttons always make it through to the
  491. # traditional controller, regardless of the control key.
  492. self.mouseValve.node().setFineControl(0, self.oobeButtonEventsType, self.onControl)
  493. # Make oobeCamera be a sibling of wherever camera is now.
  494. cameraParent = self.camera.getParent()
  495. self.oobeCamera.reparentTo(cameraParent)
  496. self.oobeCamera.clearMat()
  497. # Set our initial OOB position to be just behind the camera.
  498. mat = Mat4.translateMat(0, -10, 3) * self.camera.getMat(cameraParent)
  499. mat.invertInPlace()
  500. self.oobeTrackball.node().setMat(mat)
  501. self.cam.reparentTo(self.oobeCameraTrackball)
  502. self.camNode.setLens(self.oobeLens)
  503. if self.oobeVis:
  504. self.oobeVis.reparentTo(self.camera)
  505. self.oobeMode = 1
  506. def oobeCull(self):
  507. """
  508. While in OOBE mode (see above), cull the viewing frustum as if
  509. it were still attached to our original camera. This allows us
  510. to visualize the effectiveness of our bounding volumes.
  511. """
  512. # First, make sure OOBE mode is enabled.
  513. try:
  514. if not self.oobeMode:
  515. self.oobe()
  516. except:
  517. self.oobe()
  518. if self.oobeCullFrustum == None:
  519. # Enable OOBE culling.
  520. pnode = LensNode('oobeCull')
  521. pnode.setLens(self.camLens)
  522. self.oobeCullFrustum = self.camera.attachNewNode(pnode)
  523. # Create a visible representation of the frustum.
  524. geom = self.camLens.makeGeometry()
  525. if geom != None:
  526. gn = GeomNode('frustum')
  527. gn.addGeom(geom)
  528. self.oobeCullFrustumVis = self.oobeVis.attachNewNode(gn)
  529. # Assign each DisplayRegion shared by the camera to use
  530. # this cull frustum.
  531. numDrs = self.camNode.getNumDrs()
  532. for d in range(0, numDrs):
  533. dr = self.camNode.getDr(d)
  534. dr.setCullFrustum(pnode)
  535. else:
  536. # Disable OOBE culling.
  537. # Assign each DisplayRegion shared by the camera to use
  538. # the default cull frustum, the camera itself.
  539. numDrs = self.camNode.getNumDrs()
  540. for d in range(0, numDrs):
  541. dr = self.camNode.getDr(d)
  542. dr.setCullFrustum(self.camNode)
  543. self.oobeCullFrustum.removeNode()
  544. self.oobeCullFrustum = None
  545. if self.oobeCullFrustumVis != None:
  546. self.oobeCullFrustumVis.removeNode()
  547. self.oobeCullFrustumVis = None
  548. def screenshot(self, namePrefix='screenshot'):
  549. # Get the current date and time to uniquify the image (down to the second)
  550. date = time.ctime(time.time())
  551. # Get the current frame count to uniqify it even more
  552. frameCount = globalClock.getFrameCount()
  553. # Replace spaces with dashes because unix does not like spaces in the filename
  554. date = date.replace(' ', '-')
  555. date = date.replace(':', '-')
  556. imageName = (namePrefix + '-' + date + '-' + str(frameCount) + '.bmp')
  557. self.notify.info("Taking screenshot: " + imageName)
  558. takeSnapshot(self.win, imageName)
  559. def movie(self, namePrefix = 'movie', duration = 1.0, fps = 30,
  560. format = 'rgb', sd = 4):
  561. """
  562. movie(namePrefix = 'movie', duration=1.0, fps=30, format='rgb', sd=4)
  563. Spawn a task to capture a movie using the takeSnapshot function.
  564. - namePrefix will be used to form output file names (can include
  565. path information (e.g. 'I:/beta/frames/myMovie')
  566. - duration is the length of the movie in seconds
  567. - fps is the frame rate of the resulting movie
  568. - format specifies output file format (e.g. rgb, bmp)
  569. - sd specifies number of significant digits for frame count in the
  570. output file name (e.g. if sd = 4, movie_0001.rgb)
  571. """
  572. globalClock.setMode(ClockObject.MNonRealTime)
  573. globalClock.setDt(1.0/float(fps))
  574. t = taskMgr.spawnMethodNamed(self._movieTask, namePrefix + '_task')
  575. t.endT = globalClock.getFrameTime() + duration
  576. t.frameIndex = 1
  577. t.outputString = namePrefix + '_%0' + `sd` + 'd.' + format
  578. t.uponDeath = lambda state: globalClock.setMode(ClockObject.MNormal)
  579. def _movieTask(self, state):
  580. currT = globalClock.getFrameTime()
  581. if currT >= state.endT:
  582. return Task.done
  583. else:
  584. frameName = state.outputString % state.frameIndex
  585. self.notify.info("Capturing frame: " + frameName)
  586. takeSnapshot(self.win, frameName )
  587. state.frameIndex += 1
  588. return Task.cont
  589. # these are meant to be called in response to a user request
  590. def EnableMusic(self, bEnableMusic):
  591. if(self.musicManager == None):
  592. # would need to createaudiomanager/loadsfx for this to work. would that be safe after startup?
  593. self.notify.warning("Cant toggle music, must set audio-music-active #t in Configrc at startup")
  594. return 0
  595. self.wantMusic = bEnableMusic
  596. # dont setActive(1) if no audiofocus
  597. if(not (self.wantMusic and not self.AppHasAudioFocus)):
  598. self.musicManager.setActive(bEnableMusic)
  599. if(self.wantMusic):
  600. self.notify.debug("Enabling music")
  601. else:
  602. self.notify.debug("Disabling music")
  603. return 1
  604. def EnableSoundEffects(self, bEnableSoundEffects):
  605. if(self.sfxManager == None):
  606. # would need to createaudiomanager/loadsfx for this to work. would that be safe after startup?
  607. self.notify.warning("Cant toggle music, must set audio-music-active #t in Configrc at startup")
  608. return 0
  609. self.wantSfx = bEnableSoundEffects
  610. # dont setActive(1) if no audiofocus
  611. if(not (self.wantSfx and not self.AppHasAudioFocus)):
  612. self.sfxManager.setActive(bEnableSoundEffects)
  613. if(self.wantSfx):
  614. self.notify.debug("Enabling sound effects")
  615. else:
  616. self.notify.debug("Disabling sound effects")
  617. return 1
  618. # these are meant to be called by the sw when app loses audio focus (switched out)
  619. def DisableAudio(self):
  620. self.AppHasAudioFocus = 0
  621. if (self.wantSfx and (self.sfxManager != None)):
  622. self.sfxManager.setActive(0)
  623. if (self.wantMusic and (self.musicManager != None)):
  624. self.musicManager.setActive(0)
  625. self.notify.debug("Disabling audio")
  626. def EnableAudio(self):
  627. self.AppHasAudioFocus = 1
  628. if (self.wantSfx and (self.sfxManager != None)):
  629. self.sfxManager.setActive(1)
  630. if (self.wantMusic and (self.musicManager != None)):
  631. self.musicManager.setActive(1)
  632. self.notify.debug("Enabling audio")
  633. def run(self):
  634. self.taskMgr.run()