ordering a bin

This allows you to explicitly control the render order with the fixed bin and other bins of the same type. It allows you to place nodepaths before/after each other. Where it really helps though is in the group interface. It will let you specify a render order for a particular set of nodes and then set the render order for them as a block with respect to other nodes. Groups can contain other groups as well as nodes, and groups can be modified at runtime and the render order will correct itself.

This is useful for GUI’s–it makes sure the render order of frames and buttons and whatever else is correct (note that the direct gui sort of does this already). I have been using it with both my custom gui stuff and with the overlays by davedes. I thought it might be useful here as it fills a gap in the bin and render order design.

from random import uniform

class binOrder(object):
	def __init__(self,bin='fixed',startingIndex=0):
		self.bin = bin
		self.startingIndex = startingIndex
		self.groups = {'mainOrder':[]}
	
	def createGroup(self,name,*nodes):
		'''the nodes passed in are rendered in back to front order based upon the order of being passed in.
		can pass in a list of nodes and/or other group names
		You can use the name to move the group around in the render order
		the name must be unique or it will overwrite the existing group with that name. 
		'' means a random one will be generated'''
		if name=='': name = str(id(nodes)+uniform(0,9999))
		self.groups[name] = self._determineArgs(nodes)[:]	#make a copy of the list so that if it 
										#changes this doesn't change.  fixes a nasty intermittent bug 
										#where changing that list messes up this class
		return name
	
	'''all of these insert/move functions accept the node to reference.  This can be a group name or a node 
	already in the ordering system.  A value of None anywhere will render the node in front of everything.
	The nodes can be a list of nodes and/or group names.  movingNode has to be one node or group name'''
	def addToGroup(self,group,*nodes):
		self.groups[group].extend(self._determineArgs(nodes))
	
	def insertInFront(self,node,*nodes):
		group,index = self._findPlace(node)
		if group==None: return
		self.groups[group][index+1:index+1] = self._determineArgs(nodes)
		self._updateOrder()
			
	def insertInBack(self,node,*nodes):
		group,index = self._findPlace(node)
		if group==None: return
		self.groups[group][index:index] = self._determineArgs(nodes)
		self._updateOrder()
	
	def bringToFront(self,movingNode,group=None):
		oldGroup,oldIndex = self._findPlace(movingNode)
		if oldGroup==None: return
		self.groups[oldGroup].pop(oldIndex)
		
		if group==None: group='mainOrder'
		self.groups[group].append(movingNode)
		self._updateOrder()
	
	def moveInFront(self,node,movingNode):
		oldGroup,oldIndex = self._findPlace(movingNode)
		if oldGroup==None: return
		self.groups[oldGroup].pop(oldIndex)
	
		group,index = self._findPlace(node)
		if group==None: return
		self.groups[group][index+1:index+1] = [movingNode]
		self._updateOrder()
	
	def moveInBack(self,node,movingNode):
		oldGroup,oldIndex = self._findPlace(movingNode)
		if oldGroup==None: return
		self.groups[oldGroup].pop(oldIndex)
	
		group,index = self._findPlace(node)
		if group==None: return
		self.groups[group][index:index] = [movingNode]
		self._updateOrder()
	
	def remove(self,node):
		'''removes the node/group from the ordering system, but does not delete groups from memory--still there just insert again'''
		group,index = self._findPlace(node)
		if group==None: return
		self.groups[group].pop(index)
		self._updateOrder()
	
	def removeGroup(self,node):
		'''same as remove, but will delete the group from the list too'''
		group,index = self._findPlace(node)
		if group==None: return
		self.groups[group].pop(index)
		if isinstance(node,str):	#clean up the group too.
			del self.groups[node]
		self._updateOrder()
	
	def _updateOrder(self,group=None,num=None):
		'''actually updates the order.  recursive'''
		if group==None: group = 'mainOrder'
		if num==None: num = self.startingIndex
		
		for node in self.groups[group]:
			if isinstance(node,str):	#another group
				num = self._updateOrder(node,num)
			else:
				node.setBin(self.bin,num)
				num+=1
		return num
	
	def _findPlace(self,node):
		'finds where the supplied node is, returns the name of the group and the index'
		if node==None:
			return ['mainOrder',len(self.groups['mainOrder'])]
		for name,group in self.groups.iteritems():
			try:
				return [name,group.index(node)]
			except ValueError:
				pass
		return [None,0]
	
	def _determineArgs(self,nodes):
		nodes = list(nodes)
		if len(nodes)==0: return []
		if len(nodes)>1: return nodes
		if hasattr(nodes[0],'__iter__') and not isinstance(nodes[0],str):
			return nodes[0]
		return nodes
		
		
		
			
		

