ShowBase.py 67 KB

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