ShowBase.py 69 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861
  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 BulletinBoardGlobal import *
  11. from direct.task.TaskManagerGlobal import *
  12. from EventManagerGlobal import *
  13. from PythonUtil import *
  14. from direct.particles.ParticleManagerGlobal import *
  15. from PhysicsManagerGlobal import *
  16. #from direct.interval.IntervalManager import ivalMgr
  17. from direct.interval import IntervalManager
  18. from InputStateGlobal import inputState
  19. from direct.showbase.BufferViewer import BufferViewer
  20. from direct.task import Task
  21. import EventManager
  22. import math
  23. import sys
  24. import Loader
  25. import time
  26. from direct.fsm import ClassicFSM
  27. from direct.fsm import State
  28. import DirectObject
  29. import SfxPlayer
  30. if __debug__:
  31. from direct.directutil import DeltaProfiler
  32. import OnScreenDebug
  33. __builtins__["FADE_SORT_INDEX"] = 1000
  34. __builtins__["NO_FADE_SORT_INDEX"] = 2000
  35. # Now ShowBase is a DirectObject. We need this so ShowBase can hang
  36. # hooks on messages, particularly on window-event. This doesn't
  37. # *seem* to cause anyone any problems.
  38. class ShowBase(DirectObject.DirectObject):
  39. notify = directNotify.newCategory("ShowBase")
  40. def __init__(self):
  41. # Get the dconfig object
  42. #self.config = ConfigConfigureGetConfigConfigShowbase
  43. self.config = config
  44. # Setup wantVerifyPdb as soon as reasonable:
  45. Verify.wantVerifyPdb = self.config.GetBool('want-verify-pdb', 0)
  46. self.printEnvDebugInfo()
  47. vfs = VirtualFileSystem.getGlobalPtr()
  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.wantRender2dp = self.config.GetBool('want-render2dp', 1)
  54. self.screenshotExtension = self.config.GetString('screenshot-extension', 'jpg')
  55. self.musicManager = None
  56. self.musicManagerIsValid = None
  57. self.sfxManagerList = []
  58. self.sfxManagerIsValidList = []
  59. self.wantStats = self.config.GetBool('want-pstats', 0)
  60. # Fill this in with a function to invoke when the user "exits"
  61. # the program by closing the main window.
  62. self.exitFunc = None
  63. Task.TaskManager.taskTimerVerbose = self.config.GetBool('task-timer-verbose', 0)
  64. Task.TaskManager.extendedExceptions = self.config.GetBool('extended-exceptions', 0)
  65. Task.TaskManager.pStatsTasks = self.config.GetBool('pstats-tasks', 0)
  66. # Set up the TaskManager to reset the PStats clock back
  67. # whenever we resume from a pause. This callback function is
  68. # a little hacky, but we can't call it directly from within
  69. # the TaskManager because he doesn't know about PStats (and
  70. # has to run before libpanda is even loaded).
  71. taskMgr.resumeFunc = PStatClient.resumeAfterPause
  72. fsmRedefine = self.config.GetBool('fsm-redefine', 0)
  73. State.FsmRedefine = fsmRedefine
  74. # If the aspect ratio is 0 or None, it means to infer the
  75. # aspect ratio from the window size.
  76. self.aspectRatio = ConfigVariableDouble('aspect-ratio', 0).getValue()
  77. self.windowType = self.config.GetString('window-type', 'onscreen')
  78. self.requireWindow = self.config.GetBool('require-window', 1)
  79. # base.win is the main, or only window; base.winList is a list of
  80. # *all* windows. Similarly with base.camList.
  81. self.win = None
  82. self.frameRateMeter = None
  83. self.winList = []
  84. self.winControls = []
  85. self.mainWinMinimized = 0
  86. self.pipe = None
  87. self.pipeList = []
  88. self.mouse2cam = None
  89. self.buttonThrowers = None
  90. self.mouseWatcher = None
  91. self.mouseWatcherNode = None
  92. self.pointerWatcherNodes = None
  93. self.mouseInterface = None
  94. self.drive = None
  95. self.trackball = None
  96. self.cam = None
  97. self.cam2d = None
  98. self.cam2dp = None
  99. self.camera = None
  100. self.camera2d = None
  101. self.camera2dp = None
  102. self.camList = []
  103. self.camNode = None
  104. self.camLens = None
  105. self.camFrustumVis = None
  106. # This is used for syncing multiple PCs in a distributed cluster
  107. try:
  108. # Has the cluster sync variable been set externally?
  109. self.clusterSyncFlag = clusterSyncFlag
  110. except NameError:
  111. # Has the clusterSyncFlag been set via a config variable
  112. self.clusterSyncFlag = self.config.GetBool('cluster-sync', 0)
  113. self.hidden = NodePath('hidden')
  114. # We need a graphics engine to manage the actual rendering.
  115. self.graphicsEngine = GraphicsEngine()
  116. self.setupRender()
  117. self.setupRender2d()
  118. self.setupDataGraph()
  119. if self.wantRender2dp:
  120. self.setupRender2dp()
  121. # This is a placeholder for a CollisionTraverser. If someone
  122. # stores a CollisionTraverser pointer here, we'll traverse it
  123. # in the collisionLoop task.
  124. self.shadowTrav = 0
  125. # in the collisionLoop task.
  126. self.cTrav = 0
  127. # Ditto for an AppTraverser.
  128. self.appTrav = 0
  129. # This is the DataGraph traverser, which we might as well
  130. # create now.
  131. self.dgTrav = DataGraphTraverser()
  132. # Maybe create a RecorderController to record and/or play back
  133. # the user session.
  134. self.recorder = None
  135. playbackSession = self.config.GetString('playback-session', '')
  136. recordSession = self.config.GetString('record-session', '')
  137. if playbackSession:
  138. self.recorder = RecorderController()
  139. self.recorder.beginPlayback(Filename.fromOsSpecific(playbackSession))
  140. elif recordSession:
  141. self.recorder = RecorderController()
  142. self.recorder.beginRecord(Filename.fromOsSpecific(recordSession))
  143. if self.recorder:
  144. # If we're either playing back or recording, pass the
  145. # random seed into the system so each session will have
  146. # the same random seed.
  147. import random, whrandom
  148. seed = self.recorder.getRandomSeed()
  149. random.seed(seed)
  150. whrandom.seed(seed & 0xff, (seed >> 8) & 0xff, (seed >> 16) & 0xff)
  151. # Now that we've set up the window structures, assign an exitfunc.
  152. self.oldexitfunc = getattr(sys, 'exitfunc', None)
  153. sys.exitfunc = self.exitfunc
  154. # Open the default rendering window.
  155. if self.windowType != 'none':
  156. self.openDefaultWindow()
  157. self.loader = Loader.Loader(self)
  158. self.eventMgr = eventMgr
  159. self.messenger = messenger
  160. self.bboard = bulletinBoard
  161. self.taskMgr = taskMgr
  162. # Particle manager
  163. self.particleMgr = particleMgr
  164. self.particleMgr.setFrameStepping(1)
  165. self.particleMgrEnabled = 0
  166. # Physics manager
  167. self.physicsMgr = physicsMgr
  168. integrator = LinearEulerIntegrator()
  169. self.physicsMgr.attachLinearIntegrator(integrator)
  170. self.physicsMgrEnabled = 0
  171. self.physicsMgrAngular = 0
  172. self.createBaseAudioManagers()
  173. self.createStats()
  174. self.AppHasAudioFocus = 1
  175. __builtins__["base"] = self
  176. __builtins__["render2d"] = self.render2d
  177. __builtins__["aspect2d"] = self.aspect2d
  178. __builtins__["render"] = self.render
  179. __builtins__["hidden"] = self.hidden
  180. __builtins__["camera"] = self.camera
  181. __builtins__["loader"] = self.loader
  182. __builtins__["taskMgr"] = self.taskMgr
  183. __builtins__["eventMgr"] = self.eventMgr
  184. __builtins__["messenger"] = self.messenger
  185. __builtins__["bboard"] = self.bboard
  186. # Config needs to be defined before ShowBase is constructed
  187. #__builtins__["config"] = self.config
  188. __builtins__["run"] = self.run
  189. __builtins__["ostream"] = Notify.out()
  190. __builtins__["directNotify"] = directNotify
  191. __builtins__["globalClock"] = ClockObject.getGlobalClock()
  192. __builtins__["vfs"] = vfs
  193. __builtins__["cpMgr"] = ConfigPageManager.getGlobalPtr()
  194. __builtins__["cvMgr"] = ConfigVariableManager.getGlobalPtr()
  195. __builtins__["pandaSystem"] = PandaSystem.getGlobalPtr()
  196. __builtins__["__dev__"] = base.config.GetBool('want-dev', 0)
  197. __builtins__["wantUberdog"] = base.config.GetBool('want-uberdog', 1)
  198. if __debug__:
  199. __builtins__["deltaProfiler"] = DeltaProfiler.DeltaProfiler("ShowBase")
  200. __builtins__["onScreenDebug"] = OnScreenDebug.OnScreenDebug()
  201. if self.wantRender2dp:
  202. __builtins__["render2dp"] = self.render2dp
  203. __builtins__["aspect2dp"] = self.aspect2dp
  204. ShowBase.notify.info('__dev__ == %s' % __dev__)
  205. # Now hang a hook on the window-event from Panda. This allows
  206. # us to detect when the user resizes, minimizes, or closes the
  207. # main window.
  208. self.accept('window-event', self.__windowEvent)
  209. # Transition effects (fade, iris, etc)
  210. import Transitions
  211. self.transitions = Transitions.Transitions(self.loader)
  212. # Setup the window controls - handy for multiwindow applications
  213. self.setupWindowControls()
  214. # Client sleep
  215. sleepTime = self.config.GetFloat('client-sleep', 0.0)
  216. self.clientSleep = 0.0
  217. self.setSleep(sleepTime)
  218. # Offscreen buffer viewing utility.
  219. self.bufferViewer = BufferViewer()
  220. # Start Tk and DIRECT if specified by Config.prc
  221. fTk = self.config.GetBool('want-tk', 0)
  222. # Start DIRECT if specified in Config.prc or in cluster mode
  223. fDirect = (self.config.GetBool('want-directtools', 0) or
  224. (base.config.GetString("cluster-mode", '') != ''))
  225. # Set fWantTk to 0 to avoid starting Tk with this call
  226. self.startDirect(fWantDirect = fDirect, fWantTk = fTk)
  227. # Start IGLOOP
  228. self.restart()
  229. def printEnvDebugInfo(self):
  230. """
  231. Print some information about the environment that we are running
  232. in. Stuff like the model paths and other paths. Feel free to
  233. add stuff to this.
  234. """
  235. if self.config.GetBool('want-env-debug-info', 0):
  236. print "\n\nEnvironment Debug Info {"
  237. print "* model path:"
  238. print getModelPath()
  239. #print "* dna path:"
  240. #print getDnaPath()
  241. print "* texture path:"
  242. print getTexturePath()
  243. print "* sound path:"
  244. print 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. if self.musicManager:
  255. self.musicManager.shutdown()
  256. del self.win
  257. del self.winList
  258. del self.pipe
  259. del self.musicManager
  260. del self.sfxManagerList
  261. try:
  262. direct.panel.destroy()
  263. except StandardError:
  264. pass
  265. if self.oldexitfunc:
  266. self.oldexitfunc()
  267. def makeDefaultPipe(self):
  268. """
  269. Creates the default GraphicsPipe, which will be used to make
  270. windows unless otherwise specified.
  271. """
  272. assert self.pipe == None
  273. selection = GraphicsPipeSelection.getGlobalPtr()
  274. selection.printPipeTypes()
  275. self.pipe = selection.makeDefaultPipe()
  276. if not self.pipe:
  277. self.notify.error(
  278. "No graphics pipe is available!\n"
  279. "Your Config.prc file must name at least one valid panda display\n"
  280. "library via load-display or aux-display.")
  281. self.notify.info("Default graphics pipe is %s (%s)." % (
  282. self.pipe.getInterfaceName(), self.pipe.getType().getName()))
  283. self.pipeList.append(self.pipe)
  284. def makeAllPipes(self):
  285. """
  286. Creates all GraphicsPipes that the system knows about and fill up
  287. self.pipeList with them.
  288. """
  289. shouldPrintPipes = 0
  290. selection = GraphicsPipeSelection.getGlobalPtr()
  291. selection.loadAuxModules()
  292. # First, we should make sure the default pipe exists.
  293. if self.pipe == None:
  294. self.makeDefaultPipe()
  295. # Now go through the list of known pipes, and make each one if
  296. # we don't have one already.
  297. numPipeTypes = selection.getNumPipeTypes()
  298. for i in range(numPipeTypes):
  299. pipeType = selection.getPipeType(i)
  300. # Do we already have a pipe of this type on the list?
  301. # This operation is n-squared, but presumably there won't
  302. # be more than a handful of pipe types, so who cares.
  303. already = 0
  304. for pipe in self.pipeList:
  305. if pipe.getType() == pipeType:
  306. already = 1
  307. if not already:
  308. pipe = selection.makePipe(pipeType)
  309. if pipe:
  310. self.notify.info("Got aux graphics pipe %s (%s)." % (
  311. pipe.getInterfaceName(), pipe.getType().getName()))
  312. self.pipeList.append(pipe)
  313. else:
  314. self.notify.info("Could not make graphics pipe %s." % (
  315. pipeType.getName()))
  316. def openWindow(self, props = None, pipe = None, gsg = None,
  317. type = None, name = None, size = None, aspectRatio = None,
  318. makeCamera = 1, scene = None, stereo = None):
  319. """
  320. Creates a window and adds it to the list of windows that are
  321. to be updated every frame.
  322. """
  323. if pipe == None:
  324. pipe = self.pipe
  325. if pipe == None:
  326. self.makeDefaultPipe()
  327. pipe = self.pipe
  328. if pipe == None:
  329. # We couldn't get a pipe.
  330. return None
  331. if gsg == None:
  332. # If we weren't given a gsg, create a new one just for
  333. # this window.
  334. gsg = self.graphicsEngine.makeGsg(pipe)
  335. if gsg == None:
  336. # Couldn't make a gsg.
  337. return None
  338. if type == None:
  339. type = self.windowType
  340. if props == None:
  341. props = WindowProperties.getDefault()
  342. if size != None:
  343. # If we were given an explicit size, use it; otherwise,
  344. # the size from the properties is used.
  345. props = WindowProperties(props)
  346. props.setSize(size[0], size[1])
  347. if name == None:
  348. name = 'window%s' % (self.nextWindowIndex)
  349. self.nextWindowIndex += 1
  350. win = None
  351. if type == 'onscreen':
  352. win = self.graphicsEngine.makeWindow(gsg, name, 0)
  353. elif type == 'offscreen':
  354. win = self.graphicsEngine.makeBuffer(
  355. gsg, name, 0, props.getXSize(), props.getYSize())
  356. if win == None:
  357. # Couldn't create a window!
  358. return None
  359. if hasattr(win, "requestProperties"):
  360. win.requestProperties(props)
  361. if self.win == None:
  362. self.win = win
  363. self.winList.append(win)
  364. # Set up a 3-d camera for the window by default.
  365. if makeCamera:
  366. self.makeCamera(win, scene = scene, aspectRatio = aspectRatio,
  367. stereo = stereo)
  368. return win
  369. def closeWindow(self, win):
  370. """
  371. Closes the indicated window and removes it from the list of
  372. windows. If it is the main window, clears the main window
  373. pointer to None.
  374. """
  375. # First, remove all of the cameras associated with display
  376. # regions on the window.
  377. numRegions = win.getNumDisplayRegions()
  378. for i in range(numRegions):
  379. dr = win.getDisplayRegion(i)
  380. cam = NodePath(dr.getCamera())
  381. dr.setCamera(NodePath())
  382. if not cam.isEmpty() and cam.node().getNumDisplayRegions() == 0:
  383. # If the camera is used by no other DisplayRegions,
  384. # remove it.
  385. if self.camList.count(cam) != 0:
  386. self.camList.remove(cam)
  387. # Don't throw away self.camera; we want to
  388. # preserve it for reopening the window.
  389. if cam == self.cam:
  390. self.cam = None
  391. if cam == self.cam2d:
  392. self.cam2d = None
  393. if cam == self.cam2dp:
  394. self.cam2dp = None
  395. cam.removeNode()
  396. # Now we can actually close the window.
  397. self.graphicsEngine.removeWindow(win)
  398. self.winList.remove(win)
  399. if win == self.win:
  400. self.win = None
  401. if self.frameRateMeter:
  402. self.frameRateMeter.clearWindow()
  403. self.frameRateMeter = None
  404. def openDefaultWindow(self):
  405. # Creates the main window for the first time, without being
  406. # too particular about the kind of graphics API that is
  407. # chosen. The suggested window type from the load-display
  408. # config variable is tried first; if that fails, the first
  409. # window type that can be successfully opened at all is
  410. # accepted. Returns true on success, false otherwise.
  411. #
  412. # This is intended to be called only once, at application
  413. # startup. It is normally called automatically unless
  414. # window-type is configured to 'none'.
  415. self.openMainWindow()
  416. # Give the window a chance to truly open.
  417. self.graphicsEngine.openWindows()
  418. if self.win != None and not self.isMainWindowOpen():
  419. self.notify.info("Window did not open, removing.")
  420. self.closeWindow(self.win)
  421. if self.win == None:
  422. # Try a little harder if the window wouldn't open.
  423. self.makeAllPipes()
  424. while self.win == None and len(self.pipeList) > 1:
  425. self.pipeList.remove(self.pipe)
  426. self.pipe = self.pipeList[0]
  427. self.openMainWindow()
  428. self.graphicsEngine.openWindows()
  429. if self.win != None and not self.isMainWindowOpen():
  430. self.notify.info("Window did not open, removing.")
  431. self.closeWindow(self.win)
  432. if self.win == None:
  433. self.notify.warning("Unable to open '%s' window." % (
  434. self.windowType))
  435. if self.requireWindow:
  436. # Unless require-window is set to false, it is an
  437. # error not to open a window.
  438. raise StandardError, 'Could not open window.'
  439. return self.win != None
  440. def isMainWindowOpen(self):
  441. if self.win != None:
  442. return self.win.isValid()
  443. return 0
  444. def openMainWindow(self, *args, **kw):
  445. """
  446. Creates the initial, main window for the application, and sets
  447. up the mouse and render2d structures appropriately for it. If
  448. this method is called a second time, it will close the
  449. previous main window and open a new one, preserving the lens
  450. properties in base.camLens.
  451. The return value is true on success, or false on failure (in
  452. which case base.win may be either None, or the previous,
  453. closed window).
  454. """
  455. success = 1
  456. oldWin = self.win
  457. oldLens = self.camLens
  458. oldClearColorActive = None
  459. if self.win != None:
  460. # Close the previous window.
  461. oldClearColorActive = self.win.getClearColorActive()
  462. oldClearColor = VBase4(self.win.getClearColor())
  463. oldClearDepthActive = self.win.getClearDepthActive()
  464. oldClearDepth = self.win.getClearDepth()
  465. self.closeWindow(self.win)
  466. # Open a new window.
  467. self.openWindow(*args, **kw)
  468. if self.win == None:
  469. self.win = oldWin
  470. self.winList.append(oldWin)
  471. success = 0
  472. if self.win != None:
  473. if isinstance(self.win, GraphicsWindow):
  474. self.setupMouse(self.win)
  475. self.makeCamera2d(self.win)
  476. self.makeCamera2dp(self.win)
  477. if oldLens != None:
  478. # Restore the previous lens properties.
  479. self.camNode.setLens(oldLens)
  480. self.camLens = oldLens
  481. if oldClearColorActive != None:
  482. # Restore the previous clear properties.
  483. self.win.setClearColorActive(oldClearColorActive)
  484. self.win.setClearColor(oldClearColor)
  485. self.win.setClearDepthActive(oldClearDepthActive)
  486. self.win.setClearDepth(oldClearDepth)
  487. self.setFrameRateMeter(self.config.GetBool(
  488. 'show-frame-rate-meter', 0))
  489. return success
  490. def setSleep(self, amount):
  491. """
  492. Sets up a task that calls python 'sleep' every frame. This is a simple
  493. way to reduce the CPU usage (and frame rate) of a panda program.
  494. """
  495. if (self.clientSleep == amount):
  496. return
  497. self.clientSleep = amount
  498. if (amount == 0.0):
  499. self.taskMgr.remove('clientSleep')
  500. else:
  501. # Spawn it after igloop (at the end of each frame)
  502. self.taskMgr.remove('clientSleep')
  503. self.taskMgr.add(self.sleepCycleTask, 'clientSleep', priority = 55)
  504. def sleepCycleTask(self, task):
  505. time.sleep(self.clientSleep)
  506. return Task.cont
  507. def setFrameRateMeter(self, flag):
  508. """
  509. Turns on or off (according to flag) a standard frame rate
  510. meter in the upper-right corner of the main window.
  511. """
  512. if flag:
  513. if not self.frameRateMeter:
  514. self.frameRateMeter = FrameRateMeter('frameRateMeter')
  515. self.frameRateMeter.setupWindow(self.win)
  516. else:
  517. if self.frameRateMeter:
  518. self.frameRateMeter.clearWindow()
  519. self.frameRateMeter = None
  520. def setupWindowControls(self):
  521. if not self.winControls:
  522. winCtrl = WindowControls(
  523. self.win, mouseWatcher=self.mouseWatcher,
  524. cam=self.camera, cam2d=self.camera2d,
  525. mouseKeyboard = self.dataRoot.find("**/*"))
  526. self.winControls.append(winCtrl)
  527. def setupRender(self):
  528. """
  529. Creates the render scene graph, the primary scene graph for
  530. rendering 3-d geometry.
  531. """
  532. self.render = NodePath('render')
  533. self.render.setAttrib(RescaleNormalAttrib.makeDefault())
  534. self.render.setTwoSided(0)
  535. self.backfaceCullingEnabled = 1
  536. self.textureEnabled = 1
  537. self.wireframeEnabled = 0
  538. def setupRender2d(self):
  539. """
  540. Creates the render2d scene graph, the primary scene graph for
  541. 2-d objects and gui elements that are superimposed over the
  542. 3-d geometry in the window.
  543. """
  544. self.render2d = NodePath('render2d')
  545. # Set up some overrides to turn off certain properties which
  546. # we probably won't need for 2-d objects.
  547. # It's probably important to turn off the depth test, since
  548. # many 2-d objects will be drawn over each other without
  549. # regard to depth position.
  550. # We used to avoid clearing the depth buffer before drawing
  551. # render2d, but nowadays we clear it anyway, since we
  552. # occasionally want to put 3-d geometry under render2d, and
  553. # it's simplest (and seems to be easier on graphics drivers)
  554. # if the 2-d scene has been cleared first.
  555. self.render2d.setDepthTest(0)
  556. self.render2d.setDepthWrite(0)
  557. self.render2d.setMaterialOff(1)
  558. self.render2d.setTwoSided(1)
  559. # The normal 2-d DisplayRegion has an aspect ratio that
  560. # matches the window, but its coordinate system is square.
  561. # This means anything we parent to render2d gets stretched.
  562. # For things where that makes a difference, we set up
  563. # aspect2d, which scales things back to the right aspect
  564. # ratio.
  565. aspectRatio = self.getAspectRatio()
  566. self.aspect2d = self.render2d.attachNewNode(PGTop("aspect2d"))
  567. self.aspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0)
  568. # It's important to know the bounds of the aspect2d screen.
  569. self.a2dTop = 1.0
  570. self.a2dBottom = -1.0
  571. self.a2dLeft = -aspectRatio
  572. self.a2dRight = aspectRatio
  573. def setupRender2dp(self):
  574. """
  575. Creates a render2d scene graph, the secondary scene graph for
  576. 2-d objects and gui elements that are superimposed over the
  577. 2-d and 3-d geometry in the window.
  578. """
  579. self.render2dp = NodePath('render2dp')
  580. # Set up some overrides to turn off certain properties which
  581. # we probably won't need for 2-d objects.
  582. # It's probably important to turn off the depth test, since
  583. # many 2-d objects will be drawn over each other without
  584. # regard to depth position.
  585. dt = DepthTestAttrib.make(DepthTestAttrib.MNone)
  586. dw = DepthWriteAttrib.make(DepthWriteAttrib.MOff)
  587. self.render2dp.setDepthTest(0)
  588. self.render2dp.setDepthWrite(0)
  589. self.render2dp.setMaterialOff(1)
  590. self.render2dp.setTwoSided(1)
  591. # The normal 2-d DisplayRegion has an aspect ratio that
  592. # matches the window, but its coordinate system is square.
  593. # This means anything we parent to render2d gets stretched.
  594. # For things where that makes a difference, we set up
  595. # aspect2d, which scales things back to the right aspect
  596. # ratio.
  597. aspectRatio = self.getAspectRatio()
  598. self.aspect2dp = self.render2dp.attachNewNode(PGTop("aspect2dp"))
  599. self.aspect2dp.node().setStartSort(16384)
  600. self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
  601. # It's important to know the bounds of the aspect2d screen.
  602. self.a2dpTop = 1.0
  603. self.a2dpBottom = -1.0
  604. self.a2dpLeft = -aspectRatio
  605. self.a2dpRight = aspectRatio
  606. def getAspectRatio(self, win = None):
  607. # Returns the actual aspect ratio of the indicated (or main
  608. # window), or the default aspect ratio if there is not yet a
  609. # main window.
  610. if self.aspectRatio:
  611. return self.aspectRatio
  612. aspectRatio = 1
  613. if win == None:
  614. win = self.win
  615. if win != None and win.hasSize():
  616. aspectRatio = float(win.getXSize()) / float(win.getYSize())
  617. else:
  618. if win == None:
  619. props = WindowProperties.getDefault()
  620. else:
  621. props = win.getRequestedProperties()
  622. if not props.hasSize():
  623. props = WindowProperties.getDefault()
  624. if props.hasSize():
  625. aspectRatio = float(props.getXSize()) / float(props.getYSize())
  626. return aspectRatio
  627. def makeCamera(self, win, sort = 0, scene = None,
  628. displayRegion = (0, 1, 0, 1), stereo = None,
  629. aspectRatio = None, clearDepth = 0, clearColor = None,
  630. lens = None, camName = 'cam'):
  631. """
  632. Makes a new 3-d camera associated with the indicated window,
  633. and creates a display region in the indicated subrectangle.
  634. If stereo is True, then a stereo camera is created, with a
  635. pair of DisplayRegions. If stereo is False, then a standard
  636. camera is created. If stereo is None or omitted, a stereo
  637. camera is created if the window says it can render in stereo.
  638. """
  639. # First, make a new Camera node.
  640. camNode = Camera(camName)
  641. if lens == None:
  642. lens = PerspectiveLens()
  643. if aspectRatio == None:
  644. aspectRatio = self.getAspectRatio(win)
  645. lens.setAspectRatio(aspectRatio)
  646. camNode.setLens(lens)
  647. if scene != None:
  648. camNode.setScene(scene)
  649. # self.camera is the parent node of all cameras: a node that
  650. # we can move around to move all cameras as a group.
  651. if self.camera == None:
  652. self.camera = self.render.attachNewNode('camera')
  653. __builtins__["camera"] = self.camera
  654. cam = self.camera.attachNewNode(camNode)
  655. if self.cam == None:
  656. self.cam = cam
  657. self.camNode = camNode
  658. self.camLens = lens
  659. self.camList.append(cam)
  660. # Now, make a DisplayRegion for the camera.
  661. dr = win.makeDisplayRegion(*displayRegion)
  662. dr.setSort(sort)
  663. # By default, we do not clear 3-d display regions (the entire
  664. # window will be cleared, which is normally sufficient). But
  665. # we will if clearDepth is specified.
  666. if clearDepth:
  667. dr.setClearDepthActive(1)
  668. if clearColor:
  669. dr.setClearColorActive(1)
  670. dr.setClearColor(clearColor)
  671. dr.setCamera(cam)
  672. if stereo == None:
  673. stereo = (win.isStereo() and self.config.GetBool('default-stereo-camera', 1))
  674. if stereo:
  675. # A stereo camera!
  676. dr.setStereoChannel(Lens.SCStereo)
  677. return cam
  678. def makeCamera2d(self, win, sort = 10,
  679. displayRegion = (0, 1, 0, 1), coords = (-1, 1, -1, 1),
  680. lens = None):
  681. """
  682. Makes a new camera2d associated with the indicated window, and
  683. assigns it to render the indicated subrectangle of render2d.
  684. """
  685. dr = win.makeDisplayRegion(*displayRegion)
  686. dr.setSort(sort)
  687. # Enable clearing of the depth buffer on this new display
  688. # region (see the comment in setupRender2d, above).
  689. dr.setClearDepthActive(1)
  690. left, right, bottom, top = coords
  691. # Now make a new Camera node.
  692. cam2dNode = Camera('cam2d')
  693. if lens == None:
  694. lens = OrthographicLens()
  695. lens.setFilmSize(right - left, top - bottom)
  696. lens.setFilmOffset((right + left) * 0.5, (top + bottom) * 0.5)
  697. lens.setNearFar(-1000, 1000)
  698. cam2dNode.setLens(lens)
  699. # self.camera2d is the analog of self.camera, although it's
  700. # not as clear how useful it is.
  701. if self.camera2d == None:
  702. self.camera2d = self.render2d.attachNewNode('camera2d')
  703. camera2d = self.camera2d.attachNewNode(cam2dNode)
  704. dr.setCamera(camera2d)
  705. if self.cam2d == None:
  706. self.cam2d = camera2d
  707. return camera2d
  708. def makeCamera2dp(self, win, sort = 20,
  709. displayRegion = (0, 1, 0, 1), coords = (-1, 1, -1, 1),
  710. lens = None):
  711. """
  712. Makes a new camera2dp associated with the indicated window, and
  713. assigns it to render the indicated subrectangle of render2dp.
  714. """
  715. dr = win.makeDisplayRegion(*displayRegion)
  716. dr.setSort(sort)
  717. # Unlike render2d, we don't clear the depth buffer for
  718. # render2dp. Caveat emptor.
  719. left, right, bottom, top = coords
  720. # Now make a new Camera node.
  721. cam2dNode = Camera('cam2d')
  722. if lens == None:
  723. lens = OrthographicLens()
  724. lens.setFilmSize(right - left, top - bottom)
  725. lens.setFilmOffset((right + left) * 0.5, (top + bottom) * 0.5)
  726. lens.setNearFar(-1000, 1000)
  727. cam2dNode.setLens(lens)
  728. # self.camera2d is the analog of self.camera, although it's
  729. # not as clear how useful it is.
  730. if self.camera2dp == None:
  731. self.camera2dp = self.render2dp.attachNewNode('camera2dp')
  732. camera2dp = self.camera2dp.attachNewNode(cam2dNode)
  733. dr.setCamera(camera2dp)
  734. if self.cam2dp == None:
  735. self.cam2dp = camera2dp
  736. return camera2dp
  737. def setupDataGraph(self):
  738. """
  739. Creates the data graph and populates it with the basic input
  740. devices.
  741. """
  742. self.dataRoot = NodePath('dataRoot')
  743. # Cache the node so we do not ask for it every frame
  744. self.dataRootNode = self.dataRoot.node()
  745. self.dataUnused = NodePath('dataUnused')
  746. def setupMouse(self, win):
  747. """
  748. Creates the structures necessary to monitor the mouse input,
  749. using the indicated window. If the mouse has already been set
  750. up for a different window, those structures are deleted first.
  751. """
  752. if self.buttonThrowers != None:
  753. for bt in self.buttonThrowers:
  754. mw = bt.getParent()
  755. mk = mw.getParent()
  756. bt.removeNode()
  757. mw.removeNode()
  758. mk.removeNode()
  759. # For each mouse/keyboard device, we create
  760. # - MouseAndKeyboard
  761. # - MouseWatcher
  762. # - ButtonThrower
  763. # The ButtonThrowers are stored in a list, self.buttonThrowers.
  764. # Given a ButtonThrower, one can access the MouseWatcher and
  765. # MouseAndKeyboard using getParent.
  766. #
  767. # The MouseAndKeyboard generates mouse events and mouse
  768. # button/keyboard events; the MouseWatcher passes them through
  769. # unchanged when the mouse is not over a 2-d button, and passes
  770. # nothing through when the mouse *is* over a 2-d button. Therefore,
  771. # objects that don't want to get events when the mouse is over a
  772. # button, like the driveInterface, should be parented to
  773. # MouseWatcher, while objects that want events in all cases, like the
  774. # chat interface, should be parented to the MouseAndKeyboard.
  775. self.buttonThrowers = []
  776. self.pointerWatcherNodes = []
  777. for i in range(win.getNumInputDevices()):
  778. name = win.getInputDeviceName(i)
  779. mk = self.dataRoot.attachNewNode(MouseAndKeyboard(win, i, name))
  780. mw = mk.attachNewNode(MouseWatcher(name))
  781. mb = mw.node().getModifierButtons()
  782. mb.addButton(KeyboardButton.shift())
  783. mb.addButton(KeyboardButton.control())
  784. mb.addButton(KeyboardButton.alt())
  785. mw.node().setModifierButtons(mb)
  786. bt = mw.attachNewNode(ButtonThrower(name))
  787. if (i != 0):
  788. bt.node().setPrefix('mousedev'+str(i)+'-')
  789. mods = ModifierButtons()
  790. mods.addButton(KeyboardButton.shift())
  791. mods.addButton(KeyboardButton.control())
  792. mods.addButton(KeyboardButton.alt())
  793. bt.node().setModifierButtons(mods)
  794. self.buttonThrowers.append(bt)
  795. if (win.hasPointer(i)):
  796. self.pointerWatcherNodes.append(mw.node())
  797. self.mouseWatcher = self.buttonThrowers[0].getParent()
  798. self.mouseWatcherNode = self.mouseWatcher.node()
  799. # print "ButtonThrowers = ",self.buttonThrowers
  800. # print "PointerWatcherNodes = ",self.pointerWatcherNodes
  801. if self.recorder:
  802. # If we have a recorder, the mouseWatcher belongs under a
  803. # special MouseRecorder node, which may intercept the
  804. # mouse activity.
  805. mw = self.buttonThrowers[0].getParent()
  806. mouseRecorder = MouseRecorder('mouse')
  807. self.recorder.addRecorder(
  808. 'mouse', mouseRecorder.upcastToRecorderBase())
  809. np = mw.getParent().attachNewNode(mouseRecorder)
  810. mw.reparentTo(np)
  811. # Now we have the main trackball & drive interfaces.
  812. # useTrackball() and useDrive() switch these in and out; only
  813. # one is in use at a given time.
  814. self.trackball = self.dataUnused.attachNewNode(Trackball('trackball'))
  815. self.drive = self.dataUnused.attachNewNode(DriveInterface('drive'))
  816. self.mouse2cam = self.dataUnused.attachNewNode(Transform2SG('mouse2cam'))
  817. self.mouse2cam.node().setNode(self.camera.node())
  818. # The default is trackball mode, which is more convenient for
  819. # ad-hoc development in Python using ShowBase. Applications
  820. # can explicitly call base.useDrive() if they prefer a drive
  821. # interface.
  822. self.mouseInterface = self.trackball
  823. self.useTrackball()
  824. # A special ButtonThrower to generate keyboard events and
  825. # include the time from the OS. This is separate only to
  826. # support legacy code that did not expect a time parameter; it
  827. # will eventually be folded into the normal ButtonThrower,
  828. # above.
  829. mw = self.buttonThrowers[0].getParent()
  830. self.timeButtonThrower = mw.attachNewNode(ButtonThrower('timeButtons'))
  831. self.timeButtonThrower.node().setPrefix('time-')
  832. self.timeButtonThrower.node().setTimeFlag(1)
  833. # Tell the gui system about our new mouse watcher.
  834. self.aspect2d.node().setMouseWatcher(mw.node())
  835. self.aspect2dp.node().setMouseWatcher(mw.node())
  836. mw.node().addRegion(PGMouseWatcherBackground())
  837. def enableSoftwareMousePointer(self):
  838. """
  839. Creates some geometry and parents it to render2d to show
  840. the currently-known mouse position. Useful if the mouse
  841. pointer is invisible for some reason.
  842. """
  843. mouseViz = render2d.attachNewNode('mouseViz')
  844. lilsmiley = loader.loadModel('lilsmiley')
  845. lilsmiley.reparentTo(mouseViz)
  846. aspectRatio = self.getAspectRatio()
  847. # Scale the smiley face to 32x32 pixels.
  848. height = self.win.getYSize()
  849. lilsmiley.setScale(
  850. 32.0 / height / aspectRatio,
  851. 1.0, 32.0 / height)
  852. self.mouseWatcherNode.setGeometry(mouseViz.node())
  853. def getAlt(self):
  854. return self.mouseWatcherNode.getModifierButtons().isDown(
  855. KeyboardButton.alt())
  856. def getShift(self):
  857. return self.mouseWatcherNode.getModifierButtons().isDown(
  858. KeyboardButton.shift())
  859. def getControl(self):
  860. return self.mouseWatcherNode.getModifierButtons().isDown(
  861. KeyboardButton.control())
  862. def addAngularIntegrator(self):
  863. if not self.physicsMgrAngular:
  864. self.physicsMgrAngular = 1
  865. integrator = AngularEulerIntegrator()
  866. self.physicsMgr.attachAngularIntegrator(integrator)
  867. def enableParticles(self):
  868. if not self.particleMgrEnabled:
  869. self.particleMgrEnabled = 1
  870. self.physicsMgrEnabled = 1
  871. self.taskMgr.remove('manager-update')
  872. self.taskMgr.add(self.updateManagers, 'manager-update')
  873. def disableParticles(self):
  874. if self.particleMgrEnabled:
  875. self.particleMgrEnabled = 0
  876. self.physicsMgrEnabled = 0
  877. self.taskMgr.remove('manager-update')
  878. def toggleParticles(self):
  879. if self.particleMgrEnabled == 0:
  880. self.enableParticles()
  881. else:
  882. self.disableParticles()
  883. def isParticleMgrEnabled(self):
  884. return self.particleMgrEnabled
  885. def isPhysicsMgrEnabled(self):
  886. return self.physicsMgrEnabled
  887. def updateManagers(self, state):
  888. dt = globalClock.getDt()
  889. if (self.particleMgrEnabled == 1):
  890. self.particleMgr.doParticles(dt)
  891. if (self.physicsMgrEnabled == 1):
  892. self.physicsMgr.doPhysics(dt)
  893. return Task.cont
  894. def createStats(self):
  895. # You must specify a pstats-host in your Config.prc
  896. # The default is localhost
  897. if self.wantStats:
  898. PStatClient.connect()
  899. def addSfxManager(self, extraSfxManager):
  900. # keep a list of sfx manager objects to apply settings to,
  901. # since there may be others in addition to the one we create here
  902. self.sfxManagerList.append(extraSfxManager)
  903. newSfxManagerIsValid = (extraSfxManager!=None) and extraSfxManager.isValid()
  904. self.sfxManagerIsValidList.append(newSfxManagerIsValid)
  905. if newSfxManagerIsValid:
  906. extraSfxManager.setActive(self.sfxActive)
  907. def createBaseAudioManagers(self):
  908. self.sfxPlayer = SfxPlayer.SfxPlayer()
  909. sfxManager = AudioManager.createAudioManager()
  910. self.addSfxManager(sfxManager)
  911. self.musicManager = AudioManager.createAudioManager()
  912. self.musicManagerIsValid=self.musicManager!=None \
  913. and self.musicManager.isValid()
  914. if self.musicManagerIsValid:
  915. # ensure only 1 midi song is playing at a time:
  916. self.musicManager.setConcurrentSoundLimit(1)
  917. self.musicManager.setActive(self.musicActive)
  918. # enableMusic/enableSoundEffects are meant to be called in response
  919. # to a user request so sfxActive/musicActive represent how things
  920. # *should* be, regardless of App/OS/HW state
  921. def enableMusic(self, bEnableMusic):
  922. # dont setActive(1) if no audiofocus
  923. if self.AppHasAudioFocus and self.musicManagerIsValid:
  924. self.musicManager.setActive(bEnableMusic)
  925. self.musicActive = bEnableMusic
  926. if bEnableMusic:
  927. self.notify.debug("Enabling music")
  928. else:
  929. self.notify.debug("Disabling music")
  930. def SetAllSfxEnables(self, bEnabled):
  931. for i in range(len(self.sfxManagerList)):
  932. if (self.sfxManagerIsValidList[i]):
  933. self.sfxManagerList[i].setActive(bEnabled)
  934. def enableSoundEffects(self, bEnableSoundEffects):
  935. # dont setActive(1) if no audiofocus
  936. if self.AppHasAudioFocus or (bEnableSoundEffects==0):
  937. self.SetAllSfxEnables(bEnableSoundEffects)
  938. self.sfxActive=bEnableSoundEffects
  939. if bEnableSoundEffects:
  940. self.notify.debug("Enabling sound effects")
  941. else:
  942. self.notify.debug("Disabling sound effects")
  943. # enable/disableAllAudio allow a programmable global override-off
  944. # for current audio settings. they're meant to be called when app
  945. # loses audio focus (switched out), so we can turn off sound without
  946. # affecting internal sfxActive/musicActive sound settings, so things
  947. # come back ok when the app is switched back to
  948. def disableAllAudio(self):
  949. self.AppHasAudioFocus = 0
  950. self.SetAllSfxEnables(0)
  951. if self.musicManagerIsValid:
  952. self.musicManager.setActive(0)
  953. self.notify.debug("Disabling audio")
  954. def enableAllAudio(self):
  955. self.AppHasAudioFocus = 1
  956. self.SetAllSfxEnables(self.sfxActive)
  957. if self.musicManagerIsValid:
  958. self.musicManager.setActive(self.musicActive)
  959. self.notify.debug("Enabling audio")
  960. # This function should only be in the loader but is here for
  961. # backwards compatibility. Please do not add code here, add
  962. # it to the loader.
  963. def loadSfx(self, name):
  964. return self.loader.loadSfx(name)
  965. # This function should only be in the loader but is here for
  966. # backwards compatibility. Please do not add code here, add
  967. # it to the loader.
  968. def loadMusic(self, name):
  969. return self.loader.loadMusic(name)
  970. def playSfx(
  971. self, sfx, looping = 0, interrupt = 1, volume = None,
  972. time = 0.0, node = None):
  973. # This goes through a special player for potential localization
  974. return self.sfxPlayer.playSfx(sfx, looping, interrupt, volume, time, node)
  975. def playMusic(self, music, looping = 0, interrupt = 1, volume = None, time = 0.0):
  976. if music:
  977. if volume != None:
  978. music.setVolume(volume)
  979. # if interrupt was set to 0, start over even if it's
  980. # already playing
  981. if interrupt or (music.status() != AudioSound.PLAYING):
  982. music.setTime(time)
  983. music.setLoop(looping)
  984. music.play()
  985. def resetPrevTransform(self, state):
  986. # Clear out the previous velocity deltas now, after we have
  987. # rendered (the previous frame). We do this after the render,
  988. # so that we have a chance to draw a representation of spheres
  989. # along with their velocities. At the beginning of the frame
  990. # really means after the command prompt, which allows the user
  991. # to interactively query these deltas meaningfully.
  992. if self.cTrav:
  993. self.cTrav.resetPrevTransform(self.render)
  994. return Task.cont
  995. def dataLoop(self, state):
  996. # traverse the data graph. This reads all the control
  997. # inputs (from the mouse and keyboard, for instance) and also
  998. # directly acts upon them (for instance, to move the avatar).
  999. self.dgTrav.traverse(self.dataRootNode)
  1000. return Task.cont
  1001. def ivalLoop(self, state):
  1002. # Execute all intervals in the global ivalMgr.
  1003. IntervalManager.ivalMgr.step()
  1004. return Task.cont
  1005. def shadowCollisionLoop(self, state):
  1006. # run the collision traversal if we have a
  1007. # CollisionTraverser set.
  1008. if self.shadowTrav:
  1009. self.shadowTrav.traverse(self.render)
  1010. return Task.cont
  1011. def collisionLoop(self, state):
  1012. # run the collision traversal if we have a
  1013. # CollisionTraverser set.
  1014. if self.cTrav:
  1015. self.cTrav.traverse(self.render)
  1016. if self.appTrav:
  1017. self.appTrav.traverse(self.render)
  1018. return Task.cont
  1019. def igLoop(self, state):
  1020. # We render the watch variables for the onScreenDebug as soon
  1021. # as we reasonably can before the renderFrame().
  1022. onScreenDebug.render()
  1023. if self.recorder:
  1024. self.recorder.recordFrame()
  1025. # Finally, render the frame.
  1026. self.graphicsEngine.renderFrame()
  1027. if self.clusterSyncFlag:
  1028. self.graphicsEngine.syncFrame()
  1029. # We clear the text buffer for the onScreenDebug as soon
  1030. # as we reasonably can after the renderFrame().
  1031. onScreenDebug.clear()
  1032. if self.recorder:
  1033. self.recorder.playFrame()
  1034. if self.mainWinMinimized:
  1035. # If the main window is minimized, slow down the app a bit
  1036. # by sleeping here in igLoop so we don't use all available
  1037. # CPU needlessly.
  1038. # Note: this isn't quite right if multiple windows are
  1039. # open. We should base this on whether *all* windows are
  1040. # minimized, not just the main window. But it will do for
  1041. # now until someone complains.
  1042. time.sleep(0.1)
  1043. # Lerp stuff needs this event, and it must be generated in
  1044. # C++, not in Python.
  1045. throwNewFrame()
  1046. return Task.cont
  1047. def restart(self):
  1048. self.shutdown()
  1049. # resetPrevTransform goes at the very beginning of the frame.
  1050. self.taskMgr.add(
  1051. self.resetPrevTransform, 'resetPrevTransform', priority = -51)
  1052. # give the dataLoop task a reasonably "early" priority,
  1053. # so that it will get run before most tasks
  1054. self.taskMgr.add(self.dataLoop, 'dataLoop', priority = -50)
  1055. # spawn the ivalLoop with a later priority, so that it will
  1056. # run after most tasks, but before igLoop.
  1057. self.taskMgr.add(self.ivalLoop, 'ivalLoop', priority = 20)
  1058. # make the collisionLoop task run before igLoop,
  1059. # but leave enough room for the app to insert tasks
  1060. # between collisionLoop and igLoop
  1061. self.taskMgr.add(self.collisionLoop, 'collisionLoop', priority = 30)
  1062. # do the shadowCollisionLoop after the collisionLoop and
  1063. # befor the igLoop:
  1064. self.taskMgr.add(
  1065. self.shadowCollisionLoop, 'shadowCollisionLoop', priority = 45)
  1066. # give the igLoop task a reasonably "late" priority,
  1067. # so that it will get run after most tasks
  1068. self.taskMgr.add(self.igLoop, 'igLoop', priority = 50)
  1069. self.eventMgr.restart()
  1070. def shutdown(self):
  1071. self.taskMgr.remove('igLoop')
  1072. self.taskMgr.remove('shadowCollisionLoop')
  1073. self.taskMgr.remove('collisionLoop')
  1074. self.taskMgr.remove('dataLoop')
  1075. self.taskMgr.remove('resetPrevTransform')
  1076. self.taskMgr.remove('ivalLoop')
  1077. self.eventMgr.shutdown()
  1078. def getBackgroundColor(self, win = None):
  1079. """
  1080. Returns the current window background color. This assumes
  1081. the window is set up to clear the color each frame (this is
  1082. the normal setting).
  1083. """
  1084. if win == None:
  1085. win = self.win
  1086. return VBase4(win.getClearColor())
  1087. def setBackgroundColor(self, r = None, g = None, b = None, a = 0.0, win = None):
  1088. """
  1089. Sets the window background color to the indicated value.
  1090. This assumes the window is set up to clear the color each
  1091. frame (this is the normal setting).
  1092. The color may be either a VBase3 or a VBase4, or a 3-component
  1093. tuple, or the individual r, g, b parameters.
  1094. """
  1095. if g != None:
  1096. color = VBase4(r, g, b, a)
  1097. else:
  1098. arg = r
  1099. if isinstance(arg, VBase4):
  1100. color = arg
  1101. else:
  1102. color = VBase4(arg[0], arg[1], arg[2], a)
  1103. if win == None:
  1104. win = self.win
  1105. if win:
  1106. win.setClearColor(color)
  1107. def toggleBackface(self):
  1108. if self.backfaceCullingEnabled:
  1109. self.backfaceCullingOff()
  1110. else:
  1111. self.backfaceCullingOn()
  1112. def backfaceCullingOn(self):
  1113. if not self.backfaceCullingEnabled:
  1114. self.render.setTwoSided(0)
  1115. self.backfaceCullingEnabled = 1
  1116. def backfaceCullingOff(self):
  1117. if self.backfaceCullingEnabled:
  1118. self.render.setTwoSided(1)
  1119. self.backfaceCullingEnabled = 0
  1120. def toggleTexture(self):
  1121. if self.textureEnabled:
  1122. self.textureOff()
  1123. else:
  1124. self.textureOn()
  1125. def textureOn(self):
  1126. self.render.clearTexture()
  1127. self.textureEnabled = 1
  1128. def textureOff(self):
  1129. self.render.setTextureOff(100)
  1130. self.textureEnabled = 0
  1131. def toggleWireframe(self):
  1132. if self.wireframeEnabled:
  1133. self.wireframeOff()
  1134. else:
  1135. self.wireframeOn()
  1136. def wireframeOn(self):
  1137. self.render.setRenderModeWireframe(100)
  1138. self.render.setTwoSided(1)
  1139. self.wireframeEnabled = 1
  1140. def wireframeOff(self):
  1141. self.render.clearRenderMode()
  1142. render.setTwoSided(not self.backfaceCullingEnabled)
  1143. self.wireframeEnabled = 0
  1144. def disableMouse(self):
  1145. """
  1146. Temporarily disable the mouse control of the camera, either
  1147. via the drive interface or the trackball, whichever is
  1148. currently in use.
  1149. """
  1150. # We don't reparent the drive interface or the trackball;
  1151. # whichever one was there before will remain in the data graph
  1152. # and active. This way they won't lose button events while
  1153. # the mouse is disabled. However, we do move the mouse2cam
  1154. # object out of there, so we won't be updating the camera any
  1155. # more.
  1156. if self.mouse2cam:
  1157. self.mouse2cam.reparentTo(self.dataUnused)
  1158. def enableMouse(self):
  1159. """
  1160. Reverse the effect of a previous call to disableMouse().
  1161. useDrive() also implicitly enables the mouse.
  1162. """
  1163. if self.mouse2cam:
  1164. self.mouse2cam.reparentTo(self.mouseInterface)
  1165. def setMouseOnNode(self, newNode):
  1166. if self.mouse2cam:
  1167. self.mouse2cam.node().setNode(newNode)
  1168. def changeMouseInterface(self, changeTo):
  1169. """
  1170. Switch mouse action
  1171. """
  1172. # Get rid of the prior interface:
  1173. self.mouseInterface.reparentTo(self.dataUnused)
  1174. # Update the mouseInterface to point to the drive
  1175. self.mouseInterface = changeTo
  1176. self.mouseInterfaceNode = self.mouseInterface.node()
  1177. # Hookup the drive to the camera.
  1178. self.mouseInterface.reparentTo(self.mouseWatcher)
  1179. if self.mouse2cam:
  1180. self.mouse2cam.reparentTo(self.mouseInterface)
  1181. def useDrive(self):
  1182. """
  1183. Switch mouse action to drive mode
  1184. """
  1185. if self.drive:
  1186. self.changeMouseInterface(self.drive)
  1187. # Set the height to a good eyeheight
  1188. self.mouseInterfaceNode.reset()
  1189. self.mouseInterfaceNode.setZ(4.0)
  1190. def useTrackball(self):
  1191. """
  1192. Switch mouse action to trackball mode
  1193. """
  1194. if self.trackball:
  1195. self.changeMouseInterface(self.trackball)
  1196. def oobe(self):
  1197. """
  1198. Enable a special "out-of-body experience" mouse-interface
  1199. mode. This can be used when a "god" camera is needed; it
  1200. moves the camera node out from under its normal node and sets
  1201. the world up in trackball state. Button events are still sent
  1202. to the normal mouse action node (e.g. the DriveInterface), and
  1203. mouse events, if needed, may be sent to the normal node by
  1204. holding down the Control key.
  1205. This is different than useTrackball(), which simply changes
  1206. the existing mouse action to a trackball interface. In fact,
  1207. OOBE mode doesn't care whether useDrive() or useTrackball() is
  1208. in effect; it just temporarily layers a new trackball
  1209. interface on top of whatever the basic interface is. You can
  1210. even switch between useDrive() and useTrackball() while OOBE
  1211. mode is in effect.
  1212. This is a toggle; the second time this function is called, it
  1213. disables the mode.
  1214. """
  1215. # If oobeMode was never set, set it to false and create the
  1216. # structures we need to implement OOBE.
  1217. try:
  1218. self.oobeMode
  1219. except:
  1220. self.oobeMode = 0
  1221. self.oobeCamera = self.hidden.attachNewNode('oobeCamera')
  1222. self.oobeCameraTrackball = self.oobeCamera.attachNewNode('oobeCameraTrackball')
  1223. self.oobeLens = PerspectiveLens()
  1224. self.oobeLens.setAspectRatio(self.getAspectRatio())
  1225. self.oobeLens.setNearFar(0.1, 10000.0)
  1226. self.oobeLens.setFov(52.0)
  1227. self.oobeTrackball = self.dataUnused.attachNewNode(Trackball('oobeTrackball'), 1)
  1228. self.oobe2cam = self.oobeTrackball.attachNewNode(Transform2SG('oobe2cam'))
  1229. self.oobe2cam.node().setNode(self.oobeCameraTrackball.node())
  1230. self.oobeVis = loader.loadModelOnce('models/misc/camera')
  1231. if self.oobeVis:
  1232. self.oobeVis.node().setFinal(1)
  1233. self.oobeCullFrustum = None
  1234. self.oobeCullFrustumVis = None
  1235. if self.oobeMode:
  1236. # Disable OOBE mode.
  1237. if self.oobeCullFrustum != None:
  1238. # First, disable OOBE cull mode.
  1239. self.oobeCull()
  1240. if self.oobeVis:
  1241. self.oobeVis.reparentTo(self.hidden)
  1242. # Restore the mouse interface node.
  1243. #self.mouseInterface.reparentTo(self.mouseWatcher)
  1244. self.oobeTrackball.reparentTo(self.dataUnused)
  1245. self.cam.reparentTo(self.camera)
  1246. self.camNode.setLens(self.camLens)
  1247. self.oobeCamera.reparentTo(self.hidden)
  1248. self.oobeMode = 0
  1249. bboard.post('oobeEnabled', False)
  1250. else:
  1251. bboard.post('oobeEnabled', True)
  1252. try:
  1253. cameraParent = localAvatar
  1254. except:
  1255. # Make oobeCamera be a sibling of wherever camera is now.
  1256. cameraParent = self.camera.getParent()
  1257. self.oobeCamera.reparentTo(cameraParent)
  1258. self.oobeCamera.clearMat()
  1259. # Move aside the current mouse interface node and put the
  1260. # oobeTrackball in its place.
  1261. #self.mouseInterface.reparentTo(self.dataUnused)
  1262. self.oobeTrackball.reparentTo(self.mouseWatcher)
  1263. # Set our initial OOB position to be just behind the camera.
  1264. mat = Mat4.translateMat(0, -10, 3) * self.camera.getMat(cameraParent)
  1265. mat.invertInPlace()
  1266. self.oobeTrackball.node().setMat(mat)
  1267. self.cam.reparentTo(self.oobeCameraTrackball)
  1268. # Don't change the camera lens--keep it with the original lens.
  1269. #self.camNode.setLens(self.oobeLens)
  1270. if self.oobeVis:
  1271. self.oobeVis.reparentTo(self.camera)
  1272. self.oobeMode = 1
  1273. def oobeCull(self):
  1274. """
  1275. While in OOBE mode (see above), cull the viewing frustum as if
  1276. it were still attached to our original camera. This allows us
  1277. to visualize the effectiveness of our bounding volumes.
  1278. """
  1279. # First, make sure OOBE mode is enabled.
  1280. try:
  1281. if not self.oobeMode:
  1282. self.oobe()
  1283. except:
  1284. self.oobe()
  1285. if self.oobeCullFrustum == None:
  1286. # Enable OOBE culling.
  1287. pnode = LensNode('oobeCull')
  1288. pnode.setLens(self.camLens)
  1289. self.oobeCullFrustum = self.camera.attachNewNode(pnode)
  1290. # Create a visible representation of the frustum.
  1291. geom = self.camLens.makeGeometry()
  1292. if geom != None:
  1293. gn = GeomNode('frustum')
  1294. gn.addGeom(geom)
  1295. self.oobeCullFrustumVis = self.oobeVis.attachNewNode(gn)
  1296. # Tell the camera to cull from here instead of its own
  1297. # origin.
  1298. self.camNode.setCullCenter(self.oobeCullFrustum)
  1299. else:
  1300. # Disable OOBE culling.
  1301. self.camNode.setCullCenter(NodePath())
  1302. self.oobeCullFrustum.removeNode()
  1303. self.oobeCullFrustum = None
  1304. if self.oobeCullFrustumVis != None:
  1305. self.oobeCullFrustumVis.removeNode()
  1306. self.oobeCullFrustumVis = None
  1307. def showCameraFrustum(self):
  1308. # Create a visible representation of the frustum.
  1309. self.removeCameraFrustum()
  1310. geom = self.camLens.makeGeometry()
  1311. if geom != None:
  1312. gn = GeomNode('frustum')
  1313. gn.addGeom(geom)
  1314. self.camFrustumVis = self.camera.attachNewNode(gn)
  1315. def removeCameraFrustum(self):
  1316. if self.camFrustumVis:
  1317. self.camFrustumVis.removeNode()
  1318. def screenshot(self, namePrefix = 'screenshot',
  1319. defaultFilename = 1, source = None):
  1320. """ Captures a screenshot from the main window or from the
  1321. specified window or Texture and writes it to a filename in the
  1322. current directory (or to a specified directory).
  1323. If defaultFilename is True, the filename is synthesized by
  1324. appending namePrefix to a default filename suffix (including
  1325. the filename extension) specified in the Config variable
  1326. screenshot-filename. Otherwise, if defaultFilename is False,
  1327. the entire namePrefix is taken to be the filename to write,
  1328. and this string should include a suitable filename extension
  1329. that will be used to determine the type of image file to
  1330. write.
  1331. Normally, the source is a GraphicsWindow, GraphicsBuffer or
  1332. DisplayRegion. If a Texture is supplied instead, it must have
  1333. a ram image (that is, if it was generated by
  1334. makeTextureBuffer() or makeCubeMap(), the parameter toRam
  1335. should have been set true). If it is a cube map texture as
  1336. generated by makeCubeMap(), namePrefix should contain the hash
  1337. mark ('#') character. """
  1338. if source == None:
  1339. source = self.win
  1340. if defaultFilename:
  1341. filename = GraphicsOutput.makeScreenshotFilename(namePrefix)
  1342. else:
  1343. filename = Filename(namePrefix)
  1344. if isinstance(source, Texture):
  1345. if source.getZSize() > 1:
  1346. saved = source.writePages(filename)
  1347. else:
  1348. saved = source.write(filename)
  1349. else:
  1350. saved = source.saveScreenshot(filename)
  1351. # Announce to anybody that a screenshot has been taken
  1352. messenger.send('screenshot', [filename])
  1353. return saved
  1354. def saveCubeMap(self, namePrefix = 'cube_map_#.png',
  1355. defaultFilename = 0, source = None,
  1356. camera = None, size = 128,
  1357. cameraMask = BitMask32.allOn()):
  1358. """
  1359. Similar to screenshot(), this sets up a temporary cube map
  1360. Texture which it uses to take a series of six snapshots of the
  1361. current scene, one in each of the six cube map directions.
  1362. This requires rendering a new frame.
  1363. Unlike screenshot(), source may only be a GraphicsWindow,
  1364. GraphicsBuffer, or DisplayRegion; it may not be a Texture.
  1365. camera should be the node to which the cubemap cameras will be
  1366. parented. The default is the camera associated with source,
  1367. if source is a DisplayRegion, or base.camera otherwise."""
  1368. if source == None:
  1369. source = base.win
  1370. if camera == None:
  1371. if hasattr(source, "getCamera"):
  1372. camera = source.getCamera()
  1373. if camera == None:
  1374. camera = base.camera
  1375. if hasattr(source, "getWindow"):
  1376. source = source.getWindow()
  1377. rig = NodePath(namePrefix)
  1378. buffer = source.makeCubeMap(namePrefix, size, rig, cameraMask, 1)
  1379. if buffer == None:
  1380. raise StandardError, "Could not make cube map."
  1381. # Set the near and far planes from the default lens.
  1382. lens = rig.find('**/+Camera').node().getLens()
  1383. lens.setNearFar(base.camLens.getNear(), base.camLens.getFar())
  1384. # Now render a frame to fill up the texture.
  1385. rig.reparentTo(camera)
  1386. base.graphicsEngine.openWindows()
  1387. base.graphicsEngine.renderFrame()
  1388. tex = buffer.getTexture()
  1389. saved = self.screenshot(namePrefix = namePrefix,
  1390. defaultFilename = defaultFilename,
  1391. source = tex)
  1392. base.graphicsEngine.removeWindow(buffer)
  1393. rig.removeNode()
  1394. return saved
  1395. def saveSphereMap(self, namePrefix = 'spheremap.png',
  1396. defaultFilename = 0, source = None,
  1397. camera = None, size = 256,
  1398. cameraMask = BitMask32.allOn(),
  1399. numVertices = 1000):
  1400. """
  1401. This works much like saveCubeMap(), and uses the graphics
  1402. API's hardware cube-mapping ability to get a 360-degree view
  1403. of the world. But then it converts the six cube map faces
  1404. into a single fisheye texture, suitable for applying as a
  1405. static environment map (sphere map).
  1406. For eye-relative static environment maps, sphere maps are
  1407. often preferable to cube maps because they require only a
  1408. single texture and because they are supported on a broader
  1409. range of hardware.
  1410. """
  1411. if source == None:
  1412. source = base.win
  1413. if camera == None:
  1414. if hasattr(source, "getCamera"):
  1415. camera = source.getCamera()
  1416. if camera == None:
  1417. camera = base.camera
  1418. if hasattr(source, "getWindow"):
  1419. source = source.getWindow()
  1420. # First, make an offscreen buffer to convert the cube map to a
  1421. # sphere map. We make it first so we can guarantee the
  1422. # rendering order for the cube map.
  1423. toSphere = source.makeTextureBuffer(namePrefix, size, size,
  1424. Texture(), 1)
  1425. # Now make the cube map buffer.
  1426. rig = NodePath(namePrefix)
  1427. buffer = toSphere.makeCubeMap(namePrefix, size, rig, cameraMask, 0)
  1428. if buffer == None:
  1429. base.graphicsEngine.removeWindow(toSphere)
  1430. raise StandardError, "Could not make cube map."
  1431. # Set the near and far planes from the default lens.
  1432. lens = rig.find('**/+Camera').node().getLens()
  1433. lens.setNearFar(base.camLens.getNear(), base.camLens.getFar())
  1434. # Set up the scene to convert the cube map. It's just a
  1435. # simple scene, with only the FisheyeMaker object in it.
  1436. dr = toSphere.makeDisplayRegion()
  1437. camNode = Camera('camNode')
  1438. lens = OrthographicLens()
  1439. lens.setFilmSize(2, 2)
  1440. lens.setNearFar(-1000, 1000)
  1441. camNode.setLens(lens)
  1442. root = NodePath('buffer')
  1443. cam = root.attachNewNode(camNode)
  1444. dr.setCamera(cam)
  1445. fm = FisheyeMaker('card')
  1446. fm.setNumVertices(numVertices)
  1447. fm.setSquareInscribed(1, 1.1)
  1448. fm.setReflection(1)
  1449. card = root.attachNewNode(fm.generate())
  1450. card.setTexture(buffer.getTexture())
  1451. # Now render a frame. This will render out the cube map and
  1452. # then apply it to the the card in the toSphere buffer.
  1453. rig.reparentTo(camera)
  1454. base.graphicsEngine.openWindows()
  1455. base.graphicsEngine.renderFrame()
  1456. saved = self.screenshot(namePrefix = namePrefix,
  1457. defaultFilename = defaultFilename,
  1458. source = toSphere.getTexture())
  1459. base.graphicsEngine.removeWindow(buffer)
  1460. base.graphicsEngine.removeWindow(toSphere)
  1461. rig.removeNode()
  1462. return saved
  1463. def movie(self, namePrefix = 'movie', duration = 1.0, fps = 30,
  1464. format = 'png', sd = 4, source = None):
  1465. """
  1466. Spawn a task to capture a movie using the screenshot function.
  1467. - namePrefix will be used to form output file names (can include
  1468. path information (e.g. '/i/beta/frames/myMovie')
  1469. - duration is the length of the movie in seconds
  1470. - fps is the frame rate of the resulting movie
  1471. - format specifies output file format (e.g. png, bmp)
  1472. - sd specifies number of significant digits for frame count in the
  1473. output file name (e.g. if sd = 4, movie_0001.png)
  1474. - source is the Window, Buffer, DisplayRegion, or Texture from which
  1475. to save the resulting images. The default is the main window.
  1476. """
  1477. globalClock.setMode(ClockObject.MNonRealTime)
  1478. globalClock.setDt(1.0/float(fps))
  1479. t = taskMgr.add(self._movieTask, namePrefix + '_task')
  1480. t.frameIndex = 0 # Frame 0 is not captured.
  1481. t.numFrames = int(duration * fps)
  1482. t.source = source
  1483. t.outputString = namePrefix + '_%0' + `sd` + 'd.' + format
  1484. t.uponDeath = lambda state: globalClock.setMode(ClockObject.MNormal)
  1485. def _movieTask(self, state):
  1486. if state.frameIndex != 0:
  1487. frameName = state.outputString % state.frameIndex
  1488. self.notify.info("Capturing frame: " + frameName)
  1489. self.screenshot(namePrefix = frameName, defaultFilename = 0,
  1490. source = state.source)
  1491. state.frameIndex += 1
  1492. if state.frameIndex > state.numFrames:
  1493. return Task.done
  1494. else:
  1495. return Task.cont
  1496. def __windowEvent(self, win):
  1497. if win == self.win:
  1498. properties = win.getProperties()
  1499. self.notify.info("Got window event: %s" % (repr(properties)))
  1500. if not properties.getOpen():
  1501. # If the user closes the main window, we should exit.
  1502. self.notify.info("User closed main window.")
  1503. self.userExit()
  1504. if properties.getMinimized() and not self.mainWinMinimized:
  1505. # If the main window is minimized, throw an event to
  1506. # stop the music.
  1507. self.mainWinMinimized = 1
  1508. messenger.send('PandaPaused')
  1509. elif not properties.getMinimized() and self.mainWinMinimized:
  1510. # If the main window is restored, throw an event to
  1511. # restart the music.
  1512. self.mainWinMinimized = 0
  1513. messenger.send('PandaRestarted')
  1514. def userExit(self):
  1515. # The user has requested we exit the program. Deal with this.
  1516. if self.exitFunc:
  1517. self.exitFunc()
  1518. self.notify.info("Exiting ShowBase.")
  1519. self.finalizeExit()
  1520. def finalizeExit(self):
  1521. sys.exit()
  1522. def startTk(self, fWantTk = 1):
  1523. self.wantTk = fWantTk
  1524. if self.wantTk:
  1525. import TkGlobal
  1526. taskMgr.remove('tkLoop')
  1527. TkGlobal.spawnTkLoop()
  1528. def startDirect(self, fWantDirect = 1, fWantTk = 1):
  1529. self.startTk(fWantTk)
  1530. self.wantDirect = fWantDirect
  1531. if self.wantDirect:
  1532. from direct.directtools import DirectSession
  1533. direct.enable()
  1534. else:
  1535. __builtins__["direct"] = self.direct = None
  1536. def run(self):
  1537. self.taskMgr.run()
  1538. # A class to encapsulate information necessary for multiwindow support.
  1539. class WindowControls:
  1540. def __init__(
  1541. self, win, cam=None, cam2d=None, mouseWatcher=None,
  1542. mouseKeyboard=None, closeCmd=lambda: 0):
  1543. self.win = win
  1544. self.camera = cam
  1545. self.camera2d = cam2d
  1546. self.mouseWatcher = mouseWatcher
  1547. self.mouseKeyboard = mouseKeyboard
  1548. self.closeCommand = closeCmd
  1549. def __str__(self):
  1550. s = "window = " + str(self.win) + "\n"
  1551. s += "camera = " + str(self.camera) + "\n"
  1552. s += "camera2d = " + str(self.camera2d) + "\n"
  1553. s += "mouseWatcher = " + str(self.mouseWatcher) + "\n"
  1554. s += "mouseAndKeyboard = " + str(self.mouseKeyboard) + "\n"
  1555. return s