| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175 |
- # This module redefines the builtin import function with one
- # that prints out every import it does in a hierarchical form
- # Annoying and very noisy, but sometimes useful
- # import VerboseImport
- from PandaModules import *
- from DirectNotifyGlobal import *
- from MessengerGlobal import *
- from TaskManagerGlobal import *
- from EventManagerGlobal import *
- from PythonUtil import *
- from ParticleManagerGlobal import *
- from PhysicsManagerGlobal import *
- from IntervalManager import ivalMgr
- import Task
- import EventManager
- import math
- import sys
- import Loader
- import time
- import FSM
- import State
- import DirectObject
- __builtins__["FADE_SORT_INDEX"] = 1000
- __builtins__["NO_FADE_SORT_INDEX"] = 2000
- # Now ShowBase is a DirectObject. We need this so ShowBase can hang
- # hooks on messages, particularly on window-event. This doesn't
- # *seem* to cause anyone any problems.
- class ShowBase(DirectObject.DirectObject):
- notify = directNotify.newCategory("ShowBase")
- def __init__(self):
- # Get the dconfig object
- self.config = ConfigConfigureGetConfigConfigShowbase
- if self.config.GetBool('use-vfs', 1):
- vfs = VirtualFileSystem.getGlobalPtr()
- else:
- vfs = None
- # Store dconfig variables
- self.wantTk = self.config.GetBool('want-tk', 0)
- self.sfxActive = self.config.GetBool('audio-sfx-active', 1)
- self.musicActive = self.config.GetBool('audio-music-active', 1)
- self.wantFog = self.config.GetBool('want-fog', 1)
- self.screenshotExtension = self.config.GetString('screenshot-extension', 'jpg')
- self.musicManager = None
- self.musicManagerIsValid = None
- self.sfxManagerList = []
- self.sfxManagerIsValidList = []
- self.wantDIRECT = self.config.GetBool('want-directtools', 0)
- self.wantStats = self.config.GetBool('want-stats', 0)
- # Fill this in with a function to invoke when the user "exits"
- # the program by closing the main window.
- self.exitFunc = None
- taskMgr.taskTimerVerbose = self.config.GetBool('task-timer-verbose', 0)
- taskMgr.extendedExceptions = self.config.GetBool('extended-exceptions', 0)
-
- taskMgr.pStatsTasks = self.config.GetBool('pstats-tasks', 0)
- # Set up the TaskManager to reset the PStats clock back
- # whenever we resume from a pause. This callback function is
- # a little hacky, but we can't call it directly from within
- # the TaskManager because he doesn't know about PStats (and
- # has to run before libpanda is even loaded).
- taskMgr.resumeFunc = PStatClient.resumeAfterPause
- fsmRedefine = self.config.GetBool('fsm-redefine', 0)
- State.FsmRedefine = fsmRedefine
- # This is used for syncing multiple PCs in a distributed cluster
- try:
- # Has the cluster sync variable been set externally?
- self.clusterSyncFlag = clusterSyncFlag
- except NameError:
- # Has the clusterSyncFlag been set via a config variable
- self.clusterSyncFlag = base.config.GetBool('cluster-sync', 0)
- self.hidden = NodePath('hidden')
- # We need a graphics engine to manage the actual rendering.
- self.graphicsEngine = GraphicsEngine()
- self.setupRender()
- self.setupRender2d()
- self.setupDataGraph()
- # This is a placeholder for a CollisionTraverser. If someone
- # stores a CollisionTraverser pointer here, we'll traverse it
- # in the collisionloop task.
- self.cTrav = 0
- # Ditto for an AppTraverser.
- self.appTrav = 0
- # This is the DataGraph traverser, which we might as well
- # create now.
- self.dgTrav = DataGraphTraverser()
- # base.win is the main, or only window; base.winList is a list of
- # *all* windows. Similarly with base.camList.
- self.win = None
- self.winList = []
- self.mainWinMinimized = 0
- self.pipe = None
- self.pipeList = []
- self.mak = None
- self.cam = None
- self.camList = []
- self.camNode = None
- self.camLens = None
- #self.camera = self.render.attachNewNode('camera')
- self.camera = None
- self.cameraList = []
- self.camera2d = self.render2d.attachNewNode('camera2d')
- # Now that we've set up the window structures, assign an exitfunc.
- self.oldexitfunc = getattr(sys, 'exitfunc', None)
- sys.exitfunc = self.exitfunc
- # Open the default rendering window.
- if self.config.GetBool('open-default-window', 1):
- self.openMainWindow()
- if self.win == None:
- # Try a little harder if the window wouldn't open.
- self.makeAllPipes()
- while self.win == None and len(self.pipeList) > 1:
- self.pipeList.remove(self.pipe)
- self.pipe = self.pipeList[0]
- self.openMainWindow()
- self.loader = Loader.Loader(self)
- self.eventMgr = eventMgr
- self.messenger = messenger
- self.taskMgr = taskMgr
- # Particle manager
- self.particleMgr = particleMgr
- self.particleMgr.setFrameStepping(1)
- self.particleMgrEnabled = 0
- # Physics manager
- self.physicsMgr = physicsMgr
- integrator = LinearEulerIntegrator()
- self.physicsMgr.attachLinearIntegrator(integrator)
- self.physicsMgrEnabled = 0
- self.physicsMgrAngular = 0
- self.createBaseAudioManagers()
- self.createStats()
- self.AppHasAudioFocus = 1
- __builtins__["base"] = self
- __builtins__["render2d"] = self.render2d
- __builtins__["aspect2d"] = self.aspect2d
- __builtins__["render"] = self.render
- __builtins__["hidden"] = self.hidden
- __builtins__["camera"] = self.camera
- __builtins__["loader"] = self.loader
- __builtins__["taskMgr"] = self.taskMgr
- __builtins__["eventMgr"] = self.eventMgr
- __builtins__["messenger"] = self.messenger
- __builtins__["config"] = self.config
- __builtins__["run"] = self.run
- __builtins__["ostream"] = Notify.out()
- __builtins__["directNotify"] = directNotify
- __builtins__["globalClock"] = ClockObject.getGlobalClock()
- __builtins__["vfs"] = vfs
- # Now hang a hook on the window-event from Panda. This allows
- # us to detect when the user resizes, minimizes, or closes the
- # main window.
- self.accept('window-event', self.__windowEvent)
- # Transition effects (fade, iris, etc)
- import Transitions
- self.transitions = Transitions.Transitions(self.loader)
- # Tk
- if self.wantTk:
- import TkGlobal
- if self.wantDIRECT:
- import DirectSession
- direct.enable()
- else:
- __builtins__["direct"] = self.direct = None
- self.restart()
- def exitfunc(self):
- """exitfunc(self)
- This should be assigned to sys.exitfunc to be called just
- before Python shutdown. It guarantees that the Panda window
- is closed cleanly, so that we free system resources, restore
- the desktop and keyboard functionality, etc.
- """
- self.graphicsEngine.removeAllWindows()
- del self.win
- del self.winList
- del self.pipe
- try:
- direct.panel.destroy()
- except StandardError:
- pass
- if self.oldexitfunc:
- self.oldexitfunc()
- def makeDefaultPipe(self):
- """makeDefaultPipe(self)
- Creates the default GraphicsPipe, which will be used to make
- windows unless otherwise specified.
- """
- assert(self.pipe == None)
- selection = GraphicsPipeSelection.getGlobalPtr()
- selection.printPipeTypes()
- self.pipe = selection.makeDefaultPipe()
- if not self.pipe:
- self.notify.error("No graphics pipe is available! Check your Configrc!")
- self.notify.info("Default graphics pipe is %s (%s)." % (self.pipe.getInterfaceName(), self.pipe.getType().getName()))
- self.pipeList.append(self.pipe)
- def makeAllPipes(self):
- """makeAllPipes(self)
- Creates all GraphicsPipes that the system knows about and fill up
- self.pipeList with them.
- """
- shouldPrintPipes = 0
- selection = GraphicsPipeSelection.getGlobalPtr()
- selection.loadAuxModules()
- # First, we should make sure the default pipe exists.
- if self.pipe == None:
- self.makeDefaultPipe()
- # Now go through the list of known pipes, and make each one if
- # we don't have one already.
- numPipeTypes = selection.getNumPipeTypes()
- for i in range(numPipeTypes):
- pipeType = selection.getPipeType(i)
- # Do we already have a pipe of this type on the list?
- # This operation is n-squared, but presumably there won't
- # be more than a handful of pipe types, so who cares.
- already = 0
- for pipe in self.pipeList:
- if pipe.getType() == pipeType:
- already = 1
- if not already:
- pipe = selection.makePipe(pipeType)
- if pipe:
- self.notify.info("Got aux graphics pipe %s (%s)." % (pipe.getInterfaceName(), pipe.getType().getName()))
- self.pipeList.append(pipe)
- else:
- self.notify.info("Could not make graphics pipe %s." % (pipeType.getName()))
- def openWindow(self):
- """openWindow(self)
- Invokes ChanConfig to create a window and adds it to the list
- of windows that are to be updated every frame.
- """
- if self.pipe == None:
- self.makeDefaultPipe()
- chanString = self.config.GetString('chan-config', 'single')
- chanConfig = ChanConfig(self.graphicsEngine, self.pipe, chanString,
- self.render)
-
- win = chanConfig.getWin()
- if win != None:
- # Adjust some of the window properties.
- props = WindowProperties()
- windowTitle = self.config.GetString("window-title", "");
- if windowTitle:
- props.setTitle(windowTitle)
- win.requestProperties(props)
- if self.win == None:
- self.win = win
- self.winList.append(win)
- self.getCameras(chanConfig)
-
- return win
- def closeWindow(self, win):
- """closeWindow(self, win)
- Closes the indicated window and removes it from the list of
- windows. If it is the main window, clears the main window
- pointer to None.
- """
- # First, remove all of the cameras associated with display
- # regions on the window.
- numRegions = win.getNumDisplayRegions()
- for i in range(numRegions):
- dr = win.getDisplayRegion(i)
- cam = NodePath(dr.getCamera())
- dr.setCamera(NodePath())
- if not cam.isEmpty() and cam.node().getNumDisplayRegions() == 0:
- # If the camera is used by no other DisplayRegions,
- # remove it.
- if self.camList.count(cam) != 0:
- self.camList.remove(cam)
- if not cam.isEmpty():
- if cam == self.cam:
- self.cam = None
- cam.removeNode()
- # Now we can actually close the window.
- self.graphicsEngine.removeWindow(win)
- self.winList.remove(win)
- if win == self.win:
- self.win = None
- def openMainWindow(self):
- """openMainWindow(self)
- Creates the initial, main window for the application, and sets
- up the mouse and render2d structures appropriately for it. If
- this method is called a second time, it will close the
- previous main window and open a new one, preserving the lens
- properties in base.camLens.
- The return value is true on success, or false on failure (in
- which case base.win may be either None, or the previous,
- closed window).
- """
- success = 1
- oldWin = self.win
- oldLens = self.camLens
- oldClearColorActive = None
- if self.win != None:
- # Close the previous window.
- oldClearColorActive = self.win.getClearColorActive()
- oldClearColor = VBase4(self.win.getClearColor())
- oldClearDepthActive = self.win.getClearDepthActive()
- oldClearDepth = self.win.getClearDepth()
- self.closeWindow(self.win)
- # Open a new window.
- self.openWindow()
- if self.win == None:
- self.win = oldWin
- self.winList.append(oldWin)
- success = 0
-
- if self.win != None:
- self.setupMouse(self.win)
- self.makeCamera2d(self.win, -1, 1, -1, 1)
- if oldLens != None:
- # Restore the previous lens properties.
- self.camNode.setLens(oldLens)
- self.camLens = oldLens
- if oldClearColorActive != None:
- # Restore the previous clear properties.
- self.win.setClearColorActive(oldClearColorActive)
- self.win.setClearColor(oldClearColor)
- self.win.setClearDepthActive(oldClearDepthActive)
- self.win.setClearDepth(oldClearDepth)
- return success
-
- def setupRender(self):
- """setupRender(self)
- Creates the render scene graph, the primary scene graph for
- rendering 3-d geometry.
- """
- self.render = NodePath('render')
- self.render.setTwoSided(0)
- self.backfaceCullingEnabled = 1
- self.textureEnabled = 1
- self.wireframeEnabled = 0
- def setupRender2d(self):
- """setupRender2d(self)
- Creates the render2d scene graph, the primary scene graph for
- 2-d objects and gui elements that are superimposed over the
- 3-d geometry in the window.
- """
- self.render2d = NodePath('render2d')
- # Set up some overrides to turn off certain properties which
- # we probably won't need for 2-d objects.
- # It's particularly important to turn off the depth test,
- # since we'll be keeping the same depth buffer already filled
- # by the previously-drawn 3-d scene--we don't want to pay for
- # a clear operation, but we also don't want to collide with
- # that depth buffer.
- dt = DepthTestAttrib.make(DepthTestAttrib.MNone)
- dw = DepthWriteAttrib.make(DepthWriteAttrib.MOff)
- #lt = LightTransition.allOff()
- self.render2d.node().setAttrib(dt, 1)
- self.render2d.node().setAttrib(dw, 1)
- #self.render2d.node().setAttrib(lt, 1)
- self.render2d.setMaterialOff(1)
- self.render2d.setTwoSided(1, 1)
-
- # The normal 2-d layer has an aspect ratio that matches the
- # window, but its coordinate system is square. This means
- # anything we parent to render2d gets stretched. For things
- # where that makes a difference, we set up aspect2d, which
- # scales things back to the right aspect ratio.
- # For now, we assume that the window will have an aspect ratio
- # matching that of a traditional PC screen (w / h) = (4 / 3)
- self.aspectRatio = self.config.GetFloat('aspect-ratio', (4.0 / 3.0))
- self.aspect2d = self.render2d.attachNewNode(PGTop("aspect2d"))
- self.aspect2d.setScale(1.0 / self.aspectRatio, 1.0, 1.0)
- # It's important to know the bounds of the aspect2d screen.
- self.a2dTop = 1.0
- self.a2dBottom = -1.0
- self.a2dLeft = -self.aspectRatio
- self.a2dRight = self.aspectRatio
- def makeCamera2d(self, win, left, right, bottom, top):
- """makeCamera2d(self)
- Makes a new camera2d associated with the indicated window, and
- assigns it to render the indicated subrectangle of render2d.
- """
- # First, we need to make a new layer on the window.
- chan = win.getChannel(0)
- layer = chan.makeLayer()
- # And make a display region to cover the whole layer.
- dr = layer.makeDisplayRegion()
- # Now make a new Camera node.
- cam2dNode = Camera('cam2d')
- lens = OrthographicLens()
- lens.setFilmSize(right - left, top - bottom)
- lens.setFilmOffset((right + left) * 0.5, (top + bottom) * 0.5)
- lens.setNearFar(-1000, 1000)
- cam2dNode.setLens(lens)
- cam2dNode.setScene(self.render2d)
- camera2d = self.camera2d.attachNewNode(cam2dNode)
- dr.setCamera(camera2d)
- return camera2d
- def setupDataGraph(self):
- """setupDataGraph(self)
- Creates the data graph and populates it with the basic input
- devices.
- """
-
- self.dataRoot = NodePath('dataRoot')
- # Cache the node so we do not ask for it every frame
- self.dataRootNode = self.dataRoot.node()
- self.dataUnused = NodePath('dataUnused')
- def setupMouse(self, win):
- """setupMouse(self, win)
- Creates the structures necessary to monitor the mouse input,
- using the indicated window. If the mouse has already been set
- up for a different window, this changes the mouse to reference
- the new window.
- """
- if self.mak != None:
- # The mouse has already been set up; reappropriate it.
- self.mak.node().setSource(win, 0)
- # Reset the currently-held modifier button list for good
- # measure.
- bt = base.buttonThrower.node()
- mb = ModifierButtons(bt.getModifierButtons())
- mb.allButtonsUp()
- bt.setModifierButtons(mb)
- return
- # The mouse has not yet been set up in this application;
- # create the mouse structures now.
-
- # We create both a MouseAndKeyboard object and a MouseWatcher object
- # for the window. The MouseAndKeyboard generates mouse events and
- # mouse button/keyboard events; the MouseWatcher passes them through
- # unchanged when the mouse is not over a 2-d button, and passes
- # nothing through when the mouse *is* over a 2-d button. Therefore,
- # objects that don't want to get events when the mouse is over a
- # button, like the driveInterface, should be parented to
- # mouseWatcher, while objects that want events in all cases, like the
- # chat interface, should be parented to mak.
- self.mak = self.dataRoot.attachNewNode(MouseAndKeyboard(win, 0, 'mak'))
- self.mouseWatcherNode = MouseWatcher('mouseWatcher')
- self.mouseWatcher = self.mak.attachNewNode(self.mouseWatcherNode)
- mb = self.mouseWatcherNode.getModifierButtons()
- mb.addButton(KeyboardButton.shift())
- mb.addButton(KeyboardButton.control())
- mb.addButton(KeyboardButton.alt())
- self.mouseWatcherNode.setModifierButtons(mb)
- # Now we have the main trackball & drive interfaces.
- # useTrackball() and useDrive() switch these in and out; only
- # one is in use at a given time.
- self.trackball = self.dataUnused.attachNewNode(Trackball('trackball'))
- self.drive = self.dataUnused.attachNewNode(DriveInterface('drive'))
- self.mouse2cam = self.dataUnused.attachNewNode(Transform2SG('mouse2cam'))
- self.mouse2cam.node().setNode(self.camera.node())
- # The default is trackball mode, which is more convenient for
- # ad-hoc development in Python using ShowBase. Applications
- # can explicitly call base.useDrive() if they prefer a drive
- # interface.
- self.mouseInterface = self.trackball
- self.useTrackball()
- # A ButtonThrower to generate events from the mouse and
- # keyboard buttons as they are pressed.
- self.buttonThrower = self.mouseWatcher.attachNewNode(ButtonThrower('buttons'))
- # Specialize the events based on whether the modifier keys are
- # being held down.
- mods = ModifierButtons()
- mods.addButton(KeyboardButton.shift())
- mods.addButton(KeyboardButton.control())
- mods.addButton(KeyboardButton.alt())
- self.buttonThrower.node().setModifierButtons(mods)
- # Tell the gui system about our new mouse watcher.
- self.aspect2d.node().setMouseWatcher(self.mouseWatcherNode)
- self.mouseWatcherNode.addRegion(PGMouseWatcherBackground())
- def enableSoftwareMousePointer(self):
- """enableSoftwareMousePointer(self)
- Creates some geometry and parents it to render2d to show
- the currently-known mouse position. Useful if the mouse
- pointer is invisible for some reason.
- """
- mouseViz = render2d.attachNewNode('mouseViz')
- lilsmiley = loader.loadModel('lilsmiley')
- lilsmiley.reparentTo(mouseViz)
- # Scale the smiley face to 32x32 pixels.
- lilsmiley.setScale(32.0 / self.win.getHeight() / self.aspectRatio, 1.0, 32.0 / self.win.getHeight())
- #self.mouseWatcherNode.setGeometry(mouseViz)
-
- def getCameras(self, chanConfig):
- """
- getCameras(self, chanConfig)
- Extracts the camera(s) out of the ChanConfig record, parents
- them all to base.camera, and adds them to base.camList.
- """
- for i in range(chanConfig.getNumGroups()):
- # Create a top level camera node for this group
- camera = self.render.attachNewNode(chanConfig.getGroupNode(i))
- self.cameraList.append(camera)
- # Extract camera
- cam = camera.find('**/+Camera')
- self.camList.append(cam)
- # Enforce our expected aspect ratio, overriding whatever
- # nonsense ChanConfig put in there.
- lens = cam.node().getLens()
- lens.setAspectRatio(self.aspectRatio)
- # Update main camera variables
- if self.camera == None:
- self.camera = self.cameraList[0]
- if self.cam == None:
- self.cam = self.camList[0]
- # If you need to get a handle to the camera node itself,
- # use self.camNode.
- self.camNode = self.cam.node()
- # If you need to adjust camera parameters, like fov or
- # near/far clipping planes, use self.camLens.
- self.camLens = self.camNode.getLens()
- def getAlt(self):
- return base.mouseWatcherNode.getModifierButtons().isDown(
- KeyboardButton.alt())
- def getShift(self):
- return base.mouseWatcherNode.getModifierButtons().isDown(
- KeyboardButton.shift())
- def getControl(self):
- return base.mouseWatcherNode.getModifierButtons().isDown(
- KeyboardButton.control())
- def addAngularIntegrator(self):
- """addAngularIntegrator(self)"""
- if (self.physicsMgrAngular == 0):
- self.physicsMgrAngular = 1
- integrator = AngularEulerIntegrator()
- self.physicsMgr.attachAngularIntegrator(integrator)
- def enableParticles(self):
- """enableParticles(self)"""
- self.particleMgrEnabled = 1
- self.physicsMgrEnabled = 1
- self.taskMgr.remove('manager-update')
- self.taskMgr.add(self.updateManagers, 'manager-update')
- def disableParticles(self):
- """enableParticles(self)"""
- self.particleMgrEnabled = 0
- self.physicsMgrEnabled = 0
- self.taskMgr.remove('manager-update')
- def toggleParticles(self):
- if self.particleMgrEnabled == 0:
- self.enableParticles()
- else:
- self.disableParticles()
- def isParticleMgrEnabled(self):
- return self.particleMgrEnabled
- def isPhysicsMgrEnabled(self):
- return self.physicsMgrEnabled
- def updateManagers(self, state):
- """updateManagers(self)"""
- dt = min(globalClock.getDt(), 0.1)
- if (self.particleMgrEnabled == 1):
- self.particleMgr.doParticles(dt)
- if (self.physicsMgrEnabled == 1):
- self.physicsMgr.doPhysics(dt)
- return Task.cont
- def createStats(self):
- # You must specify a pstats-host in your configrc
- # The default is localhost
- if self.wantStats:
- PStatClient.connect()
- def addSfxManager(self, extraSfxManager):
- # keep a list of sfx manager objects to apply settings to, since there may be others
- # in addition to the one we create here
- self.sfxManagerList.append(extraSfxManager)
- newSfxManagerIsValid = (extraSfxManager!=None) and extraSfxManager.isValid()
- self.sfxManagerIsValidList.append(newSfxManagerIsValid)
- if newSfxManagerIsValid:
- extraSfxManager.setActive(self.sfxActive)
- def createBaseAudioManagers(self):
- sfxManager = AudioManager.createAudioManager()
- self.addSfxManager(sfxManager)
- self.musicManager = AudioManager.createAudioManager()
- self.musicManagerIsValid=self.musicManager!=None \
- and self.musicManager.isValid()
- if self.musicManagerIsValid:
- self.musicManager.setMutuallyExclusive(1) # ensure only 1 midi song is playing at a time
- self.musicManager.setActive(self.musicActive)
- # enableMusic/enableSoundEffects are meant to be called in response to a user request
- # so sfxActive/musicActive represent how things *should* be, regardless of App/OS/HW state
- def enableMusic(self, bEnableMusic):
- # dont setActive(1) if no audiofocus
- if self.AppHasAudioFocus and self.musicManagerIsValid:
- self.musicManager.setActive(bEnableMusic)
- self.musicActive = bEnableMusic
- if bEnableMusic:
- self.notify.debug("Enabling music")
- else:
- self.notify.debug("Disabling music")
- def SetAllSfxEnables(self, bEnabled):
- for i in range(len(self.sfxManagerList)):
- if (self.sfxManagerIsValidList[i]):
- self.sfxManagerList[i].setActive(bEnabled)
- def enableSoundEffects(self, bEnableSoundEffects):
- # dont setActive(1) if no audiofocus
- if self.AppHasAudioFocus or (bEnableSoundEffects==0):
- self.SetAllSfxEnables(bEnableSoundEffects)
- self.sfxActive=bEnableSoundEffects
- if bEnableSoundEffects:
- self.notify.debug("Enabling sound effects")
- else:
- self.notify.debug("Disabling sound effects")
- # enable/disableAllAudio allow a programmable global override-off for current audio settings.
- # they're meant to be called when app loses audio focus (switched out), so we can turn off sound
- # without affecting internal sfxActive/musicActive sound settings, so things
- # come back ok when the app is switched back to
- def disableAllAudio(self):
- self.AppHasAudioFocus = 0
- self.SetAllSfxEnables(0)
- if self.musicManagerIsValid:
- self.musicManager.setActive(0)
- self.notify.debug("Disabling audio")
- def enableAllAudio(self):
- self.AppHasAudioFocus = 1
- self.SetAllSfxEnables(self.sfxActive)
- if self.musicManagerIsValid:
- self.musicManager.setActive(self.musicActive)
- self.notify.debug("Enabling audio")
- def loadSfx(self, name):
- # should return a valid sound obj even if soundMgr is invalid
- sound = None
- if (name):
- # showbase-created sfxManager should always be at front of list
- sound=self.sfxManagerList[0].getSound(name)
- if sound == None:
- self.notify.warning("Could not load sound file %s." % name)
- return sound
- def loadMusic(self, name):
- # should return a valid sound obj even if musicMgr is invalid
- sound = None
- if (name):
- sound=self.musicManager.getSound(name)
- if sound == None:
- self.notify.warning("Could not load music file %s." % name)
- return sound
- def playSfx(self, sfx, looping = 0, interrupt = 1, volume = None, time = 0.0):
- if sfx:
- if volume != None:
- sfx.setVolume(volume)
- # dont start over if it's already playing, unless "interrupt" was specified
- if interrupt or (sfx.status() != AudioSound.PLAYING):
- sfx.setTime(time)
- sfx.setLoop(looping)
- sfx.play()
- def playMusic(self, music, looping = 0, interrupt = 1, volume = None, time = 0.0):
- if music:
- if volume != None:
- music.setVolume(volume)
- # if interrupt was set to 0, start over even if it's already playing
- if interrupt or (music.status() != AudioSound.PLAYING):
- music.setTime(time)
- music.setLoop(looping)
- music.play()
- def dataloop(self, state):
- # traverse the data graph. This reads all the control
- # inputs (from the mouse and keyboard, for instance) and also
- # directly acts upon them (for instance, to move the avatar).
- self.dgTrav.traverse(self.dataRootNode)
- return Task.cont
- def ivalloop(self, state):
- # Execute all intervals in the global ivalMgr.
- ivalMgr.step()
- return Task.cont
- def collisionloop(self, state):
- # run the collision traversal if we have a
- # CollisionTraverser set.
- if self.cTrav:
- self.cTrav.traverse(self.render)
- if self.appTrav:
- self.appTrav.traverse(self.render)
- return Task.cont
- def igloop(self, state):
- # Finally, render the frame.
- self.graphicsEngine.renderFrame()
- if self.clusterSyncFlag:
- base.graphicsEngine.syncFrame()
-
- if self.mainWinMinimized:
- # If the main window is minimized, slow down the app a bit
- # by sleeping here in igloop so we don't use all available
- # CPU needlessly.
- # Note: this isn't quite right if multiple windows are
- # open. We should base this on whether *all* windows are
- # minimized, not just the main window. But it will do for
- # now until someone complains.
- time.sleep(0.1)
- # Lerp stuff needs this event, and it must be generated in
- # C++, not in Python.
- throwNewFrame()
- return Task.cont
- def restart(self):
- self.shutdown()
- # give the igloop task a reasonably "late" priority,
- # so that it will get run after most tasks
- self.taskMgr.add(self.igloop, 'igloop', priority = 50)
- # make the collisionloop task run shortly before igloop,
- # but leave enough room for the app to insert tasks
- # between collisionloop and igloop
- self.taskMgr.add(self.collisionloop, 'collisionloop', priority = 45)
- # give the dataloop task a reasonably "early" priority,
- # so that it will get run before most tasks
- self.taskMgr.add(self.dataloop, 'dataloop', priority = -50)
- # spawn the ivalloop with a later priority, so that it will
- # run after most tasks, but before igloop.
- self.taskMgr.add(self.ivalloop, 'ivalloop', priority = 10)
- self.eventMgr.restart()
- def shutdown(self):
- self.taskMgr.remove('igloop')
- self.taskMgr.remove('collisionloop')
- self.taskMgr.remove('dataloop')
- self.taskMgr.remove('ivalloop')
- self.eventMgr.shutdown()
- def getBackgroundColor(self):
- """ Returns the current window background color. This assumes
- the window is set up to clear the color each frame (this is
- the normal setting). """
- return VBase4(self.win.getClearColor())
- def setBackgroundColor(self, *args):
- """ Sets the window background color to the indicated value.
- This assumes the window is set up to clear the color each
- frame (this is the normal setting).
- The color may be either a VBase3 or a VBase4, or a 3-component
- tuple, or the individual r, g, b parameters.
- """
- numArgs = len(args)
- if numArgs == 3 or numArgs == 4:
- color = VBase4(args[0], args[1], args[2], 1.0)
- elif numArgs == 1:
- arg = args[0]
- color = VBase4(arg[0], arg[1], arg[2], 1.0)
- else:
- raise TypeError, ('Invalid number of arguments: %d, expected 1, 3, or 4.' % numArgs)
-
- self.win.setClearColor(color)
-
- def toggleBackface(self):
- if self.backfaceCullingEnabled:
- self.backfaceCullingOff()
- else:
- self.backfaceCullingOn()
- def backfaceCullingOn(self):
- if not self.backfaceCullingEnabled:
- self.render.setTwoSided(0)
- self.backfaceCullingEnabled = 1
- def backfaceCullingOff(self):
- if self.backfaceCullingEnabled:
- self.render.setTwoSided(1)
- self.backfaceCullingEnabled = 0
- def toggleTexture(self):
- if self.textureEnabled:
- self.textureOff()
- else:
- self.textureOn()
- def textureOn(self):
- self.render.clearTexture()
- self.textureEnabled = 1
- def textureOff(self):
- self.render.setTextureOff(100)
- self.textureEnabled = 0
- def toggleWireframe(self):
- if self.wireframeEnabled:
- self.wireframeOff()
- else:
- self.wireframeOn()
- def wireframeOn(self):
- self.render.setRenderModeWireframe(100);
- self.render.setTwoSided(1);
- self.wireframeEnabled = 1
- def wireframeOff(self):
- self.render.clearRenderMode()
- render.setTwoSided(not self.backfaceCullingEnabled)
- self.wireframeEnabled = 0
- def disableMouse(self):
- """
- Temporarily disable the mouse control of the camera, either
- via the drive interface or the trackball, whichever is
- currently in use.
- """
- # We don't reparent the drive interface or the trackball;
- # whichever one was there before will remain in the data graph
- # and active. This way they won't lose button events while
- # the mouse is disabled. However, we do move the mouse2cam
- # object out of there, so we won't be updating the camera any
- # more.
- self.mouse2cam.reparentTo(self.dataUnused)
- def enableMouse(self):
- """
- Reverse the effect of a previous call to disableMouse().
- useDrive() also implicitly enables the mouse.
- """
- self.mouse2cam.reparentTo(self.mouseInterface)
- def setMouseOnNode(self, newNode):
- self.mouse2cam.node().setNode(newNode)
- def useDrive(self):
- """
- Switch mouse action to drive mode
- """
- # Get rid of the trackball
- self.mouseInterface.reparentTo(self.dataUnused)
- # Update the mouseInterface to point to the drive
- self.mouseInterface = self.drive
- self.mouseInterfaceNode = self.mouseInterface.node()
- # Hookup the drive to the camera.
- self.mouseInterface.reparentTo(self.mouseWatcher)
- self.mouse2cam.reparentTo(self.mouseInterface)
- # Set the height to a good eyeheight
- self.mouseInterfaceNode.reset()
- self.mouseInterfaceNode.setZ(4.0)
- def useTrackball(self):
- """
- Switch mouse action to trackball mode
- """
- # Get rid of the drive
- self.mouseInterface.reparentTo(self.dataUnused)
- # Update the mouseInterface to point to the trackball
- self.mouseInterface = self.trackball
- self.mouseInterfaceNode = self.mouseInterface.node()
- # Hookup the trackball to the camera.
- self.mouseInterface.reparentTo(self.mouseWatcher)
- self.mouse2cam.reparentTo(self.mouseInterface)
- def oobe(self):
- """
- Enable a special "out-of-body experience" mouse-interface
- mode. This can be used when a "god" camera is needed; it
- moves the camera node out from under its normal node and sets
- the world up in trackball state. Button events are still sent
- to the normal mouse action node (e.g. the DriveInterface), and
- mouse events, if needed, may be sent to the normal node by
- holding down the Control key.
- This is different than useTrackball(), which simply changes
- the existing mouse action to a trackball interface. In fact,
- OOBE mode doesn't care whether useDrive() or useTrackball() is
- in effect; it just temporarily layers a new trackball
- interface on top of whatever the basic interface is. You can
- even switch between useDrive() and useTrackball() while OOBE
- mode is in effect.
- This is a toggle; the second time this function is called, it
- disables the mode.
- """
- # If oobeMode was never set, set it to false and create the
- # structures we need to implement OOBE.
- try:
- self.oobeMode
- except:
- self.oobeMode = 0
- self.oobeCamera = self.hidden.attachNewNode('oobeCamera')
- self.oobeCameraTrackball = self.oobeCamera.attachNewNode('oobeCameraTrackball')
- self.oobeLens = PerspectiveLens()
- self.oobeLens.setAspectRatio(self.aspectRatio)
- self.oobeLens.setNearFar(0.1, 10000.0)
- self.oobeLens.setFov(52.0)
- self.oobeTrackball = self.dataUnused.attachNewNode(Trackball('oobeTrackball'), 1)
- self.oobe2cam = self.oobeTrackball.attachNewNode(Transform2SG('oobe2cam'))
- self.oobe2cam.node().setNode(self.oobeCameraTrackball.node())
- self.oobeVis = loader.loadModelOnce('models/misc/camera')
- if self.oobeVis:
- self.oobeVis.node().setFinal(1)
- self.oobeCullFrustum = None
- self.oobeCullFrustumVis = None
- if self.oobeMode:
- # Disable OOBE mode.
- if self.oobeCullFrustum != None:
- # First, disable OOBE cull mode.
- self.oobeCull()
-
- if self.oobeVis:
- self.oobeVis.reparentTo(self.hidden)
- # Restore the mouse interface node.
- #self.mouseInterface.reparentTo(self.mouseWatcher)
- self.oobeTrackball.reparentTo(self.dataUnused)
-
- self.cam.reparentTo(self.camera)
- self.camNode.setLens(self.camLens)
- self.oobeCamera.reparentTo(self.hidden)
- self.oobeMode = 0
- else:
- # Make oobeCamera be a sibling of wherever camera is now.
- cameraParent = self.camera.getParent()
- self.oobeCamera.reparentTo(cameraParent)
- self.oobeCamera.clearMat()
- # Move aside the current mouse interface node and put the
- # oobeTrackball in its place.
- #self.mouseInterface.reparentTo(self.dataUnused)
- self.oobeTrackball.reparentTo(self.mouseWatcher)
- # Set our initial OOB position to be just behind the camera.
- mat = Mat4.translateMat(0, -10, 3) * self.camera.getMat(cameraParent)
- mat.invertInPlace()
- self.oobeTrackball.node().setMat(mat)
- self.cam.reparentTo(self.oobeCameraTrackball)
- self.camNode.setLens(self.oobeLens)
- if self.oobeVis:
- self.oobeVis.reparentTo(self.camera)
- self.oobeMode = 1
- def oobeCull(self):
- """
- While in OOBE mode (see above), cull the viewing frustum as if
- it were still attached to our original camera. This allows us
- to visualize the effectiveness of our bounding volumes.
- """
- # First, make sure OOBE mode is enabled.
- try:
- if not self.oobeMode:
- self.oobe()
- except:
- self.oobe()
- if self.oobeCullFrustum == None:
- # Enable OOBE culling.
- pnode = LensNode('oobeCull')
- pnode.setLens(self.camLens)
- self.oobeCullFrustum = self.camera.attachNewNode(pnode)
- # Create a visible representation of the frustum.
- geom = self.camLens.makeGeometry()
- if geom != None:
- gn = GeomNode('frustum')
- gn.addGeom(geom)
- self.oobeCullFrustumVis = self.oobeVis.attachNewNode(gn)
- # Assign each DisplayRegion shared by the camera to use
- # this cull frustum.
- numDisplayRegions = self.camNode.getNumDisplayRegions()
- for d in range(0, numDisplayRegions):
- dr = self.camNode.getDisplayRegion(d)
- dr.setCullFrustum(pnode)
- else:
- # Disable OOBE culling.
- # Assign each DisplayRegion shared by the camera to use
- # the default cull frustum, the camera itself.
- numDisplayRegions = self.camNode.getNumDisplayRegions()
- for d in range(0, numDisplayRegions):
- dr = self.camNode.getDisplayRegion(d)
- dr.setCullFrustum(self.camNode)
- self.oobeCullFrustum.removeNode()
- self.oobeCullFrustum = None
- if self.oobeCullFrustumVis != None:
- self.oobeCullFrustumVis.removeNode()
- self.oobeCullFrustumVis = None
- def screenshot(self, namePrefix='screenshot'):
- # Get the current date and time to uniquify the image (down to the second)
- date = time.ctime(time.time())
- # Get the current frame count to uniquify it even more
- frameCount = globalClock.getFrameCount()
- # Replace spaces with dashes because unix does not like spaces in the filename
- date = date.replace(' ', '-')
- date = date.replace(':', '-')
- imageName = ('%s-%s-%d.%s' % (namePrefix, date, frameCount, self.screenshotExtension))
- self.notify.info("Taking screenshot: " + imageName)
- takeSnapshot(self.win, imageName)
- # Announce to anybody that a screenshot has been taken
- messenger.send('screenshot')
- def movie(self, namePrefix = 'movie', duration = 1.0, fps = 30,
- format = 'rgb', sd = 4):
- """
- movie(namePrefix = 'movie', duration=1.0, fps=30, format='rgb', sd=4)
- Spawn a task to capture a movie using the takeSnapshot function.
- - namePrefix will be used to form output file names (can include
- path information (e.g. 'I:/beta/frames/myMovie')
- - duration is the length of the movie in seconds
- - fps is the frame rate of the resulting movie
- - format specifies output file format (e.g. rgb, bmp)
- - sd specifies number of significant digits for frame count in the
- output file name (e.g. if sd = 4, movie_0001.rgb)
- """
- globalClock.setMode(ClockObject.MNonRealTime)
- globalClock.setDt(1.0/float(fps))
- t = taskMgr.add(self._movieTask, namePrefix + '_task')
- t.endT = globalClock.getFrameTime() + duration
- t.frameIndex = 1
- t.outputString = namePrefix + '_%0' + `sd` + 'd.' + format
- t.uponDeath = lambda state: globalClock.setMode(ClockObject.MNormal)
- def _movieTask(self, state):
- currT = globalClock.getFrameTime()
- if currT >= state.endT:
- return Task.done
- else:
- frameName = state.outputString % state.frameIndex
- self.notify.info("Capturing frame: " + frameName)
- takeSnapshot(self.win, frameName )
- state.frameIndex += 1
- return Task.cont
- def __windowEvent(self, win):
- properties = win.getProperties()
- if win == self.win:
- if not properties.getOpen():
- # If the user closes the main window, we should exit.
- self.notify.info("User closed main window.")
- self.userExit()
- if properties.getMinimized() and not self.mainWinMinimized:
- # If the main window is minimized, throw an event to
- # stop the music.
- self.mainWinMinimized = 1
- messenger.send('PandaPaused')
- elif not properties.getMinimized() and self.mainWinMinimized:
- # If the main window is restored, throw an event to
- # restart the music.
- self.mainWinMinimized = 0
- messenger.send('PandaRestarted')
- def userExit(self):
- # The user has requested we exit the program. Deal with this.
- if self.exitFunc:
- self.exitFunc()
- self.notify.info("Exiting ShowBase.")
- sys.exit()
- def run(self):
- self.taskMgr.run()
|