2
0

ShowBase.py 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214
  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. from IntervalManager import ivalMgr
  14. import Task
  15. import EventManager
  16. import math
  17. import sys
  18. import Loader
  19. import time
  20. import FSM
  21. import State
  22. import DirectObject
  23. import SfxPlayer
  24. if __debug__:
  25. import DeltaProfiler
  26. __builtins__["FADE_SORT_INDEX"] = 1000
  27. __builtins__["NO_FADE_SORT_INDEX"] = 2000
  28. # Now ShowBase is a DirectObject. We need this so ShowBase can hang
  29. # hooks on messages, particularly on window-event. This doesn't
  30. # *seem* to cause anyone any problems.
  31. class ShowBase(DirectObject.DirectObject):
  32. notify = directNotify.newCategory("ShowBase")
  33. def __init__(self):
  34. # Get the dconfig object
  35. self.config = ConfigConfigureGetConfigConfigShowbase
  36. if self.config.GetBool('use-vfs', 1):
  37. vfs = VirtualFileSystem.getGlobalPtr()
  38. else:
  39. vfs = None
  40. # Store dconfig variables
  41. self.sfxActive = self.config.GetBool('audio-sfx-active', 1)
  42. self.musicActive = self.config.GetBool('audio-music-active', 1)
  43. self.wantFog = self.config.GetBool('want-fog', 1)
  44. self.screenshotExtension = self.config.GetString('screenshot-extension', 'jpg')
  45. self.musicManager = None
  46. self.musicManagerIsValid = None
  47. self.sfxManagerList = []
  48. self.sfxManagerIsValidList = []
  49. self.wantStats = self.config.GetBool('want-stats', 0)
  50. # Fill this in with a function to invoke when the user "exits"
  51. # the program by closing the main window.
  52. self.exitFunc = None
  53. taskMgr.taskTimerVerbose = self.config.GetBool('task-timer-verbose', 0)
  54. taskMgr.extendedExceptions = self.config.GetBool('extended-exceptions', 0)
  55. taskMgr.pStatsTasks = self.config.GetBool('pstats-tasks', 0)
  56. # Set up the TaskManager to reset the PStats clock back
  57. # whenever we resume from a pause. This callback function is
  58. # a little hacky, but we can't call it directly from within
  59. # the TaskManager because he doesn't know about PStats (and
  60. # has to run before libpanda is even loaded).
  61. taskMgr.resumeFunc = PStatClient.resumeAfterPause
  62. fsmRedefine = self.config.GetBool('fsm-redefine', 0)
  63. State.FsmRedefine = fsmRedefine
  64. # This is used for syncing multiple PCs in a distributed cluster
  65. try:
  66. # Has the cluster sync variable been set externally?
  67. self.clusterSyncFlag = clusterSyncFlag
  68. except NameError:
  69. # Has the clusterSyncFlag been set via a config variable
  70. self.clusterSyncFlag = self.config.GetBool('cluster-sync', 0)
  71. self.hidden = NodePath('hidden')
  72. # We need a graphics engine to manage the actual rendering.
  73. self.graphicsEngine = GraphicsEngine()
  74. self.setupRender()
  75. self.setupRender2d()
  76. self.setupDataGraph()
  77. # This is a placeholder for a CollisionTraverser. If someone
  78. # stores a CollisionTraverser pointer here, we'll traverse it
  79. # in the collisionloop task.
  80. self.cTrav = 0
  81. # Ditto for an AppTraverser.
  82. self.appTrav = 0
  83. # This is the DataGraph traverser, which we might as well
  84. # create now.
  85. self.dgTrav = DataGraphTraverser()
  86. # base.win is the main, or only window; base.winList is a list of
  87. # *all* windows. Similarly with base.camList.
  88. self.win = None
  89. self.winList = []
  90. self.mainWinMinimized = 0
  91. self.pipe = None
  92. self.pipeList = []
  93. self.mak = None
  94. self.cam = None
  95. self.camList = []
  96. self.camNode = None
  97. self.camLens = None
  98. #self.camera = self.render.attachNewNode('camera')
  99. self.camera = None
  100. self.cameraList = []
  101. self.camera2d = self.render2d.attachNewNode('camera2d')
  102. # Now that we've set up the window structures, assign an exitfunc.
  103. self.oldexitfunc = getattr(sys, 'exitfunc', None)
  104. sys.exitfunc = self.exitfunc
  105. # Open the default rendering window.
  106. if self.config.GetBool('open-default-window', 1):
  107. self.openMainWindow()
  108. # Give the window a chance to truly open.
  109. self.graphicsEngine.renderFrame()
  110. self.graphicsEngine.renderFrame()
  111. if self.win != None and self.win.isClosed():
  112. self.notify.info("Window did not open, removing.")
  113. self.closeWindow(self.win)
  114. if self.win == None:
  115. # Try a little harder if the window wouldn't open.
  116. self.makeAllPipes()
  117. while self.win == None and len(self.pipeList) > 1:
  118. self.pipeList.remove(self.pipe)
  119. self.pipe = self.pipeList[0]
  120. self.openMainWindow()
  121. self.graphicsEngine.renderFrame()
  122. self.graphicsEngine.renderFrame()
  123. if self.win != None and self.win.isClosed():
  124. self.notify.info("Window did not open, removing.")
  125. self.closeWindow(self.win)
  126. self.loader = Loader.Loader(self)
  127. self.eventMgr = eventMgr
  128. self.messenger = messenger
  129. self.taskMgr = taskMgr
  130. # Particle manager
  131. self.particleMgr = particleMgr
  132. self.particleMgr.setFrameStepping(1)
  133. self.particleMgrEnabled = 0
  134. # Physics manager
  135. self.physicsMgr = physicsMgr
  136. integrator = LinearEulerIntegrator()
  137. self.physicsMgr.attachLinearIntegrator(integrator)
  138. self.physicsMgrEnabled = 0
  139. self.physicsMgrAngular = 0
  140. self.createBaseAudioManagers()
  141. self.createStats()
  142. self.AppHasAudioFocus = 1
  143. __builtins__["base"] = self
  144. __builtins__["render2d"] = self.render2d
  145. __builtins__["aspect2d"] = self.aspect2d
  146. __builtins__["render"] = self.render
  147. __builtins__["hidden"] = self.hidden
  148. __builtins__["camera"] = self.camera
  149. __builtins__["loader"] = self.loader
  150. __builtins__["taskMgr"] = self.taskMgr
  151. __builtins__["eventMgr"] = self.eventMgr
  152. __builtins__["messenger"] = self.messenger
  153. __builtins__["config"] = self.config
  154. __builtins__["run"] = self.run
  155. __builtins__["ostream"] = Notify.out()
  156. __builtins__["directNotify"] = directNotify
  157. __builtins__["globalClock"] = ClockObject.getGlobalClock()
  158. __builtins__["vfs"] = vfs
  159. if __debug__:
  160. __builtins__["deltaProfiler"] = DeltaProfiler.DeltaProfiler("ShowBase")
  161. # Now hang a hook on the window-event from Panda. This allows
  162. # us to detect when the user resizes, minimizes, or closes the
  163. # main window.
  164. self.accept('window-event', self.__windowEvent)
  165. # Transition effects (fade, iris, etc)
  166. import Transitions
  167. self.transitions = Transitions.Transitions(self.loader)
  168. # Start Tk and DIRECT if specified by Configrc
  169. self.startTk(self.config.GetBool('want-tk', 0))
  170. # Start DIRECT if specified in Configrc or in cluster mode
  171. fDirect = (self.config.GetBool('want-directtools', 0) or
  172. (base.config.GetString("cluster-mode", '') != ''))
  173. self.startDirect(fDirect)
  174. # Start IGLOOP
  175. self.restart()
  176. def exitfunc(self):
  177. """
  178. This should be assigned to sys.exitfunc to be called just
  179. before Python shutdown. It guarantees that the Panda window
  180. is closed cleanly, so that we free system resources, restore
  181. the desktop and keyboard functionality, etc.
  182. """
  183. self.graphicsEngine.removeAllWindows()
  184. del self.win
  185. del self.winList
  186. del self.pipe
  187. del self.musicManager
  188. del self.sfxManagerList
  189. try:
  190. direct.panel.destroy()
  191. except StandardError:
  192. pass
  193. if self.oldexitfunc:
  194. self.oldexitfunc()
  195. def makeDefaultPipe(self):
  196. """
  197. Creates the default GraphicsPipe, which will be used to make
  198. windows unless otherwise specified.
  199. """
  200. assert(self.pipe == None)
  201. selection = GraphicsPipeSelection.getGlobalPtr()
  202. selection.printPipeTypes()
  203. self.pipe = selection.makeDefaultPipe()
  204. if not self.pipe:
  205. self.notify.error("No graphics pipe is available! Check your Configrc!")
  206. self.notify.info("Default graphics pipe is %s (%s)." % (self.pipe.getInterfaceName(), self.pipe.getType().getName()))
  207. self.pipeList.append(self.pipe)
  208. def makeAllPipes(self):
  209. """
  210. Creates all GraphicsPipes that the system knows about and fill up
  211. self.pipeList with them.
  212. """
  213. shouldPrintPipes = 0
  214. selection = GraphicsPipeSelection.getGlobalPtr()
  215. selection.loadAuxModules()
  216. # First, we should make sure the default pipe exists.
  217. if self.pipe == None:
  218. self.makeDefaultPipe()
  219. # Now go through the list of known pipes, and make each one if
  220. # we don't have one already.
  221. numPipeTypes = selection.getNumPipeTypes()
  222. for i in range(numPipeTypes):
  223. pipeType = selection.getPipeType(i)
  224. # Do we already have a pipe of this type on the list?
  225. # This operation is n-squared, but presumably there won't
  226. # be more than a handful of pipe types, so who cares.
  227. already = 0
  228. for pipe in self.pipeList:
  229. if pipe.getType() == pipeType:
  230. already = 1
  231. if not already:
  232. pipe = selection.makePipe(pipeType)
  233. if pipe:
  234. self.notify.info("Got aux graphics pipe %s (%s)." % (pipe.getInterfaceName(), pipe.getType().getName()))
  235. self.pipeList.append(pipe)
  236. else:
  237. self.notify.info("Could not make graphics pipe %s." % (pipeType.getName()))
  238. def openWindow(self):
  239. """
  240. Invokes ChanConfig to create a window and adds it to the list
  241. of windows that are to be updated every frame.
  242. """
  243. if self.pipe == None:
  244. self.makeDefaultPipe()
  245. chanString = self.config.GetString('chan-config', 'single')
  246. chanConfig = ChanConfig(self.graphicsEngine, self.pipe, chanString,
  247. self.render)
  248. win = chanConfig.getWin()
  249. if win != None:
  250. # Adjust some of the window properties.
  251. props = WindowProperties()
  252. windowTitle = self.config.GetString("window-title", "");
  253. if windowTitle:
  254. props.setTitle(windowTitle)
  255. win.requestProperties(props)
  256. if self.win == None:
  257. self.win = win
  258. self.winList.append(win)
  259. self.getCameras(chanConfig)
  260. return win
  261. def closeWindow(self, win):
  262. """
  263. Closes the indicated window and removes it from the list of
  264. windows. If it is the main window, clears the main window
  265. pointer to None.
  266. """
  267. # First, remove all of the cameras associated with display
  268. # regions on the window.
  269. numRegions = win.getNumDisplayRegions()
  270. for i in range(numRegions):
  271. dr = win.getDisplayRegion(i)
  272. cam = NodePath(dr.getCamera())
  273. dr.setCamera(NodePath())
  274. if not cam.isEmpty() and cam.node().getNumDisplayRegions() == 0:
  275. # If the camera is used by no other DisplayRegions,
  276. # remove it.
  277. if self.camList.count(cam) != 0:
  278. self.camList.remove(cam)
  279. if not cam.isEmpty():
  280. camera = cam.getParent()
  281. if self.cameraList.count(camera) != 0:
  282. self.cameraList.remove(camera)
  283. # Don't throw away self.camera; we want to
  284. # preserve it for reopening the window.
  285. if cam == self.cam:
  286. self.cam = None
  287. cam.removeNode()
  288. # Now we can actually close the window.
  289. self.graphicsEngine.removeWindow(win)
  290. self.winList.remove(win)
  291. if win == self.win:
  292. self.win = None
  293. def openMainWindow(self):
  294. """
  295. Creates the initial, main window for the application, and sets
  296. up the mouse and render2d structures appropriately for it. If
  297. this method is called a second time, it will close the
  298. previous main window and open a new one, preserving the lens
  299. properties in base.camLens.
  300. The return value is true on success, or false on failure (in
  301. which case base.win may be either None, or the previous,
  302. closed window).
  303. """
  304. success = 1
  305. oldWin = self.win
  306. oldLens = self.camLens
  307. oldClearColorActive = None
  308. if self.win != None:
  309. # Close the previous window.
  310. oldClearColorActive = self.win.getClearColorActive()
  311. oldClearColor = VBase4(self.win.getClearColor())
  312. oldClearDepthActive = self.win.getClearDepthActive()
  313. oldClearDepth = self.win.getClearDepth()
  314. self.closeWindow(self.win)
  315. # Open a new window.
  316. self.openWindow()
  317. if self.win == None:
  318. self.win = oldWin
  319. self.winList.append(oldWin)
  320. success = 0
  321. if self.win != None:
  322. self.setupMouse(self.win)
  323. self.makeCamera2d(self.win, -1, 1, -1, 1)
  324. if oldLens != None:
  325. # Restore the previous lens properties.
  326. self.camNode.setLens(oldLens)
  327. self.camLens = oldLens
  328. if oldClearColorActive != None:
  329. # Restore the previous clear properties.
  330. self.win.setClearColorActive(oldClearColorActive)
  331. self.win.setClearColor(oldClearColor)
  332. self.win.setClearDepthActive(oldClearDepthActive)
  333. self.win.setClearDepth(oldClearDepth)
  334. return success
  335. def setupRender(self):
  336. """
  337. Creates the render scene graph, the primary scene graph for
  338. rendering 3-d geometry.
  339. """
  340. self.render = NodePath('render')
  341. self.render.setTwoSided(0)
  342. self.backfaceCullingEnabled = 1
  343. self.textureEnabled = 1
  344. self.wireframeEnabled = 0
  345. def setupRender2d(self):
  346. """setupRender2d(self)
  347. Creates the render2d scene graph, the primary scene graph for
  348. 2-d objects and gui elements that are superimposed over the
  349. 3-d geometry in the window.
  350. """
  351. self.render2d = NodePath('render2d')
  352. # Set up some overrides to turn off certain properties which
  353. # we probably won't need for 2-d objects.
  354. # It's probably important to turn off the depth test, since
  355. # many 2-d objects will be drawn over each other without
  356. # regard to depth position.
  357. # We used to avoid clearing the depth buffer before drawing
  358. # render2d, but nowadays we clear it anyway, since we
  359. # occasionally want to put 3-d geometry under render2d, and
  360. # it's simplest (and seems to be easier on graphics drivers)
  361. # if the 2-d scene has been cleared first.
  362. dt = DepthTestAttrib.make(DepthTestAttrib.MNone)
  363. dw = DepthWriteAttrib.make(DepthWriteAttrib.MOff)
  364. #lt = LightTransition.allOff()
  365. self.render2d.node().setAttrib(dt)
  366. self.render2d.node().setAttrib(dw)
  367. #self.render2d.node().setAttrib(lt, 1)
  368. self.render2d.setMaterialOff(1)
  369. self.render2d.setTwoSided(1)
  370. # The normal 2-d layer has an aspect ratio that matches the
  371. # window, but its coordinate system is square. This means
  372. # anything we parent to render2d gets stretched. For things
  373. # where that makes a difference, we set up aspect2d, which
  374. # scales things back to the right aspect ratio.
  375. # For now, we assume that the window will have an aspect ratio
  376. # matching that of a traditional PC screen (w / h) = (4 / 3)
  377. self.aspectRatio = self.config.GetFloat('aspect-ratio', (4.0 / 3.0))
  378. self.aspect2d = self.render2d.attachNewNode(PGTop("aspect2d"))
  379. self.aspect2d.setScale(1.0 / self.aspectRatio, 1.0, 1.0)
  380. # It's important to know the bounds of the aspect2d screen.
  381. self.a2dTop = 1.0
  382. self.a2dBottom = -1.0
  383. self.a2dLeft = -self.aspectRatio
  384. self.a2dRight = self.aspectRatio
  385. def makeCamera2d(self, win, left, right, bottom, top):
  386. """makeCamera2d(self)
  387. Makes a new camera2d associated with the indicated window, and
  388. assigns it to render the indicated subrectangle of render2d.
  389. """
  390. # First, we need to make a new layer on the window.
  391. chan = win.getChannel(0)
  392. layer = chan.makeLayer()
  393. # And make a display region to cover the whole layer.
  394. dr = layer.makeDisplayRegion()
  395. # Enable clearing of the depth buffer on this new display
  396. # region (see the comment above).
  397. dr.setClearDepthActive(1)
  398. # Now make a new Camera node.
  399. cam2dNode = Camera('cam2d')
  400. lens = OrthographicLens()
  401. lens.setFilmSize(right - left, top - bottom)
  402. lens.setFilmOffset((right + left) * 0.5, (top + bottom) * 0.5)
  403. lens.setNearFar(-1000, 1000)
  404. cam2dNode.setLens(lens)
  405. cam2dNode.setScene(self.render2d)
  406. camera2d = self.camera2d.attachNewNode(cam2dNode)
  407. dr.setCamera(camera2d)
  408. return camera2d
  409. def setupDataGraph(self):
  410. """setupDataGraph(self)
  411. Creates the data graph and populates it with the basic input
  412. devices.
  413. """
  414. self.dataRoot = NodePath('dataRoot')
  415. # Cache the node so we do not ask for it every frame
  416. self.dataRootNode = self.dataRoot.node()
  417. self.dataUnused = NodePath('dataUnused')
  418. def setupMouse(self, win):
  419. """setupMouse(self, win)
  420. Creates the structures necessary to monitor the mouse input,
  421. using the indicated window. If the mouse has already been set
  422. up for a different window, this changes the mouse to reference
  423. the new window.
  424. """
  425. if self.mak != None:
  426. # The mouse has already been set up; reappropriate it.
  427. self.mak.node().setSource(win, 0)
  428. # Reset the currently-held modifier button list for good
  429. # measure.
  430. bt = self.buttonThrower.node()
  431. mb = ModifierButtons(bt.getModifierButtons())
  432. mb.allButtonsUp()
  433. bt.setModifierButtons(mb)
  434. return
  435. # The mouse has not yet been set up in this application;
  436. # create the mouse structures now.
  437. # We create both a MouseAndKeyboard object and a MouseWatcher object
  438. # for the window. The MouseAndKeyboard generates mouse events and
  439. # mouse button/keyboard events; the MouseWatcher passes them through
  440. # unchanged when the mouse is not over a 2-d button, and passes
  441. # nothing through when the mouse *is* over a 2-d button. Therefore,
  442. # objects that don't want to get events when the mouse is over a
  443. # button, like the driveInterface, should be parented to
  444. # mouseWatcher, while objects that want events in all cases, like the
  445. # chat interface, should be parented to mak.
  446. self.mak = self.dataRoot.attachNewNode(MouseAndKeyboard(win, 0, 'mak'))
  447. self.mouseWatcherNode = MouseWatcher('mouseWatcher')
  448. self.mouseWatcher = self.mak.attachNewNode(self.mouseWatcherNode)
  449. mb = self.mouseWatcherNode.getModifierButtons()
  450. mb.addButton(KeyboardButton.shift())
  451. mb.addButton(KeyboardButton.control())
  452. mb.addButton(KeyboardButton.alt())
  453. self.mouseWatcherNode.setModifierButtons(mb)
  454. # Now we have the main trackball & drive interfaces.
  455. # useTrackball() and useDrive() switch these in and out; only
  456. # one is in use at a given time.
  457. self.trackball = self.dataUnused.attachNewNode(Trackball('trackball'))
  458. self.drive = self.dataUnused.attachNewNode(DriveInterface('drive'))
  459. self.mouse2cam = self.dataUnused.attachNewNode(Transform2SG('mouse2cam'))
  460. self.mouse2cam.node().setNode(self.camera.node())
  461. # The default is trackball mode, which is more convenient for
  462. # ad-hoc development in Python using ShowBase. Applications
  463. # can explicitly call base.useDrive() if they prefer a drive
  464. # interface.
  465. self.mouseInterface = self.trackball
  466. self.useTrackball()
  467. # A ButtonThrower to generate events from the mouse and
  468. # keyboard buttons as they are pressed.
  469. self.buttonThrower = self.mouseWatcher.attachNewNode(ButtonThrower('buttons'))
  470. # Specialize the events based on whether the modifier keys are
  471. # being held down.
  472. mods = ModifierButtons()
  473. mods.addButton(KeyboardButton.shift())
  474. mods.addButton(KeyboardButton.control())
  475. mods.addButton(KeyboardButton.alt())
  476. self.buttonThrower.node().setModifierButtons(mods)
  477. # Tell the gui system about our new mouse watcher.
  478. self.aspect2d.node().setMouseWatcher(self.mouseWatcherNode)
  479. self.mouseWatcherNode.addRegion(PGMouseWatcherBackground())
  480. def enableSoftwareMousePointer(self):
  481. """enableSoftwareMousePointer(self)
  482. Creates some geometry and parents it to render2d to show
  483. the currently-known mouse position. Useful if the mouse
  484. pointer is invisible for some reason.
  485. """
  486. mouseViz = render2d.attachNewNode('mouseViz')
  487. lilsmiley = loader.loadModel('lilsmiley')
  488. lilsmiley.reparentTo(mouseViz)
  489. # Scale the smiley face to 32x32 pixels.
  490. lilsmiley.setScale(32.0 / self.win.getHeight() / self.aspectRatio, 1.0, 32.0 / self.win.getHeight())
  491. #self.mouseWatcherNode.setGeometry(mouseViz)
  492. def getCameras(self, chanConfig):
  493. """
  494. getCameras(self, chanConfig)
  495. Extracts the camera(s) out of the ChanConfig record, parents
  496. them all to base.camera, and adds them to base.camList.
  497. """
  498. for i in range(chanConfig.getNumGroups()):
  499. # Create a top level camera node for this group
  500. camera = NodePath(chanConfig.getGroupNode(i))
  501. # Extract camera
  502. cam = camera.find('**/+Camera')
  503. # A special case: if we have a camera but not a
  504. # cameraList, we must have just reopened the window. Use
  505. # that existing camera instead of creating a new one.
  506. if self.camera != None and len(self.cameraList) == 0:
  507. # Throw away the chancfg group in this special case.
  508. camera = self.camera
  509. cam.reparentTo(camera)
  510. else:
  511. camera.reparentTo(self.render)
  512. self.cameraList.append(camera)
  513. self.camList.append(cam)
  514. # Enforce our expected aspect ratio, overriding whatever
  515. # nonsense ChanConfig put in there.
  516. lens = cam.node().getLens()
  517. lens.setAspectRatio(self.aspectRatio)
  518. # Update main camera variables
  519. if self.camera == None:
  520. self.camera = self.cameraList[0]
  521. if self.cam == None:
  522. self.cam = self.camList[0]
  523. # If you need to get a handle to the camera node itself,
  524. # use self.camNode.
  525. self.camNode = self.cam.node()
  526. # If you need to adjust camera parameters, like fov or
  527. # near/far clipping planes, use self.camLens.
  528. self.camLens = self.camNode.getLens()
  529. def getAlt(self):
  530. return self.mouseWatcherNode.getModifierButtons().isDown(
  531. KeyboardButton.alt())
  532. def getShift(self):
  533. return self.mouseWatcherNode.getModifierButtons().isDown(
  534. KeyboardButton.shift())
  535. def getControl(self):
  536. return self.mouseWatcherNode.getModifierButtons().isDown(
  537. KeyboardButton.control())
  538. def addAngularIntegrator(self):
  539. """addAngularIntegrator(self)"""
  540. if (self.physicsMgrAngular == 0):
  541. self.physicsMgrAngular = 1
  542. integrator = AngularEulerIntegrator()
  543. self.physicsMgr.attachAngularIntegrator(integrator)
  544. def enableParticles(self):
  545. """enableParticles(self)"""
  546. self.particleMgrEnabled = 1
  547. self.physicsMgrEnabled = 1
  548. self.taskMgr.remove('manager-update')
  549. self.taskMgr.add(self.updateManagers, 'manager-update')
  550. def disableParticles(self):
  551. """enableParticles(self)"""
  552. self.particleMgrEnabled = 0
  553. self.physicsMgrEnabled = 0
  554. self.taskMgr.remove('manager-update')
  555. def toggleParticles(self):
  556. if self.particleMgrEnabled == 0:
  557. self.enableParticles()
  558. else:
  559. self.disableParticles()
  560. def isParticleMgrEnabled(self):
  561. return self.particleMgrEnabled
  562. def isPhysicsMgrEnabled(self):
  563. return self.physicsMgrEnabled
  564. def updateManagers(self, state):
  565. """updateManagers(self)"""
  566. dt = min(globalClock.getDt(), 0.1)
  567. if (self.particleMgrEnabled == 1):
  568. self.particleMgr.doParticles(dt)
  569. if (self.physicsMgrEnabled == 1):
  570. self.physicsMgr.doPhysics(dt)
  571. return Task.cont
  572. def createStats(self):
  573. # You must specify a pstats-host in your configrc
  574. # The default is localhost
  575. if self.wantStats:
  576. PStatClient.connect()
  577. def addSfxManager(self, extraSfxManager):
  578. # keep a list of sfx manager objects to apply settings to, since there may be others
  579. # in addition to the one we create here
  580. self.sfxManagerList.append(extraSfxManager)
  581. newSfxManagerIsValid = (extraSfxManager!=None) and extraSfxManager.isValid()
  582. self.sfxManagerIsValidList.append(newSfxManagerIsValid)
  583. if newSfxManagerIsValid:
  584. extraSfxManager.setActive(self.sfxActive)
  585. def createBaseAudioManagers(self):
  586. self.sfxPlayer = SfxPlayer.SfxPlayer()
  587. sfxManager = AudioManager.createAudioManager()
  588. self.addSfxManager(sfxManager)
  589. self.musicManager = AudioManager.createAudioManager()
  590. self.musicManagerIsValid=self.musicManager!=None \
  591. and self.musicManager.isValid()
  592. if self.musicManagerIsValid:
  593. # ensure only 1 midi song is playing at a time:
  594. self.musicManager.setConcurrentSoundLimit(1)
  595. self.musicManager.setActive(self.musicActive)
  596. # enableMusic/enableSoundEffects are meant to be called in response to a user request
  597. # so sfxActive/musicActive represent how things *should* be, regardless of App/OS/HW state
  598. def enableMusic(self, bEnableMusic):
  599. # dont setActive(1) if no audiofocus
  600. if self.AppHasAudioFocus and self.musicManagerIsValid:
  601. self.musicManager.setActive(bEnableMusic)
  602. self.musicActive = bEnableMusic
  603. if bEnableMusic:
  604. self.notify.debug("Enabling music")
  605. else:
  606. self.notify.debug("Disabling music")
  607. def SetAllSfxEnables(self, bEnabled):
  608. for i in range(len(self.sfxManagerList)):
  609. if (self.sfxManagerIsValidList[i]):
  610. self.sfxManagerList[i].setActive(bEnabled)
  611. def enableSoundEffects(self, bEnableSoundEffects):
  612. # dont setActive(1) if no audiofocus
  613. if self.AppHasAudioFocus or (bEnableSoundEffects==0):
  614. self.SetAllSfxEnables(bEnableSoundEffects)
  615. self.sfxActive=bEnableSoundEffects
  616. if bEnableSoundEffects:
  617. self.notify.debug("Enabling sound effects")
  618. else:
  619. self.notify.debug("Disabling sound effects")
  620. # enable/disableAllAudio allow a programmable global override-off for current audio settings.
  621. # they're meant to be called when app loses audio focus (switched out), so we can turn off sound
  622. # without affecting internal sfxActive/musicActive sound settings, so things
  623. # come back ok when the app is switched back to
  624. def disableAllAudio(self):
  625. self.AppHasAudioFocus = 0
  626. self.SetAllSfxEnables(0)
  627. if self.musicManagerIsValid:
  628. self.musicManager.setActive(0)
  629. self.notify.debug("Disabling audio")
  630. def enableAllAudio(self):
  631. self.AppHasAudioFocus = 1
  632. self.SetAllSfxEnables(self.sfxActive)
  633. if self.musicManagerIsValid:
  634. self.musicManager.setActive(self.musicActive)
  635. self.notify.debug("Enabling audio")
  636. # This function should only be in the loader but is here for
  637. # backwards compatibility. Please do not add code here, add
  638. # it to the loader.
  639. def loadSfx(self, name):
  640. return self.loader.loadSfx(name)
  641. # This function should only be in the loader but is here for
  642. # backwards compatibility. Please do not add code here, add
  643. # it to the loader.
  644. def loadMusic(self, name):
  645. return self.loader.loadMusic(name)
  646. def playSfx(self, sfx, looping = 0, interrupt = 1, volume = None, time = 0.0, node = None):
  647. # This goes through a special player for potential localization
  648. return self.sfxPlayer.playSfx(sfx, looping, interrupt, volume, time, node)
  649. def playMusic(self, music, looping = 0, interrupt = 1, volume = None, time = 0.0):
  650. if music:
  651. if volume != None:
  652. music.setVolume(volume)
  653. # if interrupt was set to 0, start over even if it's already playing
  654. if interrupt or (music.status() != AudioSound.PLAYING):
  655. music.setTime(time)
  656. music.setLoop(looping)
  657. music.play()
  658. def dataloop(self, state):
  659. # traverse the data graph. This reads all the control
  660. # inputs (from the mouse and keyboard, for instance) and also
  661. # directly acts upon them (for instance, to move the avatar).
  662. self.dgTrav.traverse(self.dataRootNode)
  663. return Task.cont
  664. def ivalloop(self, state):
  665. # Execute all intervals in the global ivalMgr.
  666. ivalMgr.step()
  667. return Task.cont
  668. def collisionloop(self, state):
  669. # run the collision traversal if we have a
  670. # CollisionTraverser set.
  671. if self.cTrav:
  672. self.cTrav.traverse(self.render)
  673. if self.appTrav:
  674. self.appTrav.traverse(self.render)
  675. return Task.cont
  676. def igloop(self, state):
  677. # Finally, render the frame.
  678. self.graphicsEngine.renderFrame()
  679. if self.clusterSyncFlag:
  680. self.graphicsEngine.syncFrame()
  681. if self.mainWinMinimized:
  682. # If the main window is minimized, slow down the app a bit
  683. # by sleeping here in igloop so we don't use all available
  684. # CPU needlessly.
  685. # Note: this isn't quite right if multiple windows are
  686. # open. We should base this on whether *all* windows are
  687. # minimized, not just the main window. But it will do for
  688. # now until someone complains.
  689. time.sleep(0.1)
  690. # Lerp stuff needs this event, and it must be generated in
  691. # C++, not in Python.
  692. throwNewFrame()
  693. return Task.cont
  694. def restart(self):
  695. self.shutdown()
  696. # give the igloop task a reasonably "late" priority,
  697. # so that it will get run after most tasks
  698. self.taskMgr.add(self.igloop, 'igloop', priority = 50)
  699. # make the collisionloop task run before igloop,
  700. # but leave enough room for the app to insert tasks
  701. # between collisionloop and igloop
  702. self.taskMgr.add(self.collisionloop, 'collisionloop', priority = 30)
  703. # give the dataloop task a reasonably "early" priority,
  704. # so that it will get run before most tasks
  705. self.taskMgr.add(self.dataloop, 'dataloop', priority = -50)
  706. # spawn the ivalloop with a later priority, so that it will
  707. # run after most tasks, but before igloop.
  708. self.taskMgr.add(self.ivalloop, 'ivalloop', priority = 20)
  709. self.eventMgr.restart()
  710. def shutdown(self):
  711. self.taskMgr.remove('igloop')
  712. self.taskMgr.remove('collisionloop')
  713. self.taskMgr.remove('dataloop')
  714. self.taskMgr.remove('ivalloop')
  715. self.eventMgr.shutdown()
  716. def getBackgroundColor(self):
  717. """ Returns the current window background color. This assumes
  718. the window is set up to clear the color each frame (this is
  719. the normal setting). """
  720. return VBase4(self.win.getClearColor())
  721. def setBackgroundColor(self, *args):
  722. """ Sets the window background color to the indicated value.
  723. This assumes the window is set up to clear the color each
  724. frame (this is the normal setting).
  725. The color may be either a VBase3 or a VBase4, or a 3-component
  726. tuple, or the individual r, g, b parameters.
  727. """
  728. numArgs = len(args)
  729. if numArgs == 3 or numArgs == 4:
  730. color = VBase4(args[0], args[1], args[2], 1.0)
  731. elif numArgs == 1:
  732. arg = args[0]
  733. color = VBase4(arg[0], arg[1], arg[2], 1.0)
  734. else:
  735. raise TypeError, ('Invalid number of arguments: %d, expected 1, 3, or 4.' % numArgs)
  736. self.win.setClearColor(color)
  737. def toggleBackface(self):
  738. if self.backfaceCullingEnabled:
  739. self.backfaceCullingOff()
  740. else:
  741. self.backfaceCullingOn()
  742. def backfaceCullingOn(self):
  743. if not self.backfaceCullingEnabled:
  744. self.render.setTwoSided(0)
  745. self.backfaceCullingEnabled = 1
  746. def backfaceCullingOff(self):
  747. if self.backfaceCullingEnabled:
  748. self.render.setTwoSided(1)
  749. self.backfaceCullingEnabled = 0
  750. def toggleTexture(self):
  751. if self.textureEnabled:
  752. self.textureOff()
  753. else:
  754. self.textureOn()
  755. def textureOn(self):
  756. self.render.clearTexture()
  757. self.textureEnabled = 1
  758. def textureOff(self):
  759. self.render.setTextureOff(100)
  760. self.textureEnabled = 0
  761. def toggleWireframe(self):
  762. if self.wireframeEnabled:
  763. self.wireframeOff()
  764. else:
  765. self.wireframeOn()
  766. def wireframeOn(self):
  767. self.render.setRenderModeWireframe(100);
  768. self.render.setTwoSided(1);
  769. self.wireframeEnabled = 1
  770. def wireframeOff(self):
  771. self.render.clearRenderMode()
  772. render.setTwoSided(not self.backfaceCullingEnabled)
  773. self.wireframeEnabled = 0
  774. def disableMouse(self):
  775. """
  776. Temporarily disable the mouse control of the camera, either
  777. via the drive interface or the trackball, whichever is
  778. currently in use.
  779. """
  780. # We don't reparent the drive interface or the trackball;
  781. # whichever one was there before will remain in the data graph
  782. # and active. This way they won't lose button events while
  783. # the mouse is disabled. However, we do move the mouse2cam
  784. # object out of there, so we won't be updating the camera any
  785. # more.
  786. self.mouse2cam.reparentTo(self.dataUnused)
  787. def enableMouse(self):
  788. """
  789. Reverse the effect of a previous call to disableMouse().
  790. useDrive() also implicitly enables the mouse.
  791. """
  792. self.mouse2cam.reparentTo(self.mouseInterface)
  793. def setMouseOnNode(self, newNode):
  794. self.mouse2cam.node().setNode(newNode)
  795. def changeMouseInterface(self, changeTo):
  796. """
  797. Switch mouse action
  798. """
  799. # Get rid of the prior interface:
  800. self.mouseInterface.reparentTo(self.dataUnused)
  801. # Update the mouseInterface to point to the drive
  802. self.mouseInterface = changeTo
  803. self.mouseInterfaceNode = self.mouseInterface.node()
  804. # Hookup the drive to the camera.
  805. self.mouseInterface.reparentTo(self.mouseWatcher)
  806. self.mouse2cam.reparentTo(self.mouseInterface)
  807. def useDrive(self):
  808. """
  809. Switch mouse action to drive mode
  810. """
  811. self.changeMouseInterface(self.drive)
  812. # Set the height to a good eyeheight
  813. self.mouseInterfaceNode.reset()
  814. self.mouseInterfaceNode.setZ(4.0)
  815. def useTrackball(self):
  816. """
  817. Switch mouse action to trackball mode
  818. """
  819. self.changeMouseInterface(self.trackball)
  820. def oobe(self):
  821. """
  822. Enable a special "out-of-body experience" mouse-interface
  823. mode. This can be used when a "god" camera is needed; it
  824. moves the camera node out from under its normal node and sets
  825. the world up in trackball state. Button events are still sent
  826. to the normal mouse action node (e.g. the DriveInterface), and
  827. mouse events, if needed, may be sent to the normal node by
  828. holding down the Control key.
  829. This is different than useTrackball(), which simply changes
  830. the existing mouse action to a trackball interface. In fact,
  831. OOBE mode doesn't care whether useDrive() or useTrackball() is
  832. in effect; it just temporarily layers a new trackball
  833. interface on top of whatever the basic interface is. You can
  834. even switch between useDrive() and useTrackball() while OOBE
  835. mode is in effect.
  836. This is a toggle; the second time this function is called, it
  837. disables the mode.
  838. """
  839. # If oobeMode was never set, set it to false and create the
  840. # structures we need to implement OOBE.
  841. try:
  842. self.oobeMode
  843. except:
  844. self.oobeMode = 0
  845. self.oobeCamera = self.hidden.attachNewNode('oobeCamera')
  846. self.oobeCameraTrackball = self.oobeCamera.attachNewNode('oobeCameraTrackball')
  847. self.oobeLens = PerspectiveLens()
  848. self.oobeLens.setAspectRatio(self.aspectRatio)
  849. self.oobeLens.setNearFar(0.1, 10000.0)
  850. self.oobeLens.setFov(52.0)
  851. self.oobeTrackball = self.dataUnused.attachNewNode(Trackball('oobeTrackball'), 1)
  852. self.oobe2cam = self.oobeTrackball.attachNewNode(Transform2SG('oobe2cam'))
  853. self.oobe2cam.node().setNode(self.oobeCameraTrackball.node())
  854. self.oobeVis = loader.loadModelOnce('models/misc/camera')
  855. if self.oobeVis:
  856. self.oobeVis.node().setFinal(1)
  857. self.oobeCullFrustum = None
  858. self.oobeCullFrustumVis = None
  859. if self.oobeMode:
  860. # Disable OOBE mode.
  861. if self.oobeCullFrustum != None:
  862. # First, disable OOBE cull mode.
  863. self.oobeCull()
  864. if self.oobeVis:
  865. self.oobeVis.reparentTo(self.hidden)
  866. # Restore the mouse interface node.
  867. #self.mouseInterface.reparentTo(self.mouseWatcher)
  868. self.oobeTrackball.reparentTo(self.dataUnused)
  869. self.cam.reparentTo(self.camera)
  870. self.camNode.setLens(self.camLens)
  871. self.oobeCamera.reparentTo(self.hidden)
  872. self.oobeMode = 0
  873. else:
  874. # Make oobeCamera be a sibling of wherever camera is now.
  875. cameraParent = self.camera.getParent()
  876. self.oobeCamera.reparentTo(cameraParent)
  877. self.oobeCamera.clearMat()
  878. # Move aside the current mouse interface node and put the
  879. # oobeTrackball in its place.
  880. #self.mouseInterface.reparentTo(self.dataUnused)
  881. self.oobeTrackball.reparentTo(self.mouseWatcher)
  882. # Set our initial OOB position to be just behind the camera.
  883. mat = Mat4.translateMat(0, -10, 3) * self.camera.getMat(cameraParent)
  884. mat.invertInPlace()
  885. self.oobeTrackball.node().setMat(mat)
  886. self.cam.reparentTo(self.oobeCameraTrackball)
  887. self.camNode.setLens(self.oobeLens)
  888. if self.oobeVis:
  889. self.oobeVis.reparentTo(self.camera)
  890. self.oobeMode = 1
  891. def oobeCull(self):
  892. """
  893. While in OOBE mode (see above), cull the viewing frustum as if
  894. it were still attached to our original camera. This allows us
  895. to visualize the effectiveness of our bounding volumes.
  896. """
  897. # First, make sure OOBE mode is enabled.
  898. try:
  899. if not self.oobeMode:
  900. self.oobe()
  901. except:
  902. self.oobe()
  903. if self.oobeCullFrustum == None:
  904. # Enable OOBE culling.
  905. pnode = LensNode('oobeCull')
  906. pnode.setLens(self.camLens)
  907. self.oobeCullFrustum = self.camera.attachNewNode(pnode)
  908. # Create a visible representation of the frustum.
  909. geom = self.camLens.makeGeometry()
  910. if geom != None:
  911. gn = GeomNode('frustum')
  912. gn.addGeom(geom)
  913. self.oobeCullFrustumVis = self.oobeVis.attachNewNode(gn)
  914. # Assign each DisplayRegion shared by the camera to use
  915. # this cull frustum.
  916. numDisplayRegions = self.camNode.getNumDisplayRegions()
  917. for d in range(0, numDisplayRegions):
  918. dr = self.camNode.getDisplayRegion(d)
  919. dr.setCullFrustum(pnode)
  920. else:
  921. # Disable OOBE culling.
  922. # Assign each DisplayRegion shared by the camera to use
  923. # the default cull frustum, the camera itself.
  924. numDisplayRegions = self.camNode.getNumDisplayRegions()
  925. for d in range(0, numDisplayRegions):
  926. dr = self.camNode.getDisplayRegion(d)
  927. dr.setCullFrustum(self.camNode)
  928. self.oobeCullFrustum.removeNode()
  929. self.oobeCullFrustum = None
  930. if self.oobeCullFrustumVis != None:
  931. self.oobeCullFrustumVis.removeNode()
  932. self.oobeCullFrustumVis = None
  933. def screenshot(self, namePrefix='screenshot'):
  934. # Get the current date and time to uniquify the image (down to the second)
  935. date = time.ctime(time.time())
  936. # Get the current frame count to uniquify it even more
  937. frameCount = globalClock.getFrameCount()
  938. # Replace spaces with dashes because unix does not like spaces in the filename
  939. date = date.replace(' ', '-')
  940. date = date.replace(':', '-')
  941. imageName = ('%s-%s-%d.%s' % (namePrefix, date, frameCount, self.screenshotExtension))
  942. self.notify.info("Taking screenshot: " + imageName)
  943. takeSnapshot(self.win, imageName)
  944. # Announce to anybody that a screenshot has been taken
  945. messenger.send('screenshot')
  946. def movie(self, namePrefix = 'movie', duration = 1.0, fps = 30,
  947. format = 'rgb', sd = 4):
  948. """
  949. movie(namePrefix = 'movie', duration=1.0, fps=30, format='rgb', sd=4)
  950. Spawn a task to capture a movie using the takeSnapshot function.
  951. - namePrefix will be used to form output file names (can include
  952. path information (e.g. 'I:/beta/frames/myMovie')
  953. - duration is the length of the movie in seconds
  954. - fps is the frame rate of the resulting movie
  955. - format specifies output file format (e.g. rgb, bmp)
  956. - sd specifies number of significant digits for frame count in the
  957. output file name (e.g. if sd = 4, movie_0001.rgb)
  958. """
  959. globalClock.setMode(ClockObject.MNonRealTime)
  960. globalClock.setDt(1.0/float(fps))
  961. t = taskMgr.add(self._movieTask, namePrefix + '_task')
  962. t.endT = globalClock.getFrameTime() + duration
  963. t.frameIndex = 1
  964. t.outputString = namePrefix + '_%0' + `sd` + 'd.' + format
  965. t.uponDeath = lambda state: globalClock.setMode(ClockObject.MNormal)
  966. def _movieTask(self, state):
  967. currT = globalClock.getFrameTime()
  968. if currT >= state.endT:
  969. return Task.done
  970. else:
  971. frameName = state.outputString % state.frameIndex
  972. self.notify.info("Capturing frame: " + frameName)
  973. takeSnapshot(self.win, frameName )
  974. state.frameIndex += 1
  975. return Task.cont
  976. def __windowEvent(self, win):
  977. if win == self.win:
  978. properties = win.getProperties()
  979. self.notify.info("Got window event: %s" % (repr(properties)))
  980. if not properties.getOpen():
  981. # If the user closes the main window, we should exit.
  982. self.notify.info("User closed main window.")
  983. self.userExit()
  984. if properties.getMinimized() and not self.mainWinMinimized:
  985. # If the main window is minimized, throw an event to
  986. # stop the music.
  987. self.mainWinMinimized = 1
  988. messenger.send('PandaPaused')
  989. elif not properties.getMinimized() and self.mainWinMinimized:
  990. # If the main window is restored, throw an event to
  991. # restart the music.
  992. self.mainWinMinimized = 0
  993. messenger.send('PandaRestarted')
  994. def userExit(self):
  995. # The user has requested we exit the program. Deal with this.
  996. if self.exitFunc:
  997. self.exitFunc()
  998. self.notify.info("Exiting ShowBase.")
  999. sys.exit()
  1000. def startTk(self, fWantTk = 1):
  1001. self.wantTk = fWantTk
  1002. if self.wantTk:
  1003. import TkGlobal
  1004. TkGlobal.spawnTkLoop()
  1005. def startDirect(self, fWantDirect = 1):
  1006. self.wantDirect = fWantDirect
  1007. if self.wantDirect:
  1008. import DirectSession
  1009. direct.enable()
  1010. else:
  1011. __builtins__["direct"] = self.direct = None
  1012. def run(self):
  1013. self.taskMgr.run()