ShowBase.py 102 KB

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