ShowBase.py 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541
  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 pandac.PandaModules import *
  6. # This needs to be available early for DirectGUI imports
  7. __builtins__["config"] = ConfigConfigureGetConfigConfigShowbase
  8. from direct.directnotify.DirectNotifyGlobal import *
  9. from MessengerGlobal import *
  10. from direct.task.TaskManagerGlobal import *
  11. from EventManagerGlobal import *
  12. from PythonUtil import *
  13. from direct.particles.ParticleManagerGlobal import *
  14. from PhysicsManagerGlobal import *
  15. from direct.interval.IntervalManager import ivalMgr
  16. from InputStateGlobal import inputState
  17. from direct.task import Task
  18. import EventManager
  19. import math
  20. import sys
  21. import Loader
  22. import time
  23. from direct.fsm import ClassicFSM
  24. from direct.fsm import State
  25. import DirectObject
  26. import SfxPlayer
  27. if __debug__:
  28. from direct.directutil import DeltaProfiler
  29. import OnScreenDebug
  30. __builtins__["FADE_SORT_INDEX"] = 1000
  31. __builtins__["NO_FADE_SORT_INDEX"] = 2000
  32. # Now ShowBase is a DirectObject. We need this so ShowBase can hang
  33. # hooks on messages, particularly on window-event. This doesn't
  34. # *seem* to cause anyone any problems.
  35. class ShowBase(DirectObject.DirectObject):
  36. notify = directNotify.newCategory("ShowBase")
  37. def __init__(self):
  38. # Get the dconfig object
  39. #self.config = ConfigConfigureGetConfigConfigShowbase
  40. self.config = config
  41. # Setup wantVerifyPdb as soon as reasonable:
  42. Verify.wantVerifyPdb = self.config.GetBool('want-verify-pdb', 0)
  43. self.printEnvDebugInfo()
  44. if self.config.GetBool('use-vfs', 1):
  45. vfs = VirtualFileSystem.getGlobalPtr()
  46. else:
  47. vfs = None
  48. self.nextWindowIndex = 1
  49. # Store dconfig variables
  50. self.sfxActive = self.config.GetBool('audio-sfx-active', 1)
  51. self.musicActive = self.config.GetBool('audio-music-active', 1)
  52. self.wantFog = self.config.GetBool('want-fog', 1)
  53. self.screenshotExtension = self.config.GetString('screenshot-extension', 'jpg')
  54. self.musicManager = None
  55. self.musicManagerIsValid = None
  56. self.sfxManagerList = []
  57. self.sfxManagerIsValidList = []
  58. self.wantStats = self.config.GetBool('want-pstats', 0)
  59. self.clientSleep = self.config.GetFloat('client-sleep', 0.)
  60. # magic-word override
  61. self.mwClientSleep = 0.
  62. # Fill this in with a function to invoke when the user "exits"
  63. # the program by closing the main window.
  64. self.exitFunc = None
  65. taskMgr.taskTimerVerbose = self.config.GetBool('task-timer-verbose', 0)
  66. taskMgr.extendedExceptions = self.config.GetBool('extended-exceptions', 0)
  67. taskMgr.pStatsTasks = self.config.GetBool('pstats-tasks', 0)
  68. # Set up the TaskManager to reset the PStats clock back
  69. # whenever we resume from a pause. This callback function is
  70. # a little hacky, but we can't call it directly from within
  71. # the TaskManager because he doesn't know about PStats (and
  72. # has to run before libpanda is even loaded).
  73. taskMgr.resumeFunc = PStatClient.resumeAfterPause
  74. fsmRedefine = self.config.GetBool('fsm-redefine', 0)
  75. State.FsmRedefine = fsmRedefine
  76. # Get the default window properties.
  77. winWidth = self.config.GetInt('win-width', 640)
  78. winHeight = self.config.GetInt('win-height', 480)
  79. winOriginX = self.config.GetInt('win-origin-x', -1)
  80. winOriginY = self.config.GetInt('win-origin-y', -1)
  81. fullscreen = self.config.GetBool('fullscreen', 0)
  82. undecorated = self.config.GetBool('undecorated', 0)
  83. cursorHidden = self.config.GetBool('cursor-hidden', 0)
  84. zOrder = self.config.GetString('z-order', 'normal')
  85. windowTitle = self.config.GetString('window-title', 'Panda')
  86. self.defaultWindowProps = WindowProperties()
  87. self.defaultWindowProps.setOpen(1)
  88. self.defaultWindowProps.setSize(winWidth, winHeight)
  89. if winOriginX >= 0 and winOriginY >= 0:
  90. self.defaultWindowProps.setOrigin(winOriginX, winOriginY)
  91. self.defaultWindowProps.setFullscreen(fullscreen)
  92. self.defaultWindowProps.setUndecorated(undecorated)
  93. self.defaultWindowProps.setCursorHidden(cursorHidden)
  94. if zOrder == 'bottom':
  95. self.defaultWindowProps.setZOrder(WindowProperties.ZBottom)
  96. elif zOrder == 'top':
  97. self.defaultWindowProps.setZOrder(WindowProperties.ZTop)
  98. elif zOrder != 'normal':
  99. self.notify.warning("Unknown z-order: %s" % (zOrder))
  100. self.defaultWindowProps.setTitle(windowTitle)
  101. # If the aspect ratio is 0 or None, it means to infer the
  102. # aspect ratio from the window size.
  103. self.aspectRatio = self.config.GetFloat('aspect-ratio', 0)
  104. # The default background color for a window.
  105. self.winBackgroundColor = VBase4(
  106. self.config.GetFloat('win-background-r', 0.41),
  107. self.config.GetFloat('win-background-g', 0.41),
  108. self.config.GetFloat('win-background-b', 0.41),
  109. 1.0)
  110. self.windowType = self.config.GetString('window-type', 'onscreen')
  111. # base.win is the main, or only window; base.winList is a list of
  112. # *all* windows. Similarly with base.camList.
  113. self.win = None
  114. self.frameRateMeter = None
  115. self.winList = []
  116. self.mainWinMinimized = 0
  117. self.pipe = None
  118. self.pipeList = []
  119. self.mak = None
  120. self.mouse2cam = None
  121. self.mouseInterface = None
  122. self.mouseWatcherNode = None
  123. self.drive = None
  124. self.trackball = None
  125. self.cam = None
  126. self.camList = []
  127. self.camNode = None
  128. self.camLens = None
  129. self.camera = None
  130. self.camera2d = None
  131. self.camFrustumVis = None
  132. # This is used for syncing multiple PCs in a distributed cluster
  133. try:
  134. # Has the cluster sync variable been set externally?
  135. self.clusterSyncFlag = clusterSyncFlag
  136. except NameError:
  137. # Has the clusterSyncFlag been set via a config variable
  138. self.clusterSyncFlag = self.config.GetBool('cluster-sync', 0)
  139. self.hidden = NodePath('hidden')
  140. # We need a graphics engine to manage the actual rendering.
  141. self.graphicsEngine = GraphicsEngine()
  142. self.setupRender()
  143. self.setupRender2d()
  144. self.setupDataGraph()
  145. # This is a placeholder for a CollisionTraverser. If someone
  146. # stores a CollisionTraverser pointer here, we'll traverse it
  147. # in the collisionloop task.
  148. self.shadowTrav = 0
  149. # in the collisionloop task.
  150. self.cTrav = 0
  151. # Ditto for an AppTraverser.
  152. self.appTrav = 0
  153. # This is the DataGraph traverser, which we might as well
  154. # create now.
  155. self.dgTrav = DataGraphTraverser()
  156. # Maybe create a RecorderController to record and/or play back
  157. # the user session.
  158. self.recorder = None
  159. playbackSession = self.config.GetString('playback-session', '')
  160. recordSession = self.config.GetString('record-session', '')
  161. if playbackSession:
  162. self.recorder = RecorderController()
  163. self.recorder.beginPlayback(Filename.fromOsSpecific(playbackSession))
  164. elif recordSession:
  165. self.recorder = RecorderController()
  166. self.recorder.beginRecord(Filename.fromOsSpecific(recordSession))
  167. if self.recorder:
  168. # If we're either playing back or recording, pass the
  169. # random seed into the system so each session will have
  170. # the same random seed.
  171. import random, whrandom
  172. seed = self.recorder.getRandomSeed()
  173. random.seed(seed)
  174. whrandom.seed(seed & 0xff, (seed >> 8) & 0xff, (seed >> 16) & 0xff)
  175. # Now that we've set up the window structures, assign an exitfunc.
  176. self.oldexitfunc = getattr(sys, 'exitfunc', None)
  177. sys.exitfunc = self.exitfunc
  178. # Open the default rendering window.
  179. if self.windowType != 'none':
  180. self.openDefaultWindow()
  181. self.loader = Loader.Loader(self)
  182. self.eventMgr = eventMgr
  183. self.messenger = messenger
  184. self.taskMgr = taskMgr
  185. # Particle manager
  186. self.particleMgr = particleMgr
  187. self.particleMgr.setFrameStepping(1)
  188. self.particleMgrEnabled = 0
  189. # Physics manager
  190. self.physicsMgr = physicsMgr
  191. integrator = LinearEulerIntegrator()
  192. self.physicsMgr.attachLinearIntegrator(integrator)
  193. self.physicsMgrEnabled = 0
  194. self.physicsMgrAngular = 0
  195. self.createBaseAudioManagers()
  196. self.createStats()
  197. self.AppHasAudioFocus = 1
  198. __builtins__["base"] = self
  199. __builtins__["render2d"] = self.render2d
  200. __builtins__["aspect2d"] = self.aspect2d
  201. __builtins__["render"] = self.render
  202. __builtins__["hidden"] = self.hidden
  203. __builtins__["camera"] = self.camera
  204. __builtins__["loader"] = self.loader
  205. __builtins__["taskMgr"] = self.taskMgr
  206. __builtins__["eventMgr"] = self.eventMgr
  207. __builtins__["messenger"] = self.messenger
  208. # Config needs to be defined before ShowBase is constructed
  209. #__builtins__["config"] = self.config
  210. __builtins__["run"] = self.run
  211. __builtins__["ostream"] = Notify.out()
  212. __builtins__["directNotify"] = directNotify
  213. __builtins__["globalClock"] = ClockObject.getGlobalClock()
  214. __builtins__["vfs"] = vfs
  215. if __debug__:
  216. __builtins__["deltaProfiler"] = DeltaProfiler.DeltaProfiler("ShowBase")
  217. __builtins__["onScreenDebug"] = OnScreenDebug.OnScreenDebug()
  218. # Now hang a hook on the window-event from Panda. This allows
  219. # us to detect when the user resizes, minimizes, or closes the
  220. # main window.
  221. self.accept('window-event', self.__windowEvent)
  222. # Transition effects (fade, iris, etc)
  223. import Transitions
  224. self.transitions = Transitions.Transitions(self.loader)
  225. # Start Tk and DIRECT if specified by Config.prc
  226. fTk = self.config.GetBool('want-tk', 0)
  227. # Start DIRECT if specified in Config.prc or in cluster mode
  228. fDirect = (self.config.GetBool('want-directtools', 0) or
  229. (base.config.GetString("cluster-mode", '') != ''))
  230. # Set fWantTk to 0 to avoid starting Tk with this call
  231. self.startDirect(fWantDirect = fDirect, fWantTk = fTk)
  232. # Start IGLOOP
  233. self.restart()
  234. def printEnvDebugInfo(self):
  235. """
  236. Print some information about the environment that we are running
  237. in. Stuff like the model paths and other paths. Feel free to
  238. add stuff to this.
  239. """
  240. if self.config.GetBool('want-env-debug-info', 0):
  241. print "\n\nEnvironment Debug Info {"
  242. print "* model path:", getModelPath()
  243. print "* texture path:", getTexturePath()
  244. print "* sound path:", getSoundPath()
  245. print "}"
  246. def exitfunc(self):
  247. """
  248. This should be assigned to sys.exitfunc to be called just
  249. before Python shutdown. It guarantees that the Panda window
  250. is closed cleanly, so that we free system resources, restore
  251. the desktop and keyboard functionality, etc.
  252. """
  253. self.graphicsEngine.removeAllWindows()
  254. del self.win
  255. del self.winList
  256. del self.pipe
  257. del self.musicManager
  258. del self.sfxManagerList
  259. try:
  260. direct.panel.destroy()
  261. except StandardError:
  262. pass
  263. if self.oldexitfunc:
  264. self.oldexitfunc()
  265. def makeDefaultPipe(self):
  266. """
  267. Creates the default GraphicsPipe, which will be used to make
  268. windows unless otherwise specified.
  269. """
  270. assert(self.pipe == None)
  271. selection = GraphicsPipeSelection.getGlobalPtr()
  272. selection.printPipeTypes()
  273. self.pipe = selection.makeDefaultPipe()
  274. if not self.pipe:
  275. self.notify.error(
  276. "No graphics pipe is available!\n"
  277. "Your Config.prc file must name at least one valid panda display\n"
  278. "library via load-display or aux-display.")
  279. self.notify.info("Default graphics pipe is %s (%s)." % (self.pipe.getInterfaceName(), self.pipe.getType().getName()))
  280. self.pipeList.append(self.pipe)
  281. def makeAllPipes(self):
  282. """
  283. Creates all GraphicsPipes that the system knows about and fill up
  284. self.pipeList with them.
  285. """
  286. shouldPrintPipes = 0
  287. selection = GraphicsPipeSelection.getGlobalPtr()
  288. selection.loadAuxModules()
  289. # First, we should make sure the default pipe exists.
  290. if self.pipe == None:
  291. self.makeDefaultPipe()
  292. # Now go through the list of known pipes, and make each one if
  293. # we don't have one already.
  294. numPipeTypes = selection.getNumPipeTypes()
  295. for i in range(numPipeTypes):
  296. pipeType = selection.getPipeType(i)
  297. # Do we already have a pipe of this type on the list?
  298. # This operation is n-squared, but presumably there won't
  299. # be more than a handful of pipe types, so who cares.
  300. already = 0
  301. for pipe in self.pipeList:
  302. if pipe.getType() == pipeType:
  303. already = 1
  304. if not already:
  305. pipe = selection.makePipe(pipeType)
  306. if pipe:
  307. self.notify.info("Got aux graphics pipe %s (%s)." % (pipe.getInterfaceName(), pipe.getType().getName()))
  308. self.pipeList.append(pipe)
  309. else:
  310. self.notify.info("Could not make graphics pipe %s." % (pipeType.getName()))
  311. def openWindow(self, props = None, pipe = None, gsg = None,
  312. type = None, name = None):
  313. """
  314. Creates a window and adds it to the list of windows that are
  315. to be updated every frame.
  316. """
  317. if pipe == None:
  318. pipe = self.pipe
  319. if pipe == None:
  320. self.makeDefaultPipe()
  321. pipe = self.pipe
  322. if pipe == None:
  323. # We couldn't get a pipe.
  324. return None
  325. if gsg == None:
  326. # If we weren't given a gsg, create a new one just for
  327. # this window.
  328. gsg = self.graphicsEngine.makeGsg(pipe)
  329. if gsg == None:
  330. # Couldn't make a gsg.
  331. return None
  332. if type == None:
  333. type = self.windowType
  334. if props == None:
  335. props = self.defaultWindowProps
  336. if name == None:
  337. name = 'window%s' % (self.nextWindowIndex)
  338. self.nextWindowIndex += 1
  339. win = None
  340. if type == 'onscreen':
  341. win = self.graphicsEngine.makeWindow(gsg, name, 0)
  342. elif type == 'offscreen':
  343. win = self.graphicsEngine.makeBuffer(
  344. gsg, name, 0, props.getXSize(), props.getYSize(), 0)
  345. if win == None:
  346. # Couldn't create a window!
  347. return None
  348. if hasattr(win, "requestProperties"):
  349. win.requestProperties(props)
  350. # By default, the window is cleared to the background color.
  351. win.setClearColorActive(1)
  352. win.setClearDepthActive(1)
  353. win.setClearColor(self.winBackgroundColor)
  354. win.setClearDepth(1.0)
  355. if self.win == None:
  356. self.win = win
  357. self.winList.append(win)
  358. # Set up a 3-d camera for the window by default.
  359. self.makeCamera(win)
  360. return win
  361. def closeWindow(self, win):
  362. """
  363. Closes the indicated window and removes it from the list of
  364. windows. If it is the main window, clears the main window
  365. pointer to None.
  366. """
  367. # First, remove all of the cameras associated with display
  368. # regions on the window.
  369. numRegions = win.getNumDisplayRegions()
  370. for i in range(numRegions):
  371. dr = win.getDisplayRegion(i)
  372. cam = NodePath(dr.getCamera())
  373. dr.setCamera(NodePath())
  374. if not cam.isEmpty() and cam.node().getNumDisplayRegions() == 0:
  375. # If the camera is used by no other DisplayRegions,
  376. # remove it.
  377. if self.camList.count(cam) != 0:
  378. self.camList.remove(cam)
  379. # Don't throw away self.camera; we want to
  380. # preserve it for reopening the window.
  381. if cam == self.cam:
  382. self.cam = None
  383. cam.removeNode()
  384. # Now we can actually close the window.
  385. self.graphicsEngine.removeWindow(win)
  386. self.winList.remove(win)
  387. if win == self.win:
  388. self.win = None
  389. self.frameRateMeter = None
  390. def openDefaultWindow(self):
  391. # Creates the main window for the first time, without being
  392. # too particular about the kind of graphics API that is
  393. # chosen. The suggested window type from the load-display
  394. # config variable is tried first; if that fails, the first
  395. # window type that can be successfully opened at all is
  396. # accepted. Returns true on success, false otherwise.
  397. #
  398. # This is intended to be called only once, at application
  399. # startup. It is normally called automatically unless
  400. # window-type is configured to 'none'.
  401. self.openMainWindow()
  402. # Give the window a chance to truly open.
  403. self.graphicsEngine.openWindows()
  404. if self.win != None and not self.isMainWindowOpen():
  405. self.notify.info("Window did not open, removing.")
  406. self.closeWindow(self.win)
  407. if self.win == None:
  408. # Try a little harder if the window wouldn't open.
  409. self.makeAllPipes()
  410. while self.win == None and len(self.pipeList) > 1:
  411. self.pipeList.remove(self.pipe)
  412. self.pipe = self.pipeList[0]
  413. self.openMainWindow()
  414. self.graphicsEngine.openWindows()
  415. if self.win != None and not self.isMainWindowOpen():
  416. self.notify.info("Window did not open, removing.")
  417. self.closeWindow(self.win)
  418. if self.win == None:
  419. # This doesn't really need to be an error condition, but I
  420. # figure any app that includes ShowBase really wants to
  421. # have a window open.
  422. # For toontown, it is possible that window open failed
  423. # because of a graphics card issue. In that case, take
  424. # user to the appropriate page.
  425. self.notify.error("Unable to open '%s' window." % (self.windowType))
  426. try:
  427. launcher.setPandaErrorCode(14)
  428. sys.exit(1)
  429. except:
  430. pass
  431. return (self.win != None)
  432. def isMainWindowOpen(self):
  433. if self.win != None:
  434. return self.win.isValid()
  435. return 0
  436. def openMainWindow(self):
  437. """
  438. Creates the initial, main window for the application, and sets
  439. up the mouse and render2d structures appropriately for it. If
  440. this method is called a second time, it will close the
  441. previous main window and open a new one, preserving the lens
  442. properties in base.camLens.
  443. The return value is true on success, or false on failure (in
  444. which case base.win may be either None, or the previous,
  445. closed window).
  446. """
  447. success = 1
  448. oldWin = self.win
  449. oldLens = self.camLens
  450. oldClearColorActive = None
  451. if self.win != None:
  452. # Close the previous window.
  453. oldClearColorActive = self.win.getClearColorActive()
  454. oldClearColor = VBase4(self.win.getClearColor())
  455. oldClearDepthActive = self.win.getClearDepthActive()
  456. oldClearDepth = self.win.getClearDepth()
  457. self.closeWindow(self.win)
  458. # Open a new window.
  459. self.openWindow()
  460. if self.win == None:
  461. self.win = oldWin
  462. self.winList.append(oldWin)
  463. success = 0
  464. if self.win != None:
  465. if isinstance(self.win, GraphicsWindow):
  466. self.setupMouse(self.win)
  467. self.makeCamera2d(self.win)
  468. if oldLens != None:
  469. # Restore the previous lens properties.
  470. self.camNode.setLens(oldLens)
  471. self.camLens = oldLens
  472. if oldClearColorActive != None:
  473. # Restore the previous clear properties.
  474. self.win.setClearColorActive(oldClearColorActive)
  475. self.win.setClearColor(oldClearColor)
  476. self.win.setClearDepthActive(oldClearDepthActive)
  477. self.win.setClearDepth(oldClearDepth)
  478. self.setFrameRateMeter(self.config.GetBool('show-frame-rate-meter', 0))
  479. return success
  480. def setFrameRateMeter(self, flag):
  481. """
  482. Turns on or off (according to flag) a standard frame rate
  483. meter in the upper-right corner of the main window.
  484. """
  485. if flag:
  486. if not self.frameRateMeter:
  487. self.frameRateMeter = FrameRateMeter('frameRateMeter')
  488. self.frameRateMeter.setupLayer(self.win)
  489. else:
  490. if self.frameRateMeter:
  491. self.frameRateMeter.clearLayer()
  492. self.frameRateMeter = None
  493. def setupRender(self):
  494. """
  495. Creates the render scene graph, the primary scene graph for
  496. rendering 3-d geometry.
  497. """
  498. self.render = NodePath('render')
  499. self.render.setTwoSided(0)
  500. self.backfaceCullingEnabled = 1
  501. self.textureEnabled = 1
  502. self.wireframeEnabled = 0
  503. def setupRender2d(self):
  504. """
  505. Creates the render2d scene graph, the primary scene graph for
  506. 2-d objects and gui elements that are superimposed over the
  507. 3-d geometry in the window.
  508. """
  509. self.render2d = NodePath('render2d')
  510. # Set up some overrides to turn off certain properties which
  511. # we probably won't need for 2-d objects.
  512. # It's probably important to turn off the depth test, since
  513. # many 2-d objects will be drawn over each other without
  514. # regard to depth position.
  515. # We used to avoid clearing the depth buffer before drawing
  516. # render2d, but nowadays we clear it anyway, since we
  517. # occasionally want to put 3-d geometry under render2d, and
  518. # it's simplest (and seems to be easier on graphics drivers)
  519. # if the 2-d scene has been cleared first.
  520. dt = DepthTestAttrib.make(DepthTestAttrib.MNone)
  521. dw = DepthWriteAttrib.make(DepthWriteAttrib.MOff)
  522. #lt = LightTransition.allOff()
  523. self.render2d.node().setAttrib(dt)
  524. self.render2d.node().setAttrib(dw)
  525. #self.render2d.node().setAttrib(lt, 1)
  526. self.render2d.setMaterialOff(1)
  527. self.render2d.setTwoSided(1)
  528. # The normal 2-d layer has an aspect ratio that matches the
  529. # window, but its coordinate system is square. This means
  530. # anything we parent to render2d gets stretched. For things
  531. # where that makes a difference, we set up aspect2d, which
  532. # scales things back to the right aspect ratio.
  533. aspectRatio = self.getAspectRatio()
  534. self.aspect2d = self.render2d.attachNewNode(PGTop("aspect2d"))
  535. self.aspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0)
  536. # It's important to know the bounds of the aspect2d screen.
  537. self.a2dTop = 1.0
  538. self.a2dBottom = -1.0
  539. self.a2dLeft = -aspectRatio
  540. self.a2dRight = aspectRatio
  541. def getAspectRatio(self, win = None):
  542. # Returns the actual aspect ratio of the indicated (or main
  543. # window), or the default aspect ratio if there is not yet a
  544. # main window.
  545. if self.aspectRatio:
  546. return self.aspectRatio
  547. aspectRatio = 1
  548. if win == None:
  549. win = self.win
  550. if win != None and win.hasSize():
  551. aspectRatio = float(win.getXSize()) / float(win.getYSize())
  552. else:
  553. props = self.defaultWindowProps
  554. if not props.hasSize():
  555. props = win.getRequestedProperties()
  556. if props.hasSize():
  557. aspectRatio = float(props.getXSize()) / float(props.getYSize())
  558. return aspectRatio
  559. def makeCamera(self, win, chan = None, layer = None, layerSort = 0,
  560. scene = None,
  561. displayRegion = (0, 1, 0, 1), aspectRatio = None):
  562. """
  563. Makes a new 3-d camera associated with the indicated window,
  564. and creates a display region in the indicated subrectangle.
  565. """
  566. if chan == None:
  567. chan = win.getChannel(0)
  568. if layer == None:
  569. # Make a new layer on the window.
  570. layer = chan.makeLayer(layerSort)
  571. if scene == None:
  572. scene = self.render
  573. # And make a display region on this layer of the requested
  574. # area.
  575. dr = layer.makeDisplayRegion(*displayRegion)
  576. # By default, we do not clear 3-d display regions (the entire
  577. # window will be cleared, which is normally sufficient).
  578. if aspectRatio == None:
  579. aspectRatio = self.getAspectRatio()
  580. # Now make a new Camera node.
  581. camNode = Camera('cam')
  582. lens = PerspectiveLens()
  583. lens.setAspectRatio(aspectRatio)
  584. camNode.setLens(lens)
  585. camNode.setScene(scene)
  586. # self.camera is the parent node of all cameras: a node that
  587. # we can move around to move all cameras as a group.
  588. if self.camera == None:
  589. self.camera = self.render.attachNewNode('camera')
  590. __builtins__["camera"] = self.camera
  591. cam = self.camera.attachNewNode(camNode)
  592. dr.setCamera(cam)
  593. if self.cam == None:
  594. self.cam = cam
  595. self.camNode = camNode
  596. self.camLens = lens
  597. self.camList.append(cam)
  598. return cam
  599. def makeCamera2d(self, win, chan = None, layer = None, layerSort = 10,
  600. displayRegion = (0, 1, 0, 1), coords = (-1, 1, -1, 1)):
  601. """
  602. Makes a new camera2d associated with the indicated window, and
  603. assigns it to render the indicated subrectangle of render2d.
  604. """
  605. if chan == None:
  606. chan = win.getChannel(0)
  607. if layer == None:
  608. # Make a new layer on the window.
  609. layer = chan.makeLayer(layerSort)
  610. # And make a display region on this layer of the requested
  611. # area.
  612. dr = layer.makeDisplayRegion(*displayRegion)
  613. # Enable clearing of the depth buffer on this new display
  614. # region (see the comment in setupRender2d, above).
  615. dr.setClearDepthActive(1)
  616. left, right, bottom, top = coords
  617. # Now make a new Camera node.
  618. cam2dNode = Camera('cam2d')
  619. lens = OrthographicLens()
  620. lens.setFilmSize(right - left, top - bottom)
  621. lens.setFilmOffset((right + left) * 0.5, (top + bottom) * 0.5)
  622. lens.setNearFar(-1000, 1000)
  623. cam2dNode.setLens(lens)
  624. cam2dNode.setScene(self.render2d)
  625. # self.camera2d is the analog of self.camera, although it's
  626. # not as clear how useful it is.
  627. if self.camera2d == None:
  628. self.camera2d = self.render2d.attachNewNode('camera2d')
  629. camera2d = self.camera2d.attachNewNode(cam2dNode)
  630. dr.setCamera(camera2d)
  631. return camera2d
  632. def setupDataGraph(self):
  633. """
  634. Creates the data graph and populates it with the basic input
  635. devices.
  636. """
  637. self.dataRoot = NodePath('dataRoot')
  638. # Cache the node so we do not ask for it every frame
  639. self.dataRootNode = self.dataRoot.node()
  640. self.dataUnused = NodePath('dataUnused')
  641. def setupMouse(self, win):
  642. """
  643. Creates the structures necessary to monitor the mouse input,
  644. using the indicated window. If the mouse has already been set
  645. up for a different window, this changes the mouse to reference
  646. the new window.
  647. """
  648. if self.mak != None:
  649. # The mouse has already been set up; reappropriate it.
  650. self.mak.node().setSource(win, 0)
  651. # Reset the currently-held modifier button list for good
  652. # measure.
  653. bt = self.buttonThrower.node()
  654. mb = ModifierButtons(bt.getModifierButtons())
  655. mb.allButtonsUp()
  656. bt.setModifierButtons(mb)
  657. return
  658. # The mouse has not yet been set up in this application;
  659. # create the mouse structures now.
  660. # We create both a MouseAndKeyboard object and a MouseWatcher object
  661. # for the window. The MouseAndKeyboard generates mouse events and
  662. # mouse button/keyboard events; the MouseWatcher passes them through
  663. # unchanged when the mouse is not over a 2-d button, and passes
  664. # nothing through when the mouse *is* over a 2-d button. Therefore,
  665. # objects that don't want to get events when the mouse is over a
  666. # button, like the driveInterface, should be parented to
  667. # mouseWatcher, while objects that want events in all cases, like the
  668. # chat interface, should be parented to mak.
  669. self.mak = self.dataRoot.attachNewNode(MouseAndKeyboard(win, 0, 'mak'))
  670. self.mouseWatcherNode = MouseWatcher('mouseWatcher')
  671. self.mouseWatcher = self.mak.attachNewNode(self.mouseWatcherNode)
  672. if self.recorder:
  673. # If we have a recorder, the mouseWatcher belongs under a
  674. # special MouseRecorder node, which may intercept the
  675. # mouse activity.
  676. mouseRecorder = MouseRecorder('mouse')
  677. self.recorder.addRecorder('mouse', mouseRecorder.upcastToRecorderBase())
  678. np = self.mak.attachNewNode(mouseRecorder)
  679. self.mouseWatcher.reparentTo(np)
  680. mb = self.mouseWatcherNode.getModifierButtons()
  681. mb.addButton(KeyboardButton.shift())
  682. mb.addButton(KeyboardButton.control())
  683. mb.addButton(KeyboardButton.alt())
  684. self.mouseWatcherNode.setModifierButtons(mb)
  685. # Now we have the main trackball & drive interfaces.
  686. # useTrackball() and useDrive() switch these in and out; only
  687. # one is in use at a given time.
  688. self.trackball = self.dataUnused.attachNewNode(Trackball('trackball'))
  689. self.drive = self.dataUnused.attachNewNode(DriveInterface('drive'))
  690. self.mouse2cam = self.dataUnused.attachNewNode(Transform2SG('mouse2cam'))
  691. self.mouse2cam.node().setNode(self.camera.node())
  692. # The default is trackball mode, which is more convenient for
  693. # ad-hoc development in Python using ShowBase. Applications
  694. # can explicitly call base.useDrive() if they prefer a drive
  695. # interface.
  696. self.mouseInterface = self.trackball
  697. self.useTrackball()
  698. # A ButtonThrower to generate events from the mouse and
  699. # keyboard buttons as they are pressed.
  700. self.buttonThrower = self.mouseWatcher.attachNewNode(ButtonThrower('buttons'))
  701. # Specialize the events based on whether the modifier keys are
  702. # being held down.
  703. mods = ModifierButtons()
  704. mods.addButton(KeyboardButton.shift())
  705. mods.addButton(KeyboardButton.control())
  706. mods.addButton(KeyboardButton.alt())
  707. self.buttonThrower.node().setModifierButtons(mods)
  708. # A special ButtonThrower to generate keyboard events and
  709. # include the time from the OS. This is separate only to
  710. # support legacy code that did not expect a time parameter; it
  711. # will eventually be folded into the normal ButtonThrower,
  712. # above.
  713. self.timeButtonThrower = self.mouseWatcher.attachNewNode(ButtonThrower('timeButtons'))
  714. self.timeButtonThrower.node().setPrefix('time-')
  715. self.timeButtonThrower.node().setTimeFlag(1)
  716. # Tell the gui system about our new mouse watcher.
  717. self.aspect2d.node().setMouseWatcher(self.mouseWatcherNode)
  718. self.mouseWatcherNode.addRegion(PGMouseWatcherBackground())
  719. def enableSoftwareMousePointer(self):
  720. """enableSoftwareMousePointer(self)
  721. Creates some geometry and parents it to render2d to show
  722. the currently-known mouse position. Useful if the mouse
  723. pointer is invisible for some reason.
  724. """
  725. mouseViz = render2d.attachNewNode('mouseViz')
  726. lilsmiley = loader.loadModel('lilsmiley')
  727. lilsmiley.reparentTo(mouseViz)
  728. aspectRatio = self.getAspectRatio()
  729. # Scale the smiley face to 32x32 pixels.
  730. lilsmiley.setScale(32.0 / self.win.getHeight() / aspectRatio,
  731. 1.0, 32.0 / self.win.getHeight())
  732. #self.mouseWatcherNode.setGeometry(mouseViz)
  733. def getAlt(self):
  734. return self.mouseWatcherNode.getModifierButtons().isDown(
  735. KeyboardButton.alt())
  736. def getShift(self):
  737. return self.mouseWatcherNode.getModifierButtons().isDown(
  738. KeyboardButton.shift())
  739. def getControl(self):
  740. return self.mouseWatcherNode.getModifierButtons().isDown(
  741. KeyboardButton.control())
  742. def addAngularIntegrator(self):
  743. """addAngularIntegrator(self)"""
  744. if (self.physicsMgrAngular == 0):
  745. self.physicsMgrAngular = 1
  746. integrator = AngularEulerIntegrator()
  747. self.physicsMgr.attachAngularIntegrator(integrator)
  748. def enableParticles(self):
  749. """enableParticles(self)"""
  750. self.particleMgrEnabled = 1
  751. self.physicsMgrEnabled = 1
  752. self.taskMgr.remove('manager-update')
  753. self.taskMgr.add(self.updateManagers, 'manager-update')
  754. def disableParticles(self):
  755. """enableParticles(self)"""
  756. self.particleMgrEnabled = 0
  757. self.physicsMgrEnabled = 0
  758. self.taskMgr.remove('manager-update')
  759. def toggleParticles(self):
  760. if self.particleMgrEnabled == 0:
  761. self.enableParticles()
  762. else:
  763. self.disableParticles()
  764. def isParticleMgrEnabled(self):
  765. return self.particleMgrEnabled
  766. def isPhysicsMgrEnabled(self):
  767. return self.physicsMgrEnabled
  768. def updateManagers(self, state):
  769. """updateManagers(self)"""
  770. dt = globalClock.getDt()
  771. if (self.particleMgrEnabled == 1):
  772. self.particleMgr.doParticles(dt)
  773. if (self.physicsMgrEnabled == 1):
  774. self.physicsMgr.doPhysics(dt)
  775. return Task.cont
  776. def createStats(self):
  777. # You must specify a pstats-host in your Config.prc
  778. # The default is localhost
  779. if self.wantStats:
  780. PStatClient.connect()
  781. def addSfxManager(self, extraSfxManager):
  782. # keep a list of sfx manager objects to apply settings to, since there may be others
  783. # in addition to the one we create here
  784. self.sfxManagerList.append(extraSfxManager)
  785. newSfxManagerIsValid = (extraSfxManager!=None) and extraSfxManager.isValid()
  786. self.sfxManagerIsValidList.append(newSfxManagerIsValid)
  787. if newSfxManagerIsValid:
  788. extraSfxManager.setActive(self.sfxActive)
  789. def createBaseAudioManagers(self):
  790. self.sfxPlayer = SfxPlayer.SfxPlayer()
  791. sfxManager = AudioManager.createAudioManager()
  792. self.addSfxManager(sfxManager)
  793. self.musicManager = AudioManager.createAudioManager()
  794. self.musicManagerIsValid=self.musicManager!=None \
  795. and self.musicManager.isValid()
  796. if self.musicManagerIsValid:
  797. # ensure only 1 midi song is playing at a time:
  798. self.musicManager.setConcurrentSoundLimit(1)
  799. self.musicManager.setActive(self.musicActive)
  800. # enableMusic/enableSoundEffects are meant to be called in response to a user request
  801. # so sfxActive/musicActive represent how things *should* be, regardless of App/OS/HW state
  802. def enableMusic(self, bEnableMusic):
  803. # dont setActive(1) if no audiofocus
  804. if self.AppHasAudioFocus and self.musicManagerIsValid:
  805. self.musicManager.setActive(bEnableMusic)
  806. self.musicActive = bEnableMusic
  807. if bEnableMusic:
  808. self.notify.debug("Enabling music")
  809. else:
  810. self.notify.debug("Disabling music")
  811. def SetAllSfxEnables(self, bEnabled):
  812. for i in range(len(self.sfxManagerList)):
  813. if (self.sfxManagerIsValidList[i]):
  814. self.sfxManagerList[i].setActive(bEnabled)
  815. def enableSoundEffects(self, bEnableSoundEffects):
  816. # dont setActive(1) if no audiofocus
  817. if self.AppHasAudioFocus or (bEnableSoundEffects==0):
  818. self.SetAllSfxEnables(bEnableSoundEffects)
  819. self.sfxActive=bEnableSoundEffects
  820. if bEnableSoundEffects:
  821. self.notify.debug("Enabling sound effects")
  822. else:
  823. self.notify.debug("Disabling sound effects")
  824. # enable/disableAllAudio allow a programmable global override-off for current audio settings.
  825. # they're meant to be called when app loses audio focus (switched out), so we can turn off sound
  826. # without affecting internal sfxActive/musicActive sound settings, so things
  827. # come back ok when the app is switched back to
  828. def disableAllAudio(self):
  829. self.AppHasAudioFocus = 0
  830. self.SetAllSfxEnables(0)
  831. if self.musicManagerIsValid:
  832. self.musicManager.setActive(0)
  833. self.notify.debug("Disabling audio")
  834. def enableAllAudio(self):
  835. self.AppHasAudioFocus = 1
  836. self.SetAllSfxEnables(self.sfxActive)
  837. if self.musicManagerIsValid:
  838. self.musicManager.setActive(self.musicActive)
  839. self.notify.debug("Enabling audio")
  840. # This function should only be in the loader but is here for
  841. # backwards compatibility. Please do not add code here, add
  842. # it to the loader.
  843. def loadSfx(self, name):
  844. return self.loader.loadSfx(name)
  845. # This function should only be in the loader but is here for
  846. # backwards compatibility. Please do not add code here, add
  847. # it to the loader.
  848. def loadMusic(self, name):
  849. return self.loader.loadMusic(name)
  850. def playSfx(self, sfx, looping = 0, interrupt = 1, volume = None, time = 0.0, node = None):
  851. # This goes through a special player for potential localization
  852. return self.sfxPlayer.playSfx(sfx, looping, interrupt, volume, time, node)
  853. def playMusic(self, music, looping = 0, interrupt = 1, volume = None, time = 0.0):
  854. if music:
  855. if volume != None:
  856. music.setVolume(volume)
  857. # if interrupt was set to 0, start over even if it's already playing
  858. if interrupt or (music.status() != AudioSound.PLAYING):
  859. music.setTime(time)
  860. music.setLoop(looping)
  861. music.play()
  862. def resetPrevTransform(self, state):
  863. # Clear out the previous velocity deltas now, after we have
  864. # rendered (the previous frame). We do this after the render,
  865. # so that we have a chance to draw a representation of spheres
  866. # along with their velocities. At the beginning of the frame
  867. # really means after the command prompt, which allows the user
  868. # to interactively query these deltas meaningfully.
  869. if self.cTrav:
  870. self.cTrav.resetPrevTransform(self.render)
  871. return Task.cont
  872. def dataloop(self, state):
  873. # traverse the data graph. This reads all the control
  874. # inputs (from the mouse and keyboard, for instance) and also
  875. # directly acts upon them (for instance, to move the avatar).
  876. self.dgTrav.traverse(self.dataRootNode)
  877. return Task.cont
  878. def ivalloop(self, state):
  879. # Execute all intervals in the global ivalMgr.
  880. ivalMgr.step()
  881. return Task.cont
  882. def shadowCollisionLoop(self, state):
  883. # run the collision traversal if we have a
  884. # CollisionTraverser set.
  885. if self.shadowTrav:
  886. self.shadowTrav.traverse(self.render)
  887. return Task.cont
  888. def collisionloop(self, state):
  889. # run the collision traversal if we have a
  890. # CollisionTraverser set.
  891. if self.cTrav:
  892. self.cTrav.traverse(self.render)
  893. if self.appTrav:
  894. self.appTrav.traverse(self.render)
  895. return Task.cont
  896. def igloop(self, state):
  897. if __debug__:
  898. # We render the watch variables for the onScreenDebug as soon
  899. # as we reasonably can before the renderFrame().
  900. onScreenDebug.render()
  901. if self.recorder:
  902. self.recorder.recordFrame()
  903. # Finally, render the frame.
  904. self.graphicsEngine.renderFrame()
  905. if self.clusterSyncFlag:
  906. self.graphicsEngine.syncFrame()
  907. if __debug__:
  908. # We clear the text buffer for the onScreenDebug as soon
  909. # as we reasonably can after the renderFrame().
  910. onScreenDebug.clear()
  911. if self.recorder:
  912. self.recorder.playFrame()
  913. if self.mainWinMinimized:
  914. # If the main window is minimized, slow down the app a bit
  915. # by sleeping here in igloop so we don't use all available
  916. # CPU needlessly.
  917. # Note: this isn't quite right if multiple windows are
  918. # open. We should base this on whether *all* windows are
  919. # minimized, not just the main window. But it will do for
  920. # now until someone complains.
  921. time.sleep(0.1)
  922. else:
  923. # magic word overrides config
  924. if self.mwClientSleep:
  925. time.sleep(self.mwClientSleep)
  926. elif self.clientSleep:
  927. time.sleep(self.clientSleep)
  928. # Lerp stuff needs this event, and it must be generated in
  929. # C++, not in Python.
  930. throwNewFrame()
  931. return Task.cont
  932. def restart(self):
  933. self.shutdown()
  934. # resetPrevTransform goes at the very beginning of the frame.
  935. self.taskMgr.add(self.resetPrevTransform, 'resetPrevTransform', priority = -51)
  936. # give the dataloop task a reasonably "early" priority,
  937. # so that it will get run before most tasks
  938. self.taskMgr.add(self.dataloop, 'dataloop', priority = -50)
  939. # spawn the ivalloop with a later priority, so that it will
  940. # run after most tasks, but before igloop.
  941. self.taskMgr.add(self.ivalloop, 'ivalloop', priority = 20)
  942. # make the collisionloop task run before igloop,
  943. # but leave enough room for the app to insert tasks
  944. # between collisionloop and igloop
  945. self.taskMgr.add(self.collisionloop, 'collisionloop', priority = 30)
  946. # do the shadowCollisionLoop after the collisionloop and
  947. # befor the igloop:
  948. self.taskMgr.add(self.shadowCollisionLoop, 'shadowCollisionLoop', priority = 34)
  949. # give the igloop task a reasonably "late" priority,
  950. # so that it will get run after most tasks
  951. self.taskMgr.add(self.igloop, 'igloop', priority = 50)
  952. self.eventMgr.restart()
  953. def shutdown(self):
  954. self.taskMgr.remove('igloop')
  955. self.taskMgr.remove('shadowCollisionLoop')
  956. self.taskMgr.remove('collisionloop')
  957. self.taskMgr.remove('dataloop')
  958. self.taskMgr.remove('resetPrevTransform')
  959. self.taskMgr.remove('ivalloop')
  960. self.eventMgr.shutdown()
  961. def getBackgroundColor(self, win = None):
  962. """
  963. Returns the current window background color. This assumes
  964. the window is set up to clear the color each frame (this is
  965. the normal setting).
  966. """
  967. if win == None:
  968. win = self.win
  969. return VBase4(win.getClearColor())
  970. def setBackgroundColor(self, r = None, g = None, b = None, win = None):
  971. """
  972. Sets the window background color to the indicated value.
  973. This assumes the window is set up to clear the color each
  974. frame (this is the normal setting).
  975. The color may be either a VBase3 or a VBase4, or a 3-component
  976. tuple, or the individual r, g, b parameters.
  977. """
  978. if g != None:
  979. color = VBase4(r, g, b, 1.0)
  980. else:
  981. arg = r
  982. color = VBase4(arg[0], arg[1], arg[2], 1.0)
  983. if win == None:
  984. win = self.win
  985. if win:
  986. win.setClearColor(color)
  987. def toggleBackface(self):
  988. if self.backfaceCullingEnabled:
  989. self.backfaceCullingOff()
  990. else:
  991. self.backfaceCullingOn()
  992. def backfaceCullingOn(self):
  993. if not self.backfaceCullingEnabled:
  994. self.render.setTwoSided(0)
  995. self.backfaceCullingEnabled = 1
  996. def backfaceCullingOff(self):
  997. if self.backfaceCullingEnabled:
  998. self.render.setTwoSided(1)
  999. self.backfaceCullingEnabled = 0
  1000. def toggleTexture(self):
  1001. if self.textureEnabled:
  1002. self.textureOff()
  1003. else:
  1004. self.textureOn()
  1005. def textureOn(self):
  1006. self.render.clearTexture()
  1007. self.textureEnabled = 1
  1008. def textureOff(self):
  1009. self.render.setTextureOff(100)
  1010. self.textureEnabled = 0
  1011. def toggleWireframe(self):
  1012. if self.wireframeEnabled:
  1013. self.wireframeOff()
  1014. else:
  1015. self.wireframeOn()
  1016. def wireframeOn(self):
  1017. self.render.setRenderModeWireframe(100);
  1018. self.render.setTwoSided(1);
  1019. self.wireframeEnabled = 1
  1020. def wireframeOff(self):
  1021. self.render.clearRenderMode()
  1022. render.setTwoSided(not self.backfaceCullingEnabled)
  1023. self.wireframeEnabled = 0
  1024. def disableMouse(self):
  1025. """
  1026. Temporarily disable the mouse control of the camera, either
  1027. via the drive interface or the trackball, whichever is
  1028. currently in use.
  1029. """
  1030. # We don't reparent the drive interface or the trackball;
  1031. # whichever one was there before will remain in the data graph
  1032. # and active. This way they won't lose button events while
  1033. # the mouse is disabled. However, we do move the mouse2cam
  1034. # object out of there, so we won't be updating the camera any
  1035. # more.
  1036. if self.mouse2cam:
  1037. self.mouse2cam.reparentTo(self.dataUnused)
  1038. def enableMouse(self):
  1039. """
  1040. Reverse the effect of a previous call to disableMouse().
  1041. useDrive() also implicitly enables the mouse.
  1042. """
  1043. if self.mouse2cam:
  1044. self.mouse2cam.reparentTo(self.mouseInterface)
  1045. def setMouseOnNode(self, newNode):
  1046. if self.mouse2cam:
  1047. self.mouse2cam.node().setNode(newNode)
  1048. def changeMouseInterface(self, changeTo):
  1049. """
  1050. Switch mouse action
  1051. """
  1052. # Get rid of the prior interface:
  1053. self.mouseInterface.reparentTo(self.dataUnused)
  1054. # Update the mouseInterface to point to the drive
  1055. self.mouseInterface = changeTo
  1056. self.mouseInterfaceNode = self.mouseInterface.node()
  1057. # Hookup the drive to the camera.
  1058. self.mouseInterface.reparentTo(self.mouseWatcher)
  1059. if self.mouse2cam:
  1060. self.mouse2cam.reparentTo(self.mouseInterface)
  1061. def useDrive(self):
  1062. """
  1063. Switch mouse action to drive mode
  1064. """
  1065. if self.drive:
  1066. self.changeMouseInterface(self.drive)
  1067. # Set the height to a good eyeheight
  1068. self.mouseInterfaceNode.reset()
  1069. self.mouseInterfaceNode.setZ(4.0)
  1070. def useTrackball(self):
  1071. """
  1072. Switch mouse action to trackball mode
  1073. """
  1074. if self.trackball:
  1075. self.changeMouseInterface(self.trackball)
  1076. def oobe(self):
  1077. """
  1078. Enable a special "out-of-body experience" mouse-interface
  1079. mode. This can be used when a "god" camera is needed; it
  1080. moves the camera node out from under its normal node and sets
  1081. the world up in trackball state. Button events are still sent
  1082. to the normal mouse action node (e.g. the DriveInterface), and
  1083. mouse events, if needed, may be sent to the normal node by
  1084. holding down the Control key.
  1085. This is different than useTrackball(), which simply changes
  1086. the existing mouse action to a trackball interface. In fact,
  1087. OOBE mode doesn't care whether useDrive() or useTrackball() is
  1088. in effect; it just temporarily layers a new trackball
  1089. interface on top of whatever the basic interface is. You can
  1090. even switch between useDrive() and useTrackball() while OOBE
  1091. mode is in effect.
  1092. This is a toggle; the second time this function is called, it
  1093. disables the mode.
  1094. """
  1095. # If oobeMode was never set, set it to false and create the
  1096. # structures we need to implement OOBE.
  1097. try:
  1098. self.oobeMode
  1099. except:
  1100. self.oobeMode = 0
  1101. self.oobeCamera = self.hidden.attachNewNode('oobeCamera')
  1102. self.oobeCameraTrackball = self.oobeCamera.attachNewNode('oobeCameraTrackball')
  1103. self.oobeLens = PerspectiveLens()
  1104. self.oobeLens.setAspectRatio(self.getAspectRatio())
  1105. self.oobeLens.setNearFar(0.1, 10000.0)
  1106. self.oobeLens.setFov(52.0)
  1107. self.oobeTrackball = self.dataUnused.attachNewNode(Trackball('oobeTrackball'), 1)
  1108. self.oobe2cam = self.oobeTrackball.attachNewNode(Transform2SG('oobe2cam'))
  1109. self.oobe2cam.node().setNode(self.oobeCameraTrackball.node())
  1110. self.oobeVis = loader.loadModelOnce('models/misc/camera')
  1111. if self.oobeVis:
  1112. self.oobeVis.node().setFinal(1)
  1113. self.oobeCullFrustum = None
  1114. self.oobeCullFrustumVis = None
  1115. if self.oobeMode:
  1116. # Disable OOBE mode.
  1117. if self.oobeCullFrustum != None:
  1118. # First, disable OOBE cull mode.
  1119. self.oobeCull()
  1120. if self.oobeVis:
  1121. self.oobeVis.reparentTo(self.hidden)
  1122. # Restore the mouse interface node.
  1123. #self.mouseInterface.reparentTo(self.mouseWatcher)
  1124. self.oobeTrackball.reparentTo(self.dataUnused)
  1125. self.cam.reparentTo(self.camera)
  1126. self.camNode.setLens(self.camLens)
  1127. self.oobeCamera.reparentTo(self.hidden)
  1128. self.oobeMode = 0
  1129. else:
  1130. # Make oobeCamera be a sibling of wherever camera is now.
  1131. cameraParent = self.camera.getParent()
  1132. self.oobeCamera.reparentTo(cameraParent)
  1133. self.oobeCamera.clearMat()
  1134. # Move aside the current mouse interface node and put the
  1135. # oobeTrackball in its place.
  1136. #self.mouseInterface.reparentTo(self.dataUnused)
  1137. self.oobeTrackball.reparentTo(self.mouseWatcher)
  1138. # Set our initial OOB position to be just behind the camera.
  1139. mat = Mat4.translateMat(0, -10, 3) * self.camera.getMat(cameraParent)
  1140. mat.invertInPlace()
  1141. self.oobeTrackball.node().setMat(mat)
  1142. self.cam.reparentTo(self.oobeCameraTrackball)
  1143. self.camNode.setLens(self.oobeLens)
  1144. if self.oobeVis:
  1145. self.oobeVis.reparentTo(self.camera)
  1146. self.oobeMode = 1
  1147. def oobeCull(self):
  1148. """
  1149. While in OOBE mode (see above), cull the viewing frustum as if
  1150. it were still attached to our original camera. This allows us
  1151. to visualize the effectiveness of our bounding volumes.
  1152. """
  1153. # First, make sure OOBE mode is enabled.
  1154. try:
  1155. if not self.oobeMode:
  1156. self.oobe()
  1157. except:
  1158. self.oobe()
  1159. if self.oobeCullFrustum == None:
  1160. # Enable OOBE culling.
  1161. pnode = LensNode('oobeCull')
  1162. pnode.setLens(self.camLens)
  1163. self.oobeCullFrustum = self.camera.attachNewNode(pnode)
  1164. # Create a visible representation of the frustum.
  1165. geom = self.camLens.makeGeometry()
  1166. if geom != None:
  1167. gn = GeomNode('frustum')
  1168. gn.addGeom(geom)
  1169. self.oobeCullFrustumVis = self.oobeVis.attachNewNode(gn)
  1170. # Assign each DisplayRegion shared by the camera to use
  1171. # this cull frustum.
  1172. # Support for this is currently absent from Panda.
  1173. ## numDisplayRegions = self.camNode.getNumDisplayRegions()
  1174. ## for d in range(0, numDisplayRegions):
  1175. ## dr = self.camNode.getDisplayRegion(d)
  1176. ## dr.setCullFrustum(pnode)
  1177. else:
  1178. # Disable OOBE culling.
  1179. # Assign each DisplayRegion shared by the camera to use
  1180. # the default cull frustum, the camera itself.
  1181. ## numDisplayRegions = self.camNode.getNumDisplayRegions()
  1182. ## for d in range(0, numDisplayRegions):
  1183. ## dr = self.camNode.getDisplayRegion(d)
  1184. ## dr.setCullFrustum(self.camNode)
  1185. self.oobeCullFrustum.removeNode()
  1186. self.oobeCullFrustum = None
  1187. if self.oobeCullFrustumVis != None:
  1188. self.oobeCullFrustumVis.removeNode()
  1189. self.oobeCullFrustumVis = None
  1190. def showCameraFrustum(self):
  1191. # Create a visible representation of the frustum.
  1192. self.removeCameraFrustum()
  1193. geom = self.camLens.makeGeometry()
  1194. if geom != None:
  1195. gn = GeomNode('frustum')
  1196. gn.addGeom(geom)
  1197. self.camFrustumVis = self.camera.attachNewNode(gn)
  1198. def removeCameraFrustum(self):
  1199. if self.camFrustumVis:
  1200. self.camFrustumVis.removeNode()
  1201. def screenshot(self, namePrefix='screenshot'):
  1202. filename = self.win.saveScreenshotDefault(namePrefix)
  1203. if filename.empty():
  1204. # The screenshot attempt failed for some reason.
  1205. return 0
  1206. # Announce to anybody that a screenshot has been taken
  1207. messenger.send('screenshot', [filename])
  1208. return 1
  1209. def movie(self, namePrefix = 'movie', duration = 1.0, fps = 30,
  1210. format = 'rgb', sd = 4):
  1211. """
  1212. movie(namePrefix = 'movie', duration=1.0, fps=30, format='rgb', sd=4)
  1213. Spawn a task to capture a movie using the takeSnapshot function.
  1214. - namePrefix will be used to form output file names (can include
  1215. path information (e.g. 'I:/beta/frames/myMovie')
  1216. - duration is the length of the movie in seconds
  1217. - fps is the frame rate of the resulting movie
  1218. - format specifies output file format (e.g. rgb, bmp)
  1219. - sd specifies number of significant digits for frame count in the
  1220. output file name (e.g. if sd = 4, movie_0001.rgb)
  1221. """
  1222. globalClock.setMode(ClockObject.MNonRealTime)
  1223. globalClock.setDt(1.0/float(fps))
  1224. t = taskMgr.add(self._movieTask, namePrefix + '_task')
  1225. t.endT = globalClock.getFrameTime() + duration
  1226. t.frameIndex = 1
  1227. t.outputString = namePrefix + '_%0' + `sd` + 'd.' + format
  1228. t.uponDeath = lambda state: globalClock.setMode(ClockObject.MNormal)
  1229. def _movieTask(self, state):
  1230. currT = globalClock.getFrameTime()
  1231. if currT >= state.endT:
  1232. return Task.done
  1233. else:
  1234. frameName = state.outputString % state.frameIndex
  1235. self.notify.info("Capturing frame: " + frameName)
  1236. takeSnapshot(self.win, frameName )
  1237. state.frameIndex += 1
  1238. return Task.cont
  1239. def __windowEvent(self, win):
  1240. if win == self.win:
  1241. properties = win.getProperties()
  1242. self.notify.info("Got window event: %s" % (repr(properties)))
  1243. if not properties.getOpen():
  1244. # If the user closes the main window, we should exit.
  1245. self.notify.info("User closed main window.")
  1246. self.userExit()
  1247. if properties.getMinimized() and not self.mainWinMinimized:
  1248. # If the main window is minimized, throw an event to
  1249. # stop the music.
  1250. self.mainWinMinimized = 1
  1251. messenger.send('PandaPaused')
  1252. elif not properties.getMinimized() and self.mainWinMinimized:
  1253. # If the main window is restored, throw an event to
  1254. # restart the music.
  1255. self.mainWinMinimized = 0
  1256. messenger.send('PandaRestarted')
  1257. def userExit(self):
  1258. # The user has requested we exit the program. Deal with this.
  1259. if self.exitFunc:
  1260. self.exitFunc()
  1261. self.notify.info("Exiting ShowBase.")
  1262. sys.exit()
  1263. def startTk(self, fWantTk = 1):
  1264. self.wantTk = fWantTk
  1265. if self.wantTk:
  1266. import TkGlobal
  1267. taskMgr.remove('tkloop')
  1268. TkGlobal.spawnTkLoop()
  1269. def startDirect(self, fWantDirect = 1, fWantTk = 1):
  1270. self.startTk(fWantTk)
  1271. self.wantDirect = fWantDirect
  1272. if self.wantDirect:
  1273. from direct.directtools import DirectSession
  1274. direct.enable()
  1275. else:
  1276. __builtins__["direct"] = self.direct = None
  1277. def run(self):
  1278. self.taskMgr.run()