Bladeren bron

added support for static grid objects in setLocation of DistributedNode

Samir Naik 21 jaren geleden
bovenliggende
commit
877a2aa548

+ 40 - 0
direct/src/distributed/CartesianGridBase.py

@@ -0,0 +1,40 @@
+
+# Utility functions that are useful to both AI and client CartesianGrid code
+
+class CartesianGridBase:
+    def isValidZone(self, zoneId):
+        if ((zoneId < self.startingZone) or
+            (zoneId > self.startingZone + self.gridSize * self.gridSize - 1)):
+            return 0
+        return 1
+    
+    def getZoneFromXYZ(self, pos):
+        # NOTE: pos should be relative to our own grid origin
+        # Convert a 3d position to a zone
+        dx = self.cellWidth * self.gridSize * .5
+        x = pos[0] + dx
+        y = pos[1] + dx
+        col = x // self.cellWidth
+        row = y // self.cellWidth
+        # Compute which zone we are in
+        zoneId = int(self.startingZone + ((row * self.gridSize) + col))
+
+        return zoneId
+
+    def getGridSizeFromSphereRadius(self, sphereRadius, cellWidth, gridRadius):
+        # NOTE: This ensures that the grid is at least a "gridRadius" number of cells
+        # larger than the trigger sphere that loads the grid.  This gives us some
+        # room to start setting interest to the grid before we expect to see any objects
+        # on it.
+        return 2 * (sphereRadius // cellWidth)
+        
+    def getZoneCellOrigin(self, zoneId):
+        dx = self.cellWidth * self.gridSize * .5
+        zone = zoneId - self.startingZone
+        row = zone // self.gridSize
+        col = zone % self.gridSize
+        x = col * self.cellWidth - dx
+        y = row * self.cellWidth - dx
+
+        return (x,y,0)
+    

+ 275 - 0
direct/src/distributed/DistributedCartesianGrid.py

@@ -0,0 +1,275 @@
+
+from pandac.PandaModules import *
+
+from direct.distributed import DistributedNode
+from direct.task import Task
+from direct.interval.IntervalGlobal import *
+from direct.gui import DirectGuiGlobals
+
+if __debug__:
+    # For grid drawing
+    from direct.directtools.DirectGeometry import *
+    from direct.showbase.PythonUtil import randFloat
+
+import CartesianGridBase
+
+
+class DistributedCartesianGrid(DistributedNode.DistributedNode,
+                               CartesianGridBase.CartesianGridBase):
+
+    notify = directNotify.newCategory("DistributedCartesianGrid")
+
+    VisualizeGrid = config.GetBool("visualize-cartesian-grid", 0)
+
+    RuleSeparator = ":"
+
+    def __init__(self, cr):
+        DistributedNode.DistributedNode.__init__(self, cr)
+        # Let the derived classes instantiate the NodePath
+        self.visAvatar = None
+        self.visContext = None
+        # Do we have grid lines visualized?
+        if __debug__:
+            self.haveGridLines = 0
+
+    def generate(self):
+        DistributedNode.DistributedNode.generate(self)
+        
+    def disable(self):
+        DistributedNode.DistributedNode.disable(self)
+        self.stopProcessVisibility()
+        
+    def delete(self):
+        DistributedNode.DistributedNode.delete(self)
+
+    def isGridParent(self):
+        # If this distributed object is a DistributedGrid return 1.  0 by default
+        return 1
+        
+    def setParentingRules(self, style, rule):
+        assert self.notify.debug("setParentingRules: style: %s, rule: %s" % (style, rule))
+        rules = rule.split(self.RuleSeparator)
+        assert(len(rules) == 3)
+        self.startingZone = int(rules[0])
+        self.gridSize = int(rules[1])
+        self.viewingRadius = int(rules[2])
+
+        # Store the center of the grid
+        cx = self.cellWidth * self.gridSize/2.0
+        self.centerPos = Vec3(cx, cx, 0)
+
+        if __debug__:
+            if self.VisualizeGrid:
+                self.visualizeGrid()
+
+    def getCenterPos(self):
+        return self.centerPos
+
+    def startProcessVisibility(self, avatar):
+        self.stopProcessVisibility()
+        self.visAvatar = avatar
+        self.visZone = None
+        self.visContext = self.cr.addInterest(self.getDoId(), 0, self.uniqueName("visibility"))
+        taskMgr.add(self.processVisibility, self.taskName("processVisibility"))
+
+    def stopProcessVisibility(self):
+        taskMgr.remove(self.taskName("processVisibility"))
+        if self.visContext is not None:
+            self.cr.removeInterest(self.visContext)
+            self.visContext = None
+        self.visAvatar = None
+        self.visZone = None
+
+    def processVisibility(self, task):
+        pos = self.visAvatar.getPos(self)
+        # Check to make sure our x and y are positive
+        dx = self.cellWidth * self.gridSize * .5
+        x = pos[0] + dx
+        y = pos[1] + dx
+        col = x // self.cellWidth
+        row = y // self.cellWidth
+        assert self.notify.debug("processVisibility: %s: avatar pos: %s %s" % (self.doId, x,y))
+        if (row < 0) or (col < 0) or (row > self.gridSize) or (col > self.gridSize):
+            assert self.notify.debug("processVisibility: %s: not on the grid" % (self.doId))
+            # If we are viewingRadius away from this entire grid,
+            # remove interest in any current visZone we may have
+            if self.visContext:
+                self.cr.removeInterest(self.visContext)
+                self.visZone = None
+                self.visContext = None
+            return Task.cont
+        # Compute which zone we are in
+        zoneId = int(self.startingZone + ((row * self.gridSize) + col))
+        assert self.notify.debug("processVisibility: %s: row: %s col: %s zoneId: %s" %
+                                 (self.doId, row, col, zoneId))
+        if (zoneId == self.visZone):
+            assert self.notify.debug("processVisibility: %s: interest did not change" % (self.doId))
+            return Task.cont
+        else:
+            assert self.notify.debug("processVisibility: %s: new interest" % (self.doId))
+            self.visZone = zoneId
+            if not self.visContext:
+                self.visContext = self.cr.addInterest(self.getDoId(), self.visZone,
+                                                      self.uniqueName("visibility"))
+            else:
+                assert self.notify.debug("processVisibility: %s: altering interest to zoneId: %s" %
+                                         (self.doId, zoneId))
+                self.cr.alterInterest(self.visContext, self.getDoId(), self.visZone)
+                # If the visAvatar is parented to this grid, also do a setLocation
+                parentId, oldZoneId = self.visAvatar.getLocation()
+                assert self.notify.debug("processVisibility: %s: parentId: %s oldZoneId: %s" %
+                                         (self.doId, parentId, oldZoneId))
+                if parentId == self.doId:
+                    assert self.notify.debug("processVisibility: %s: changing location" % (self.doId))
+                    self.handleAvatarZoneChange(self.visAvatar, zoneId)
+            return Task.cont
+
+    def handleAvatarZoneChange(self, av, zoneId):
+        assert self.notify.debug("handleAvatarZoneChange(%s, %s)" % (av.doId, zoneId))
+        # This method can be overridden by derived classes that
+        # want to do some special management when the avatar changes
+        # zones.
+        # Make sure this is a valid zone
+        if not self.isValidZone(zoneId):
+            self.notify.warning("handleAvatarZoneChange: not a valid zone (%s)" % zoneId)
+            return
+                
+        # Set the location on the server
+        av.b_setLocation(self.doId, zoneId)
+        return
+
+
+    ##################################################
+    # Visualization Tools
+    ##################################################
+
+    if __debug__:
+
+        def initializeGridLines(self):
+            # Grid Lines
+            self.gridColor = VBase4(0.4 + randFloat(0.4),
+                                    0.4 + randFloat(0.4),
+                                    0.4 + randFloat(0.4),
+                                    1)
+            # A Dark version of the grid color
+            color = self.gridColor * 0.5
+            color.setW(1)
+            
+            self.lines = self.attachNewNode('gridLines')
+            self.minorLines = LineNodePath(self.lines)
+            self.minorLines.lineNode.setName('minorLines')
+            self.minorLines.setColor(color)
+            self.minorLines.setThickness(1)
+
+            self.majorLines = LineNodePath(self.lines)
+            self.majorLines.lineNode.setName('majorLines')
+            self.majorLines.setColor(color)
+            self.majorLines.setThickness(5)
+
+            self.centerLines = LineNodePath(self.lines)
+            self.centerLines.lineNode.setName('centerLines')
+            self.centerLines.setColor(VBase4(1,0,0,0))
+            self.centerLines.setThickness(3)
+
+            # Load up grid parts to initialize grid object
+            # Polygon used to mark grid plane
+            # self.gridBack = loader.loadModel('models/misc/gridBack')
+            # self.gridBack.reparentTo(self)
+            # self.gridBack.setColor(0.2,0.2,0.2,0.5)
+
+            self.cellLabelParent = None
+            self.markerParent = None
+            self.haveGridLines = 1
+            
+        def updateGrid(self):
+            # Update grid lines based upon current grid spacing and grid size
+            # First reset existing grid lines
+            self.minorLines.reset()
+            self.majorLines.reset()
+            self.centerLines.reset()
+            # Now redraw lines
+            numLines = self.gridSize
+            scaledSize = numLines * self.cellWidth / 2.0
+            center = self.centerLines
+            minor = self.minorLines
+            major = self.majorLines
+            cw = self.cellWidth
+            dx = cw * self.gridSize * .5
+            for i in range(numLines+1):
+                icw = i * cw - dx
+                if i == numLines/2:
+                    center.moveTo(icw, -scaledSize, 0)
+                    center.drawTo(icw, scaledSize, 0)
+                    center.moveTo(-scaledSize, icw, 0)
+                    center.drawTo(scaledSize, icw, 0)
+                else:
+                    if (i % 5) == 0:
+                        major.moveTo(icw, -scaledSize, 0)
+                        major.drawTo(icw, scaledSize, 0)
+                        major.moveTo(-scaledSize, icw, 0)
+                        major.drawTo(scaledSize, icw, 0)
+                    else:
+                        minor.moveTo(icw, -scaledSize, 0)
+                        minor.drawTo(icw, scaledSize, 0)
+                        minor.moveTo(-scaledSize, icw, 0)
+                        minor.drawTo(scaledSize, icw, 0)
+            center.create()
+            minor.create()
+            major.create()
+            # self.gridBack.setScale(scaledSize)
+            self.labelCells()
+
+        def labelCells(self):
+            if self.cellLabelParent:
+                self.cellLabelParent.removeNode()
+            self.cellLabelParent = self.attachNewNode('cellLabels')
+            cw = self.cellWidth
+            scale = cw / 10.0
+            dx = cw * self.gridSize * .5
+            font = DirectGuiGlobals.getDefaultFont()
+            color = self.gridColor
+            for i in range(self.gridSize):
+                for j in range(self.gridSize):
+                    zoneId = self.startingZone + ((j * self.gridSize) + i)
+                    zoneStr = str(zoneId)
+                    textNode = TextNode(zoneStr)
+                    textNode.setText(zoneStr)
+                    textNode.setFont(font)
+                    textNode.setTextColor(color)
+                    textNode.setAlign(TextNode.ACenter)
+                    genTextNode = textNode.generate()
+                    textNodePath = self.cellLabelParent.attachNewNode(genTextNode)
+                    # Place the text node in the center of the cell
+                    textNodePath.setPosHprScale((i * cw - dx) + (cw * 0.5), # x
+                                                (j * cw - dx) + (cw * 0.5), # y
+                                                3.0, # z
+                                                # Lay them down flat
+                                                0,-90,0, # hpr
+                                                scale, scale, scale)
+            self.cellLabelParent.flattenLight()
+
+        def markCells(self):
+            if self.markerParent:
+                self.markerParent.removeNode()
+            self.markerParent = self.attachNewNode('markers')
+            self.cellMarkers = []
+            dx = self.cellWidth * self.gridSize * .5
+            for i in range(self.gridSize):
+                for j in range(self.gridSize):
+                    marker = loader.loadModelCopy("models/misc/smiley")
+                    marker.reparentTo(self.markerParent)
+                    marker.setPos(i * self.cellWidth - dx,
+                                  j * self.cellWidth - dx,
+                                  1.0)
+                    marker.setScale(5)
+                    self.cellMarkers.append(marker)
+
+        def unmarkCells(self):
+            if self.markerParent:
+                self.markerParent.removeNode()
+            self.markerParent = None
+
+        def visualizeGrid(self):
+            if not self.haveGridLines:
+                self.initializeGridLines()
+            self.updateGrid()

+ 117 - 0
direct/src/distributed/DistributedCartesianGridAI.py

@@ -0,0 +1,117 @@
+
+from pandac.PandaModules import *
+
+from direct.task import Task
+import DistributedNodeAI
+import CartesianGridBase
+
+class DistributedCartesianGridAI(DistributedNodeAI.DistributedNodeAI,
+                                 CartesianGridBase.CartesianGridBase):
+
+    notify = directNotify.newCategory("DistributedCartesianGridAI")
+
+    RuleSeparator = ":"
+
+    def __init__(self, air, startingZone, gridSize, gridRadius):
+        DistributedNodeAI.DistributedNodeAI.__init__(self, air)
+        self.style = "Cartesian"
+        self.startingZone = startingZone
+        self.gridSize = gridSize
+        self.gridRadius = gridRadius
+
+        # Keep track of all AI objects added to the grid
+        self.gridObjects = {}
+        self.updateTaskStarted = 0
+
+    def delete(self):
+        DistributedNodeAI.DistributedNodeAI.delete(self)
+        self.stopUpdateGridTask()
+
+    def isGridParent(self):
+        # If this distributed object is a DistributedGrid return 1.  0 by default
+        return 1
+
+    def getParentingRules(self):
+        print "calling getter"
+        rule = ("%i%s%i%s%i" % (self.startingZone, self.RuleSeparator,
+                                self.gridSize, self.RuleSeparator,
+                                self.gridRadius))
+        return [self.style, rule]
+
+    # Reparent and setLocation on av to DistributedOceanGrid
+    def addObjectToGrid(self, av):
+        print "setting parent to grid %s" % self
+        avId = av.doId
+        
+        # Create a grid parent
+        #gridParent = self.attachNewNode("gridParent-%s" % avId)
+        #self.gridParents[avId] = gridParent
+        self.gridObjects[avId] = av
+
+        # Put the avatar on the grid
+        self.handleAvatarZoneChange(av)
+
+        if not self.updateTaskStarted:
+            self.startUpdateGridTask()
+
+    def removeObjectFromGrid(self, av):
+        # TODO: WHAT LOCATION SHOULD WE SET THIS TO?
+        #av.wrtReparentTo(self.parentNP)
+        av.setLocation(simbase.air.districtId, PiratesGlobals.TestZone)
+
+        # Remove grid parent for this av
+        avId = av.doId
+        if self.gridObjects.has_key(avId):
+            del self.gridObjects[avId]
+
+        # Stop task if there are no more av's being managed
+        if len(self.gridObjects) == 0:
+            self.stopUpdateGridTask()
+
+    #####################################################################
+    # updateGridTask
+    # This task is similar to the processVisibility task for the local client.
+    # A couple differences:
+    #  - we are not doing setInterest on the AI (that is a local client specific call).
+    #  - we assume that the moving objects on the grid are parented to a gridParent,
+    #    and are broadcasting their position relative to that gridParent.  This
+    #    makes the task's math easy.  Just check to see when our position goes
+    #    out of the current grid cell.  When it does, call handleAvatarZoneChange
+
+    def startUpdateGridTask(self):
+        self.stopUpdateGridTask()
+        self.updateTaskStarted = 1
+        taskMgr.add(self.updateGridTask, self.taskName("updateGridTask"))
+
+    def stopUpdateGridTask(self):
+        taskMgr.remove(self.taskName("updateGridTask"))
+        self.updateTaskStarted = 0
+
+    def updateGridTask(self, task):
+        # Run through all grid objects and update their parents if needed
+        for avId in self.gridObjects:
+            av = self.gridObjects[avId]
+            pos = av.getPos()
+            if ((pos[0] < 0 or pos[1] < 0) or
+                (pos[0] > self.cellWidth or pos[1] > self.cellWidth)):
+                # we are out of the bounds of this current cell
+                self.handleAvatarZoneChange(av)
+        # Do this every second, not every frame
+        task.delayTime = 1.0
+        return Task.again
+
+    def handleAvatarZoneChange(self, av):
+        # Calculate zone id
+        # Get position of av relative to this grid
+        pos = av.getPos(self)
+        zoneId = self.getZoneFromXYZ(pos)
+
+        if not self.isValidZone(zoneId):
+            self.notify.warning("%s handleAvatarZoneChange %s: not a valid zone (%s) for pos %s" %
+                                (self.doId, av.doId, zoneId, pos))
+            return
+        
+        # Set the location on the server.
+        # setLocation will update the gridParent
+        av.b_setLocation(self.doId, zoneId)
+

+ 28 - 2
direct/src/distributed/DistributedNode.py

@@ -1,9 +1,10 @@
 """DistributedNode module: contains the DistributedNode class"""
 
-from direct.showbase.ShowBaseGlobal import *
 from pandac.PandaModules import NodePath
-import DistributedObject
+from direct.showbase.ShowBaseGlobal import *
 from direct.task import Task
+import GridParent
+import DistributedObject
 import types
 
 class DistributedNode(DistributedObject.DistributedObject, NodePath):
@@ -17,6 +18,9 @@ class DistributedNode(DistributedObject.DistributedObject, NodePath):
             self.gotStringParentToken = 0
             DistributedObject.DistributedObject.__init__(self, cr)
 
+            # initialize gridParent
+            self.gridParent = None
+            
     def disable(self):
         if self.activeState != DistributedObject.ESDisabled:
             self.reparentTo(hidden)
@@ -29,12 +33,34 @@ class DistributedNode(DistributedObject.DistributedObject, NodePath):
             self.DistributedNode_deleted = 1
             if not self.isEmpty():
                 self.removeNode()
+            if self.gridParent:
+                self.gridParent.delete()
             DistributedObject.DistributedObject.delete(self)
 
     def generate(self):
         DistributedObject.DistributedObject.generate(self)
         self.gotStringParentToken = 0
 
+    def setLocation(self, parentId, zoneId):
+        # Redefine DistributedObject setLocation, so that when
+        # location is set to the ocean grid, we can update our parenting
+        # under gridParent
+        DistributedObject.DistributedObject.setLocation(self, parentId, zoneId)
+        parentObj = self.cr.doId2do.get(parentId)
+        if parentObj:
+            if parentObj.isGridParent():
+                if not self.gridParent:
+                    self.gridParent = GridParent.GridParent(self)
+                self.gridParent.setGridParent(parentObj, zoneId)
+            else:
+                if self.gridParent:
+                    self.gridParent.delete()
+                    self.gridParent = None
+                    # NOTE: at this point the avatar has been detached from the scene
+                    # graph.  Someone else needs to reparent him to something in the scene graph
+            # TODO: handle DistributedNode parenting
+
+            
     def __cmp__(self, other):
         # DistributedNode inherits from NodePath, which inherits a
         # definition of __cmp__ from FFIExternalObject that uses the

+ 25 - 1
direct/src/distributed/DistributedNodeAI.py

@@ -1,6 +1,7 @@
 from otp.ai.AIBaseGlobal import *
 from pandac.PandaModules import NodePath
-from direct.distributed import DistributedObjectAI
+import DistributedObjectAI
+import GridParent
 
 class DistributedNodeAI(DistributedObjectAI.DistributedObjectAI, NodePath):
     def __init__(self, air, name=None):
@@ -13,12 +14,35 @@ class DistributedNodeAI(DistributedObjectAI.DistributedObjectAI, NodePath):
             if name is None:
                 name = self.__class__.__name__
             NodePath.__init__(self, name)
+            self.gridParent = None
 
     def delete(self):
         if not self.isEmpty():
             self.removeNode()
+        if self.gridParent:
+            self.gridParent.delete()
         DistributedObjectAI.DistributedObjectAI.delete(self)
 
+    def setLocation(self, parentId, zoneId):
+        # Redefine DistributedObject setLocation, so that when
+        # location is set to the ocean grid, we can update our parenting
+        # under gridParent
+        DistributedObjectAI.DistributedObjectAI.setLocation(self, parentId, zoneId)
+        parentObj = self.air.doId2do.get(parentId)
+        if parentObj:
+            if parentObj.isGridParent():
+                if not self.gridParent:
+                    self.gridParent = GridParent.GridParent(self)
+                self.gridParent.setGridParent(parentObj, zoneId)
+            else:
+                if self.gridParent:
+                    self.gridParent.delete()
+                    self.gridParent = None
+                    # NOTE: at this point the avatar has been detached from the scene
+                    # graph.  Someone else needs to reparent him to something in the scene graph
+            # TODO: handle DistributedNode parenting
+
+
     ### setParent ###
 
     def b_setParent(self, parentToken):

+ 4 - 0
direct/src/distributed/DistributedObject.py

@@ -367,3 +367,7 @@ class DistributedObject(PandaObject):
     def updateZone(self, zoneId):
         self.cr.sendUpdateZone(self, zoneId)
 
+    def isGridParent(self):
+        # If this distributed object is a DistributedGrid return 1.  0 by default
+        return 0
+

+ 3 - 1
direct/src/distributed/DistributedObjectAI.py

@@ -316,7 +316,6 @@ class DistributedObjectAI(DirectObject.DirectObject):
                 assert doId is None or doId == self.__preallocDoId
                 doId=self.__preallocDoId
                 self.__preallocDoId = 0
-
             self.air.sendGenerateOtpObject(
                     self, parentId, zoneId, optionalFields, doId=doId)
             assert not hasattr(self, 'parentId')
@@ -475,4 +474,7 @@ class DistributedObjectAI(DirectObject.DirectObject):
         else:
             self.notify.warning("Unexpected completion from barrier %s" % (context))
 
+    def isGridParent(self):
+        # If this distributed object is a DistributedGrid return 1.  0 by default
+        return 0
 

+ 72 - 0
direct/src/distributed/GridParent.py

@@ -0,0 +1,72 @@
+
+from pandac.PandaModules import *
+
+#
+# GridParent.py
+# Any object that can be parented to the ocean grid
+# (or any grid whose size is too large to represent in 16 bits),
+# should derive from GridParent.  Can be used on client and AI code.
+
+# GridParent will put a node inbetween the object and the grid so
+# that the object is broadcasting its position relative to the gridCell
+# it lies in.
+
+class GridParent:
+    
+    def __init__(self, av):
+        # The object on the grid will need to broadcast his position relative to
+        # his current grid cell in order to use 16 bit
+        # telemetry.  To do this, we will have a node attached to the
+        # grid cell origin, and the object will wrtReparent himself to it when
+        # crossing into that grid cell.  We don't need to create a node for each
+        # cell origin.  We just need two nodes:  one that we are currently parented
+        # to, and the other that we will wrtReparentTo.  Just before wrtReparenting
+        # to the new node, set it's position to the new grid cell origin.
+        self.av = av
+        self.grid = None
+        self.cellOrigin = NodePath("cellOrigin")
+
+    def delete(self):
+        if self.av.getParent() == self.cellOrigin:
+            self.av.detachNode()
+        del self.av
+        # Remove the gridNodes
+        self.cellOrigin.removeNode()
+                
+    def setGridParent(self, grid, zoneId):
+        # If the avatar has a parent, preserve his absolute position.  Otherwise
+        # if he has no parent, assume this is the first time he's been parented
+        # to anything, and don't wrtReparent, just do a regular reparent.
+        firstParent = 0
+        if self.av.getParent().isEmpty():
+            firstParent = 1
+
+        if not firstParent:
+            # Stick the avatar under hidden while we move the cellOrigin into
+            # position so we do not lose the avatars absolute position.
+            self.av.wrtReparentTo(hidden)
+
+        if grid != self.grid:
+            # Setup the grid for the first time
+            # set/change the grid that we are working on.
+            self.grid = grid
+            # Reparent the gridNodes under this grid
+            self.cellOrigin.reparentTo(grid)
+            self.cellOrigin.setPosHpr(0,0,0,0,0,0)
+        
+        # Get grid cell origin
+        cellPos = self.grid.getZoneCellOrigin(zoneId)
+
+        # Set the gridNode's position
+        self.cellOrigin.setPos(*cellPos)
+
+        # Reparent our avatar to this node
+        if not firstParent:
+            self.av.wrtReparentTo(self.cellOrigin)
+        else:
+            self.av.reparentTo(self.cellOrigin)
+            
+        #print "gridParent: reparent to %s" % self.av
+        #print "gridParent: pos = %s,%s" % (self.av.getPos(), self.av.getParent().getPos())
+
+