It be nice if you include a snipit on how it should be used.

This keeps track of where things are back to front and allows nodes to be inserted in the middle…it also lets you create a “group” of nodes which you can insert/move around as one–everything’s recursive so you can have groups inside of groups etc. It handles changing each node’s render order when you insert/remove nodes and groups so that everything stays on screen in the order you specify.

It is to overcome the problem of setBin not being additive from parent to children

I will come up with a code example tomorrow…

This is not very appealing visually, and visuals mean nothing, but the code is well documented with each of the functions.

from pandac.PandaModules import *
import direct.directbase.DirectStart
from binOrder import binOrder
from random import uniform
#the visual really doesn't mean anything here...
def createSprite():
   vdata = GeomVertexData('', GeomVertexFormat.getV3t2(), Geom.UHStatic) 
   vertex = GeomVertexWriter(vdata, 'vertex') 
   uv = GeomVertexWriter(vdata, 'texcoord') 
   prim = GeomTriangles(Geom.UHStatic) 
    
   vertex.addData3f(0, 0, 1) 
   vertex.addData3f(0, 0, 0) 
   vertex.addData3f(1, 0, 0) 
   vertex.addData3f(1, 0, 1) 
   uv.addData2f(0,0) 
   uv.addData2f(0,1) 
   uv.addData2f(1,1) 
   uv.addData2f(1,0) 
    
   prim.addVertices(3,2,0) 
   prim.addVertices(1,0,2) 
   prim.closePrimitive() 
    
   geom = Geom(vdata) 
   geom.addPrimitive(prim) 
   nodepath = NodePath(GeomNode('gnode')) 
   nodepath.node().addGeom(geom) 
   nodepath.setTransparency(1) 

   return nodepath
  
nodes = []
for i in range(0,13):
	nodes.append(createSprite())
	nodes[-1].setPos(-1+i/7.0,0,0)
	nodes[-1].reparentTo(render2d)
	nodes[-1].setColorScale(uniform(0,1),uniform(0,1),uniform(0,1),1)
  
bo = binOrder()

#simple node operations
bo.insertInFront(None,nodes[0])	#nodes[0] is now in front of everything
bo.insertInBack(nodes[0],nodes[1])	#nodes[1] is now behind nodes[0]
bo.insertInFront(nodes[1],nodes[2])	#nodes[2] is now in between nodes[0] and nodes[1]
bo.moveInFront(nodes[0],nodes[2])	#now nodes[2] is in front of nodes[0] which is in front of nodes[1]
bo.moveInBack(nodes[1],nodes[0])	#now the order is nodes[2]->nodes[1]->nodes[0]
bo.insertInBack(nodes[1],nodes[3])	#order is 2>1>3>0
bo.bringToFront(nodes[0])			#0>2>1>3


#groups
group1 = bo.createGroup('group1name')	#creates a group called group1name...note that the variable group1 simply holds a string called group1name	
group2 = bo.createGroup('group2name',nodes[4:10])	#creates a group with nodes 4-10 inside with 10 rendered in front and 4 rendered in back

#groups are not in the list of render order yet, just created.
#we can add nodes to groups, and even groups to groups using the normal node commands:
bo.addToGroup(group1,nodes[11])	#group1 now contains 11
bo.insertInFront(nodes[11],nodes[12])	#group1 now contatins 12>11
bo.insertInBack(nodes[12],group2)	#group1 now contains 12>group2>11
										#also 12>10>9>8>7>6>5>4>11
										
#we can insert group1 into the actual list with the same commands:
bo.insertInFront(nodes[2],group1)	#total order is 0>group1>2>1>3
										#or 0>12>group2>11>2>1>3
										#or 0>12>10>9>8>7>6>5>4>2>1>3

										
#we can move things into/out of groups easy:
bo.moveInFront(nodes[4],nodes[0])	#now group2 is 10>9>8>7>6>5>0>4
									#the total is group1>2>1>3

#if you want to remove a node:
bo.remove(nodes[1])			#now the order is group1>2>3

#we can remove a group, but it keeps its data intact so we can reinsert it at a later time:
bo.remove(group2)			#now group1 is 12>11
#and insert again
bo.insertInBack(nodes[2],group2)	#now the total is group1>2>group2>3

#note that the two above commands are the same as
#bo.moveInBack(nodes[2],group2)

#to remove a group completely:
bo.removeGroup(group1)	#gone...Gone...GONE!

#bo.insertInback(nodes[3],group1)	#would error, group1 doesn't exist anymore.  anything inside of group1 was lost
 
 
run()