diff --git a/direct/src/cluster/ClusterClient.py b/direct/src/cluster/ClusterClient.py new file mode 100644 index 0000000000..825ca210a4 --- /dev/null +++ b/direct/src/cluster/ClusterClient.py @@ -0,0 +1,151 @@ +"""ClusterClient: Master for mutlipiping or PC clusters. """ + +from PandaModules import * +from TaskManagerGlobal import * +from ShowBaseGlobal import * +import Task +import DirectNotifyGlobal +import Datagram +import DirectObject +from ClusterMsgs import * +import time + +class ClusterConfigItem: + def __init__(self, serverFunction, serverName, pos, hpr, port): + self.serverName = serverName + self.serverFunction = serverFunction + self.xyz = pos + self.hpr = hpr + self.port = port + +class DisplayConnection: + def __init__(self,qcm,serverName,port,msgHandler): + self.msgHandler = msgHandler + gameServerTimeoutMs = base.config.GetInt("game-server-timeout-ms", + 20000) + # A big old 20 second timeout. + self.tcpConn = qcm.openTCPClientConnection( + serverName, port, gameServerTimeoutMs) + # Test for bad connection + if self.tcpConn == None: + return None + else: + self.tcpConn.setNoDelay(1) + self.qcr=QueuedConnectionReader(qcm, 0) + self.qcr.addConnection(self.tcpConn) + self.cw=ConnectionWriter(qcm, 0) + + def sendCamOffset(self,xyz,hpr): + ClusterManager.notify.debug("send cam offset...") + ClusterManager.notify.debug( ("packet %d xyz,hpr=%f %f %f %f %f %f" % + (self.msgHandler.packetNumber,xyz[0],xyz[1],xyz[2], + hpr[0],hpr[1],hpr[2])) ) + datagram = self.msgHandler.makeCamOffsetDatagram(xyz, hpr) + self.cw.send(datagram, self.tcpConn) + + def sendMoveCam(self,xyz,hpr): + ClusterManager.notify.debug("send cam move...") + ClusterManager.notify.debug( ("packet %d xyz,hpr=%f %f %f %f %f %f" % + (self.msgHandler.packetNumber,xyz[0],xyz[1],xyz[2], + hpr[0],hpr[1],hpr[2])) ) + datagram = self.msgHandler.makeMoveCamDatagram(xyz, hpr) + self.cw.send(datagram, self.tcpConn) + + # the following should only be called by a synchronized cluster manger + def getSwapReady(self): + datagram = self.msgHandler.blockingRead(self.qcr) + (type,dgi) = self.msgHandler.readHeader(datagram) + if type != CLUSTER_SWAP_READY: + self.notify.warning( ('was expecting SWAP_READY, got %d' % type) ) + + # the following should only be called by a synchronized cluster manger + def sendSwapNow(self): + ClusterManager.notify.debug( ("dispaly connect send swap now, packet %d" % self.msgHandler.packetNumber)) + datagram = self.msgHandler.makeSwapNowDatagram() + self.cw.send(datagram, self.tcpConn) + +class ClusterManager(DirectObject.DirectObject): + notify = DirectNotifyGlobal.directNotify.newCategory("ClusterClient") + MGR_NUM = 1000000 + + def __init__(self, dispConfigs): + self.qcm=QueuedConnectionManager() + self.dispList = [] + self.msgHandler = MsgHandler(ClusterManager.MGR_NUM,self.notify) + for dispConfig in dispConfigs: + thisDisp = DisplayConnection(self.qcm,dispConfig.serverName, + dispConfig.port,self.msgHandler) + if thisDisp == None: + self.notify.error( ('Could not open %s on %s port %d' % + (dispConfig.serverFunction, + dispConfig.serverName, + dispConfig.port)) ) + else: + self.dispList.append(thisDisp) + self.dispList[len(self.dispList)-1].sendCamOffset( + dispConfig.xyz,dispConfig.hpr) + self.startMoveCamTask() + + def moveCamera(self, xyz, hpr): + for disp in self.dispList: + disp.sendMoveCam(xyz,hpr) + + def startMoveCamTask(self): + task = Task.Task(self.moveCameraTask,49) + taskMgr.spawnTaskNamed(task, "moveCamTask") + return None + + def moveCameraTask(self,task): + self.moveCamera( + base.camera.getPos(render), + base.camera.getHpr(render)) + return Task.cont + + +class ClusterManagerSync(ClusterManager): + + def __init__(self, dispConfigs): + ClusterManager.__init__(self, dispConfigs) + #I probably don't need this + self.waitForSwap = 0 + self.ready = 0 + self.startSwapCoordinatorTask() + + def startSwapCoordinatorTask(self): + task = Task.Task(self.swapCoordinator,51) + taskMgr.spawnTaskNamed(task, "clientSwapCoordinator") + return None + + def swapCoordinator(self,task): + self.ready = 1 + if self.waitForSwap: + self.waitForSwap=0 + self.notify.debug("START get swaps----------------------------------") + localClock = ClockObject() + t1 = localClock.getRealTime() + for disp in self.dispList: + disp.getSwapReady() + self.notify.debug("----------------START swap now--------------------") + t2 = localClock.getRealTime() + for disp in self.dispList: + disp.sendSwapNow() + self.notify.debug("------------------------------START swap----------") + t3 = localClock.getRealTime() + base.win.swap() + t4 = localClock.getRealTime() + self.notify.debug("------------------------------------------END swap") + self.notify.debug( ("times=%f %f %f %f" % (t1,t2,t3,t4)) ) + self.notify.debug( ("deltas=%f %f %f" % (t2-t1,t3-t2,t4-t3)) ) + return Task.cont + + def moveCamera(self,xyz,hpr): + if self.ready: + self.notify.debug('moving synced camera') + ClusterManager.moveCamera(self,xyz,hpr) + self.waitForSwap=1 + + + + + + diff --git a/direct/src/cluster/ClusterConfig.py b/direct/src/cluster/ClusterConfig.py new file mode 100644 index 0000000000..c3cccbfd4e --- /dev/null +++ b/direct/src/cluster/ClusterConfig.py @@ -0,0 +1,52 @@ +from PandaObject import * +from ClusterClient import * +import string + +# this is an array of offsets for the various servers. For example, +# mono-modelcave-pipe0 has one server with both a pos and hpr of 0. +# For mono-modelcave-pipe0, I decided to set the offsets in the +# actual configuration for the display. + +ClientConfigs = {'mono-modelcave-pipe0': [ [Vec3(0,0,0),Vec3(0,0,0)] ] + +# The following is a fake configuration to show an example with two servers. +# 'two-server' : [ [Vec3(0,0,0),Vec3(-60,0,0)], +# [Vec3(0,0,0),Vec3(60,0,0) ] ] + +def createCluster(): + # setup camera offsets based on chanconfig + # This should ideally be independent of chan-config. In other + # words, there might be some other configuration for clustering, + # from which the number and offset of the servers' cameras are set. + chanConfig = base.config.GetString('chan-config', 'single') + + if base.config.GetBool('display-client'): + if not ClientConfigs.has_key(chanConfig): + base.notify.warning('Display Client flag set, but %s is not a display client configuration.' % chanConfig) + return None + thisClientConfig = ClientConfigs[chanConfig] + displayConfigs = [] + for i in range(len(thisClientConfig)): + configString = 'display%d' % i + serverString = base.config.GetString(configString, '') + if serverString == '': + base.notify.warning('%s should be defined in Configrc to use %s as display client.' % [configString,chanConfig]) + base.notify.warning('%s will not be used.' % configString) + else: + serverInfo = string.split(serverString) + if len(serverInfo) > 1: + port = int(serverInfo[1]) + else: + port = CLUSTER_PORT + displayConfigs.append(ClusterConfigItem(configString, + serverInfo[0], thisClientConfig[i][0], thisClientConfig[i][1],port)) + + synced = base.config.GetBool('sync-display',0) + if synced: + base.win.setSync(1) + return ClusterManagerSync(displayConfigs) + else: + return ClusterManager(displayConfigs) + + + diff --git a/direct/src/cluster/ClusterMsgs.py b/direct/src/cluster/ClusterMsgs.py new file mode 100644 index 0000000000..67615aca1b --- /dev/null +++ b/direct/src/cluster/ClusterMsgs.py @@ -0,0 +1,114 @@ +"""ClusterMsgs module: Message types for Cluster rendering""" + +CLUSTER_NOTHING = -1 +CLUSTER_CAM_OFFSET = 1 +CLUSTER_POS_UPDATE = 2 +CLUSTER_SWAP_READY = 3 +CLUSTER_SWAP_NOW = 4 + +#Port number for cluster rendering +CLUSTER_PORT = 1970 + +from ShowBaseGlobal import * +from PandaModules import * +from TaskManagerGlobal import * +import Task +import DirectNotifyGlobal +import Datagram +import time + +class MsgHandler: + """MsgHandler: wrapper for PC clusters/multi-piping networking""" + def __init__(self,packetStart, notify): + """packetStart can be used to distinguish which MsgHandler sends a + given packet.""" + self.packetNumber = packetStart + self.notify = notify + + def nonBlockingRead(self,qcr): + availGetVal = qcr.dataAvailable() + if availGetVal: + datagram = NetDatagram() + readRetVal = qcr.getData(datagram) + if readRetVal: + dgi = DatagramIterator(datagram) + number = dgi.getUint32() + type = dgi.getUint8() + self.notify.debug( ("Packet %d type %d recieved" % (number,type)) ) + else: + self.notify.warning("getData returned false") + else: + type = CLUSTER_NOTHING + dgi = None + + return (type,dgi) + + def readHeader(self,datagram): + dgi = DatagramIterator(datagram) + number = dgi.getUint32() + type = dgi.getUint8() + self.notify.info( ("Packet %d type %d recieved" % (number,type)) ) + return (type,dgi) + + def blockingRead(self,qcr): + availGetVal = 0 + while not availGetVal: + availGetVal = qcr.dataAvailable() + if not availGetVal: + time.sleep(0.002) + type = CLUSTER_NOTHING + datagram = NetDatagram() + readRetVal = qcr.getData(datagram) + if not readRetVal: + self.notify.warning("getData returned false") + + return datagram + + def makeCamOffsetDatagram(self,xyz,hpr): + datagram = Datagram.Datagram() + datagram.addUint32(self.packetNumber) + self.packetNumber = self.packetNumber + 1 + datagram.addUint8(CLUSTER_CAM_OFFSET) + datagram.addFloat32(xyz[0]) + datagram.addFloat32(xyz[1]) + datagram.addFloat32(xyz[2]) + datagram.addFloat32(hpr[0]) + datagram.addFloat32(hpr[1]) + datagram.addFloat32(hpr[2]) + return datagram + + def makeMoveCamDatagram(self,xyz,hpr): + datagram = Datagram.Datagram() + datagram.addUint32(self.packetNumber) + self.packetNumber = self.packetNumber + 1 + datagram.addUint8(CLUSTER_POS_UPDATE) + datagram.addFloat32(xyz[0]) + datagram.addFloat32(xyz[1]) + datagram.addFloat32(xyz[2]) + datagram.addFloat32(hpr[0]) + datagram.addFloat32(hpr[1]) + datagram.addFloat32(hpr[2]) + return datagram + + def makeSwapNowDatagram(self): + datagram = Datagram.Datagram() + datagram.addUint32(self.packetNumber) + self.packetNumber = self.packetNumber + 1 + datagram.addUint8(CLUSTER_SWAP_NOW) + return datagram + + def makeSwapReadyDatagram(self): + datagram = Datagram.Datagram() + datagram.addUint32(self.packetNumber) + self.packetNumber = self.packetNumber + 1 + datagram.addUint8(CLUSTER_SWAP_READY) + return datagram + + + + + + + + + diff --git a/direct/src/cluster/ClusterServer.py b/direct/src/cluster/ClusterServer.py new file mode 100644 index 0000000000..c9095d26e4 --- /dev/null +++ b/direct/src/cluster/ClusterServer.py @@ -0,0 +1,241 @@ +"""ServerRepository module: contains the ServerRepository class""" + +from ShowBaseGlobal import * +from ClusterMsgs import * +import DirectObject +import Datagram +#import DatagramIterator +#import NetDatagram +import __builtin__ +import time +from PandaModules import * +from TaskManagerGlobal import * +from MsgTypes import * +import Task +import DirectNotifyGlobal + +# Cam offset handling is a little sloppy. The problem is that there is a +# single arc used for both movement of the camera, and the offset of the +# group. + +# Also, I'm not sure multiple camera-group configurations are working for the +# cluster system. + +class ClusterServer(DirectObject.DirectObject): + notify = DirectNotifyGlobal.directNotify.newCategory("ClusterServer") + MSG_NUM = 2000000 + + def __init__(self,cameraGroup,camera): + self.qcm = QueuedConnectionManager() + self.qcl = QueuedConnectionListener(self.qcm, 0) + self.qcr = QueuedConnectionReader(self.qcm, 0) + self.cw = ConnectionWriter(self.qcm,0) + port = base.config.GetInt("cluster-server-port",CLUSTER_PORT) + self.tcpRendezvous = self.qcm.openTCPServerRendezvous(port, 1) + print self.tcpRendezvous + self.cameraGroup = cameraGroup + self.camera = camera + self.qcl.addConnection(self.tcpRendezvous) + self.msgHandler = MsgHandler(ClusterServer.MSG_NUM,self.notify) + self.startListenerPollTask() + self.startReaderPollTask() + self.posOffset = Vec3(0,0,0) + self.hprOffset = Vec3(0,0,0) + return None + + def startListenerPollTask(self): + task = Task.Task(self.listenerPoll) + taskMgr.spawnTaskNamed(task, "serverListenerPollTask") + return None + + def listenerPoll(self, task): + if self.qcl.newConnectionAvailable(): + print "New connection is available" + rendezvous = PointerToConnection() + netAddress = NetAddress() + newConnection = PointerToConnection() + retVal = self.qcl.getNewConnection(rendezvous, netAddress, + newConnection) + if retVal: + # Crazy dereferencing + newConnection=newConnection.p() + self.qcr.addConnection(newConnection) + print "Got a connection!" + self.lastConnection = newConnection + else: + self.notify.warning( + "getNewConnection returned false") + return Task.cont + + def startReaderPollTask(self): + task = Task.Task(self.readerPollUntilEmpty,-10) + taskMgr.spawnTaskNamed(task, "serverReaderPollTask") + return None + + def readerPollUntilEmpty(self, task): + while self.readerPollOnce(): + pass + return Task.cont + + def readerPollOnce(self): + availGetVal = self.qcr.dataAvailable() + if availGetVal: + datagram = NetDatagram() + readRetVal = self.qcr.getData(datagram) + if readRetVal: + self.handleDatagram(datagram) + else: + self.notify.warning("getData returned false") + return availGetVal + + def handleCamOffset(self,dgi): + x=dgi.getFloat32() + y=dgi.getFloat32() + z=dgi.getFloat32() + h=dgi.getFloat32() + p=dgi.getFloat32() + r=dgi.getFloat32() + self.notify.debug((' new offset=%f %f %f %f %f %f' % + (x,y,z,h,p,r))) + self.posOffset = Vec3(x,y,z) + self.hprOffset = Vec3(h,p,r) + + def handleCamMovement(self,dgi): + x=dgi.getFloat32() + y=dgi.getFloat32() + z=dgi.getFloat32() + h=dgi.getFloat32() + p=dgi.getFloat32() + r=dgi.getFloat32() + self.notify.debug((' new position=%f %f %f %f %f %f' % + (x,y,z,h,p,r))) + finalX = x + self.posOffset[0] + finalY = y + self.posOffset[1] + finalZ = z + self.posOffset[2] + finalH = h + self.hprOffset[0] + finalP = p + self.hprOffset[1] + finalR = r + self.hprOffset[2] + self.cameraGroup.setPosHpr(render,finalX,finalY,finalZ, + finalH,finalP,finalR) + + def handleDatagram(self, datagram): + (type, dgi) = msgHandler.nonBlockingRead(self.qcr) + if type==CLUSTER_CAM_OFFSET: + self.handleCamOffset(dgi) + elif type==CLUSTER_POS_UPDATE: + self.handleCamMovement(dgi) + elif type==CLUSTER_SWAP_READY: + pass + elif type==CLUSTER_SWAP_NOW: + pass + else: + self.notify.warning("recieved unknown packet") + return type + +class ClusterServerSync(ClusterServer): + + def __init__(self,cameraGroup,camera): + self.notify.info('starting ClusterServerSync') + self.startReading = 0 + self.posRecieved = 0 + ClusterServer.__init__(self,cameraGroup,camera) + self.startSwapCoordinator() + return None + + def startListenerPollTask(self): + task = Task.Task(self.listenerPoll,-2) + taskMgr.spawnTaskNamed(task, "serverListenerPollTask") + return None + + def listenerPoll(self, task): + if self.qcl.newConnectionAvailable(): + print "New connection is available" + rendezvous = PointerToConnection() + netAddress = NetAddress() + newConnection = PointerToConnection() + retVal = self.qcl.getNewConnection(rendezvous, netAddress, + newConnection) + if retVal: + # Crazy dereferencing + newConnection=newConnection.p() + self.qcr.addConnection(newConnection) + print "Got a connection!" + self.lastConnection = newConnection + datagram = self.msgHandler.blockingRead(self.qcr) + (type,dgi) = self.msgHandler.readHeader(datagram) + if type==CLUSTER_CAM_OFFSET: + self.handleCamOffset(dgi) + else: + self.notify.warning("Wanted cam offset, got something else") + self.startReading = 1 + # now that we have the offset read, can start reading + else: + self.notify.warning("getNewConnection returned false") + return Task.cont + + def startReaderPollTask(self): + task = Task.Task(self.readPos,-1) + taskMgr.spawnTaskNamed(task, "serverReadPosTask") + return None + + def readPos(self, task): + if self.startReading and self.qcr.isConnectionOk(self.lastConnection): + datagram = self.msgHandler.blockingRead(self.qcr) + (type,dgi) = self.msgHandler.readHeader(datagram) + if type == CLUSTER_POS_UPDATE: + self.posRecieved = 1 + self.handleCamMovement(dgi) + elif type == CLUSTER_CAM_OFFSET: + self.handleCamOffset(dgi) + else: + self.notify.warning('expected pos or orientation, instead got %d' % type) + else: + self.startReading = 0 # keep this 0 as long as connection not ok + + return Task.cont + + def sendSwapReady(self): + self.notify.debug( ('send swap ready packet %d' % + self.msgHandler.packetNumber ) ) + datagram = self.msgHandler.makeSwapReadyDatagram() + self.cw.send(datagram, self.lastConnection) + + def startSwapCoordinator(self): + task = Task.Task(self.swapCoordinatorTask, 51) + taskMgr.spawnTaskNamed(task, "serverSwapCoordinator") + return None + + def swapCoordinatorTask(self, task): + if self.posRecieved: + self.posRecieved = 0 + localClock = ClockObject() +# print "START send-------------------------------" + t1 = localClock.getRealTime() + self.sendSwapReady() +# print "-----------START read--------------------" + t2 = localClock.getRealTime() + datagram = self.msgHandler.blockingRead(self.qcr) + (type,dgi) = self.msgHandler.readHeader(datagram) + if type == CLUSTER_SWAP_NOW: + self.notify.debug('swapping') +# print "---------------------START SWAP----------" + t3 = localClock.getRealTime() + base.win.swap() + t4 = localClock.getRealTime() +# print "---------------------------------END SWAP" +# print "times=",t1,t2,t3,t4 +# print "deltas=",t2-t1,t3-t2,t4-t3 + else: + self.notify.warning("did not get expected swap now") + return Task.cont + + + + + + + + + + + diff --git a/direct/src/configfiles/direct.pth b/direct/src/configfiles/direct.pth index 306094223c..d750125bf9 100644 --- a/direct/src/configfiles/direct.pth +++ b/direct/src/configfiles/direct.pth @@ -1,6 +1,7 @@ lib lib/py src/actor +src/cluster src/directtools src/directdevices src/directnotify diff --git a/direct/src/directtools/DirectCamConfig.py b/direct/src/directtools/DirectCamConfig.py index fdcfaee132..60959bb0eb 100644 --- a/direct/src/directtools/DirectCamConfig.py +++ b/direct/src/directtools/DirectCamConfig.py @@ -1,13 +1,25 @@ from PandaObject import * -def setCameraOffsets(camList): +def setCameraOffsets(camList,camGroup): # setup camera offsets based on chanconfig chanConfig = base.config.GetString('chan-config', 'single') if chanConfig == 'cave3': - camList[0].setHpr(camList[0], -90, 0, 0) - camList[2].setHpr(camList[2], 90, 0, 0) + camList[0].setH(camGroup[0],-60) + camList[2].setH(camGroup[0], 60) elif chanConfig == 'cave3+god': - camList[0].setHpr(camList[0], -90, 0, 0) - camList[2].setHpr(camList[2], 90, 0, 0) + camList[0].setH(camGroup[0], -60) + camList[2].setH(camGroup[0], 60) + elif chanConfig == 'mono-cave': + camList[4].setH(camGroup[0], 120) + camList[5].setH(camGroup[0], 60) + elif chanConfig == 'mono-modelcave-pipe0': + camList[0].setH(camGroup[0], 120) + camList[1].setH(camGroup[0], 60) + camList[2].setH(camGroup[0], 0) + elif chanConfig == 'mono-modelcave-pipe1': + camList[0].setH(camGroup[0], -120) + camList[1].setH(camGroup[0], -60) + + diff --git a/direct/src/directtools/DirectCameraControl.py b/direct/src/directtools/DirectCameraControl.py index 0a3c37a179..7d03633530 100644 --- a/direct/src/directtools/DirectCameraControl.py +++ b/direct/src/directtools/DirectCameraControl.py @@ -294,7 +294,9 @@ class DirectCameraControl(PandaObject): nodePath = render.findPathDownTo(node) if camera not in nodePath.getAncestry(): # Compute hit point - hitPt = direct.iRay.parentToHitPt(entry) + # KEH: use current display region ray + # hitPt = direct.iRay.parentToHitPt(entry) + hitPt = direct.drList.getCurrentDr().iRay.parentToHitPt(entry) # Move coa marker to new point self.updateCoa(hitPt, ref = self.coaMarkerRef) else: @@ -304,6 +306,7 @@ class DirectCameraControl(PandaObject): def computeCOA(self, node, hitPt, hitPtDist): coa = Point3(0) + dr = direct.drList.getCurrentDr() if self.fLockCOA: # COA is locked, use existing point # Use existing point @@ -315,8 +318,8 @@ class DirectCameraControl(PandaObject): # Set center of action coa.assign(hitPt) # Handle case of bad coa point (too close or too far) - if ((hitPtDist < (1.1 * direct.dr.near)) or - (hitPtDist > direct.dr.far)): + if ((hitPtDist < (1.1 * dr.near)) or + (hitPtDist > dr.far)): # Just use existing point coa.assign(self.coaMarker.getPos(direct.camera)) # Reset hit point count @@ -334,7 +337,7 @@ class DirectCameraControl(PandaObject): coa.set(0,dist,0) # Compute COA Dist coaDist = Vec3(coa - ZERO_POINT).length() - if coaDist < (1.1 * direct.dr.near): + if coaDist < (1.1 * dr.near): coa.set(0,100,0) coaDist = 100 # Update coa and marker @@ -346,7 +349,9 @@ class DirectCameraControl(PandaObject): coaDist = Vec3(self.coa - ZERO_POINT).length() # Place the marker in render space if ref == None: - ref = base.cam + # KEH: use the current display region + # ref = base.cam + ref = direct.drList.getCurrentDr().cam self.coaMarker.setPos(ref, self.coa) # Resize it self.updateCoaMarkerSize(coaDist) @@ -360,7 +365,9 @@ class DirectCameraControl(PandaObject): def updateCoaMarkerSize(self, coaDist = None): if not coaDist: coaDist = Vec3(self.coaMarker.getPos( direct.camera )).length() - sf = COA_MARKER_SF * coaDist * math.tan(deg2Rad(direct.dr.fovV)) + # KEH: use current display region for fov + # sf = COA_MARKER_SF * coaDist * math.tan(deg2Rad(direct.dr.fovV)) + sf = COA_MARKER_SF * coaDist * math.tan(deg2Rad(direct.drList.getCurrentDr().fovV)) if sf == 0.0: sf = 0.1 self.coaMarker.setScale(sf) diff --git a/direct/src/directtools/DirectSession.py b/direct/src/directtools/DirectSession.py index dd70015875..4c7d78171b 100644 --- a/direct/src/directtools/DirectSession.py +++ b/direct/src/directtools/DirectSession.py @@ -8,6 +8,8 @@ from DirectGeometry import * from DirectLights import * from DirectSessionPanel import * from DirectCamConfig import * +from ClusterServer import * +from ClusterConfig import * from tkSimpleDialog import askstring import Placer import EntryScale @@ -31,6 +33,9 @@ class DirectSession(PandaObject): self.font = Loader.Loader.loadFont(loader, "models/fonts/Comic", priority = 100) + self.iAmAClient = base.config.GetBool("display-client",0) + self.clusterManager = createCluster() + self.iAmAServer = base.config.GetBool("display-server",0) self.fEnabled = 0 self.drList = DisplayRegionList() self.iRayList = map(lambda x: x.iRay, self.drList) @@ -150,6 +155,15 @@ class DirectSession(PandaObject): from TkGlobal import * self.panel = DirectSessionPanel(parent = tkroot) + if self.iAmAServer: + if base.config.GetBool('sync-display',0): + self.serverManager = ClusterServerSync(base.cameraList[0], + self.drList[0].cam) + base.win.setSync(1) + else: + self.serverManager = ClusterServer(base.cameraList[0], + self.drList[0].cam) + def enable(self): # Make sure old tasks are shut down self.disable() @@ -642,20 +656,26 @@ class DisplayRegionList(PandaObject): i = 0 for cameraGroup in base.cameraList: + # This is following the old way of setting up + # display regions. A display region is set up for + # each camera node in the scene graph. This was done + # so that only display regions in the scene graph are + # considered. The right way to do this is to set up + # a display region for each real display region, and then + # keep track of which are currently active (e.g. use a flag) + # processing only them. camList=cameraGroup.findAllMatches('**/+Camera') for cameraIndex in range(camList.getNumPaths()): camera = camList[cameraIndex] if camera.getName()=='': camera.setName('Camera%d' % cameraIndex) + group = base.groupList[cameraIndex] self.displayRegionList.append( DisplayRegionContext(base.win, - camera)) + camera,group)) if camera.getName()!='' or len(camera.getName())==0: self.displayRegionLookup[camera.getName()]=i i = i + 1 - for cameraIndex in range(len(base.groupList)): - self.displayRegionList[cameraIndex].setGroup( - base.groupList[cameraIndex]) self.accept("CamChange",self.camUpdate) self.accept("DIRECT_mouse1",self.mouseUpdate) self.accept("DIRECT_mouse2",self.mouseUpdate) @@ -668,7 +688,7 @@ class DisplayRegionList(PandaObject): cameraList = [] for dr in self.displayRegionList: cameraList.append(dr.cam) - setCameraOffsets(cameraList) + setCameraOffsets(cameraList,base.cameraList) def __getitem__(self, index): return self.displayRegionList[index] @@ -693,11 +713,17 @@ class DisplayRegionList(PandaObject): def setFov(self, hfov, vfov): for dr in self.displayRegionList: - dr.camNode.setFov(hfov, vfov) + if dr.isSideways: + dr.camNode.setFov(vfov, hfov) + else: + dr.camNode.setFov(hfov, vfov) def setHfov(self, fov): for dr in self.displayRegionList: - dr.camNode.setHfov(fov) + if dr.isSideways: + dr.camNode.setVfov(fov) + else: + dr.camNode.setHfov(fov) def setVfov(self, fov): for dr in self.displayRegionList: @@ -742,18 +768,24 @@ class DisplayRegionList(PandaObject): def contextTask(self, state): # Window Data self.mouseUpdate() + # hack to test movement return Task.cont - class DisplayRegionContext: - def __init__(self, win, cam): + def __init__(self, win, cam, group): self.win = win self.cam = cam self.camNode = self.cam.getNode(0) + self.group = group self.iRay = SelectionRay(self.cam) self.nearVec = Vec3(0) self.mouseX = 0.0 self.mouseY = 0.0 + # A Camera node can have more than one display region + # associated with it. Here I assume that there is only + # one display region per camera, since we are defining a + # display region on a per-camera basis. See note in + # DisplayRegionList.__init__() numDrs = self.camNode.getNumDrs() self.dr = self.camNode.getDr(0) left = self.dr.getLeft() @@ -764,14 +796,33 @@ class DisplayRegionContext: self.originY = top+bottom-1 self.scaleX = 1.0/(right-left) self.scaleY = 1.0/(top-bottom) + self.setOrientation() self.camUpdate() def __getitem__(self,key): return self.__dict__[key] - def setGroup(self, groupIndex): - self.groupIndex = groupIndex + def setOrientation(self): + hpr = self.cam.getHpr(base.camList[self.group]) + if hpr[2] < 135 and hpr[2]>45 or hpr[2]>225 and hpr[2]<315: + self.isSideways = 1 + elif hpr[2] > -135 and hpr[2] < -45 and hpr[2] < -225 and hpr[2] > -315: + self.isSideways = 1 + else: + self.isSideways = 0 + # The following take into consideration sideways displays + def getHfov(self): + if self.isSideways: + return self.camNode.getVfov() + else: + return self.camNode.getHfov() + + def getVfov(self): + if self.isSideways: + return self.camNode.getHfov() + else: + return self.camNode.getVfov() def camUpdate(self): # Window Data diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index f5322d5872..51d73ef3a1 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -67,9 +67,6 @@ class ShowBase: self.render.setColorOff() self.hidden = NodePath(NamedNode('hidden')) - # This will be the list of cameras, one per display region - # For now, we only have one display region, so just create the - # default camera self.dataRoot = NodePath(NamedNode('dataRoot'), DataRelation.getClassType()) # Cache the node so we do not ask for it every frame @@ -83,10 +80,15 @@ class ShowBase: self.oldexitfunc = getattr(sys, 'exitfunc', None) sys.exitfunc = self.exitfunc + # cameraList is a list of camera group nodes. There may + # be more than one display region/camera node beneath each + # one. self.cameraList = [] for i in range(chanConfig.getNumGroups()): self.cameraList.append(self.render.attachNewNode( chanConfig.getGroupNode(i))) + # this is how we know which display region cameras belong to which + # camera group. display region i belongs to group self.groupList[i] self.groupList = [] for i in range(chanConfig.getNumDrs()): self.groupList.append(chanConfig.getGroupMembership(i))