added support for static grid objects in setLocation of DistributedNode

This commit is contained in:
Samir Naik 2005-03-25 02:54:27 +00:00
parent 9c9a444209
commit 877a2aa548
8 changed files with 564 additions and 4 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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())