From 877a2aa548c7487bbdff01e12186a5e26821997a Mon Sep 17 00:00:00 2001 From: Samir Naik Date: Fri, 25 Mar 2005 02:54:27 +0000 Subject: [PATCH] added support for static grid objects in setLocation of DistributedNode --- direct/src/distributed/CartesianGridBase.py | 40 +++ .../distributed/DistributedCartesianGrid.py | 275 ++++++++++++++++++ .../distributed/DistributedCartesianGridAI.py | 117 ++++++++ direct/src/distributed/DistributedNode.py | 30 +- direct/src/distributed/DistributedNodeAI.py | 26 +- direct/src/distributed/DistributedObject.py | 4 + direct/src/distributed/DistributedObjectAI.py | 4 +- direct/src/distributed/GridParent.py | 72 +++++ 8 files changed, 564 insertions(+), 4 deletions(-) create mode 100755 direct/src/distributed/CartesianGridBase.py create mode 100755 direct/src/distributed/DistributedCartesianGrid.py create mode 100755 direct/src/distributed/DistributedCartesianGridAI.py create mode 100755 direct/src/distributed/GridParent.py diff --git a/direct/src/distributed/CartesianGridBase.py b/direct/src/distributed/CartesianGridBase.py new file mode 100755 index 0000000000..7487de488b --- /dev/null +++ b/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) + diff --git a/direct/src/distributed/DistributedCartesianGrid.py b/direct/src/distributed/DistributedCartesianGrid.py new file mode 100755 index 0000000000..528efae493 --- /dev/null +++ b/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() diff --git a/direct/src/distributed/DistributedCartesianGridAI.py b/direct/src/distributed/DistributedCartesianGridAI.py new file mode 100755 index 0000000000..47477aa813 --- /dev/null +++ b/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) + diff --git a/direct/src/distributed/DistributedNode.py b/direct/src/distributed/DistributedNode.py index d8b9bbce15..b62e7e52ac 100644 --- a/direct/src/distributed/DistributedNode.py +++ b/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 diff --git a/direct/src/distributed/DistributedNodeAI.py b/direct/src/distributed/DistributedNodeAI.py index c67c268fa4..2eac8bb745 100644 --- a/direct/src/distributed/DistributedNodeAI.py +++ b/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): diff --git a/direct/src/distributed/DistributedObject.py b/direct/src/distributed/DistributedObject.py index 32920e6b7f..a64e525c62 100644 --- a/direct/src/distributed/DistributedObject.py +++ b/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 + diff --git a/direct/src/distributed/DistributedObjectAI.py b/direct/src/distributed/DistributedObjectAI.py index ee5b909473..fe6c5fb5c0 100644 --- a/direct/src/distributed/DistributedObjectAI.py +++ b/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 diff --git a/direct/src/distributed/GridParent.py b/direct/src/distributed/GridParent.py new file mode 100755 index 0000000000..18c9bc463b --- /dev/null +++ b/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()) + +