ShowBase.py 86 KB

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