Browse Source

Adding support for multi-PC or multi-piping.

khillesl 24 years ago
parent
commit
8b5ea4acc5

+ 151 - 0
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
+        
+
+
+
+
+

+ 52 - 0
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)
+    
+
+

+ 114 - 0
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
+        
+
+
+
+
+
+
+
+

+ 241 - 0
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
+
+
+    
+
+
+
+
+
+
+
+

+ 1 - 0
direct/src/configfiles/direct.pth

@@ -1,6 +1,7 @@
 lib
 lib/py
 src/actor
+src/cluster
 src/directtools
 src/directdevices
 src/directnotify

+ 17 - 5
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)
+        
+    
     
 

+ 13 - 6
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)

+ 62 - 11
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()=='<noname>':
                     camera.setName('Camera%d' % cameraIndex)
+                group = base.groupList[cameraIndex]
                 self.displayRegionList.append(
                     DisplayRegionContext(base.win,
-                                         camera))
+                                         camera,group))
                 if camera.getName()!='<noname>' 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

+ 5 - 3
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))