mirror of
				https://github.com/panda3d/panda3d.git
				synced 2025-11-04 04:25:42 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			721 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			721 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#################################################################
 | 
						|
# seSelection.py
 | 
						|
# Originally from DirectSelection.py
 | 
						|
# Altered by Yi-Hong Lin, yihhongl@andrew.cmu.edu, 2004
 | 
						|
#
 | 
						|
# We didn't change anything essential.
 | 
						|
# Just because we customized the seSession from DirectSession,
 | 
						|
# So we need related files can follow the change.
 | 
						|
# However, we don't want to change anything inside the original directool
 | 
						|
# to let them can work with our scene editor.
 | 
						|
# (If we do change original directools, it will force user has to install the latest version of OUR Panda)
 | 
						|
#
 | 
						|
#################################################################
 | 
						|
from direct.directtools.DirectGlobals import *
 | 
						|
from direct.directtools.DirectUtil import *
 | 
						|
from seGeometry import *
 | 
						|
from direct.showbase.DirectObject import *
 | 
						|
from quad import *
 | 
						|
COA_ORIGIN = 0
 | 
						|
COA_CENTER = 1
 | 
						|
 | 
						|
# MRM: To do: handle broken node paths in selected and deselected dicts
 | 
						|
class DirectNodePath(NodePath):
 | 
						|
    # A node path augmented with info, bounding box, and utility methods
 | 
						|
    def __init__(self, nodePath):
 | 
						|
        # Initialize the superclass
 | 
						|
        NodePath.__init__(self)
 | 
						|
        self.assign(nodePath)
 | 
						|
        # Create a bounding box
 | 
						|
        self.bbox = DirectBoundingBox(self)
 | 
						|
        center = self.bbox.getCenter()
 | 
						|
        # Create matrix to hold the offset between the nodepath
 | 
						|
        # and its center of action (COA)
 | 
						|
        self.mCoa2Dnp = Mat4(Mat4.identMat())
 | 
						|
        if SEditor.coaMode == COA_CENTER:
 | 
						|
            self.mCoa2Dnp.setRow(3, Vec4(center[0], center[1], center[2], 1))
 | 
						|
            
 | 
						|
        # Transform from nodePath to widget
 | 
						|
        self.tDnp2Widget = TransformState.makeIdentity()
 | 
						|
 | 
						|
    def highlight(self):
 | 
						|
        self.bbox.show()
 | 
						|
 | 
						|
    def dehighlight(self):
 | 
						|
        self.bbox.hide()
 | 
						|
 | 
						|
    def getCenter(self):
 | 
						|
        return self.bbox.getCenter()
 | 
						|
 | 
						|
    def getRadius(self):
 | 
						|
        return self.bbox.getRadius()
 | 
						|
 | 
						|
    def getMin(self):
 | 
						|
        return self.bbox.getMin()
 | 
						|
 | 
						|
    def getMax(self):
 | 
						|
        return self.bbox.getMax()
 | 
						|
 | 
						|
class SelectedNodePaths(DirectObject):
 | 
						|
    def __init__(self):
 | 
						|
        self.reset()
 | 
						|
 | 
						|
    def reset(self):
 | 
						|
        self.selectedDict = {}
 | 
						|
        self.deselectedDict = {}
 | 
						|
        __builtins__["last"] = self.last = None
 | 
						|
 | 
						|
    def select(self, nodePath, fMultiSelect = 0):
 | 
						|
        """ Select the specified node path.  Multiselect as required """
 | 
						|
        # Do nothing if nothing selected
 | 
						|
        if not nodePath:
 | 
						|
            print 'Nothing selected!!'
 | 
						|
            return None
 | 
						|
        
 | 
						|
        # Reset selected objects and highlight if multiSelect is false
 | 
						|
        if not fMultiSelect:
 | 
						|
            self.deselectAll()
 | 
						|
        
 | 
						|
        # Get this pointer
 | 
						|
        id = nodePath.id()
 | 
						|
        # First see if its already in the selected dictionary
 | 
						|
        dnp = self.getSelectedDict(id)
 | 
						|
        # If so, we're done
 | 
						|
        if not dnp:
 | 
						|
            # See if it is in the deselected dictionary
 | 
						|
            dnp = self.getDeselectedDict(id)
 | 
						|
            if dnp:
 | 
						|
                # Remove it from the deselected dictionary
 | 
						|
                del(self.deselectedDict[id])
 | 
						|
                # Show its bounding box
 | 
						|
                dnp.highlight()
 | 
						|
            else:
 | 
						|
                # Didn't find it, create a new selectedNodePath instance
 | 
						|
                dnp = DirectNodePath(nodePath)
 | 
						|
                # Show its bounding box
 | 
						|
                dnp.highlight()
 | 
						|
            # Add it to the selected dictionary
 | 
						|
            self.selectedDict[dnp.id()] = dnp
 | 
						|
        # And update last
 | 
						|
        __builtins__["last"] = self.last = dnp
 | 
						|
        return dnp
 | 
						|
 | 
						|
    def deselect(self, nodePath):
 | 
						|
        """ Deselect the specified node path """
 | 
						|
        # Get this pointer
 | 
						|
        id = nodePath.id()
 | 
						|
        # See if it is in the selected dictionary
 | 
						|
        dnp = self.getSelectedDict(id)
 | 
						|
        if dnp:
 | 
						|
            # It was selected:
 | 
						|
            # Hide its bounding box
 | 
						|
            dnp.dehighlight()
 | 
						|
            # Remove it from the selected dictionary
 | 
						|
            del(self.selectedDict[id])
 | 
						|
            # And keep track of it in the deselected dictionary
 | 
						|
            self.deselectedDict[id] = dnp
 | 
						|
            # Send a message
 | 
						|
            messenger.send('DIRECT_deselectedNodePath', [dnp])
 | 
						|
        return dnp
 | 
						|
 | 
						|
    def getSelectedAsList(self):
 | 
						|
        """
 | 
						|
        Return a list of all selected node paths.  No verification of
 | 
						|
        connectivity is performed on the members of the list
 | 
						|
        """
 | 
						|
        return self.selectedDict.values()[:]
 | 
						|
 | 
						|
    def __getitem__(self,index):
 | 
						|
        return self.getSelectedAsList()[index]
 | 
						|
 | 
						|
    def getSelectedDict(self, id):
 | 
						|
        """
 | 
						|
        Search selectedDict for node path, try to repair broken node paths.
 | 
						|
        """
 | 
						|
        dnp = self.selectedDict.get(id, None)
 | 
						|
        if dnp:
 | 
						|
            return dnp
 | 
						|
        else:
 | 
						|
            # Not in selected dictionary
 | 
						|
            return None
 | 
						|
 | 
						|
    def getDeselectedAsList(self):
 | 
						|
        return self.deselectedDict.values()[:]
 | 
						|
 | 
						|
    def getDeselectedDict(self, id):
 | 
						|
        """
 | 
						|
        Search deselectedDict for node path, try to repair broken node paths.
 | 
						|
        """
 | 
						|
        dnp = self.deselectedDict.get(id, None)
 | 
						|
        if dnp:
 | 
						|
            # Yes
 | 
						|
            return dnp
 | 
						|
        else:
 | 
						|
            # Not in deselected dictionary
 | 
						|
            return None
 | 
						|
 | 
						|
    def forEachSelectedNodePathDo(self, func):
 | 
						|
        """
 | 
						|
        Perform given func on selected node paths.  No node path
 | 
						|
        connectivity verification performed
 | 
						|
        """
 | 
						|
        selectedNodePaths = self.getSelectedAsList()
 | 
						|
        for nodePath in selectedNodePaths:
 | 
						|
            func(nodePath)
 | 
						|
 | 
						|
    def forEachDeselectedNodePathDo(self, func):
 | 
						|
        """
 | 
						|
        Perform given func on deselected node paths.  No node path
 | 
						|
        connectivity verification performed
 | 
						|
        """
 | 
						|
        deselectedNodePaths = self.getDeselectedAsList()
 | 
						|
        for nodePath in deselectedNodePaths:
 | 
						|
            func(nodePath)
 | 
						|
 | 
						|
    def getWrtAll(self):
 | 
						|
        self.forEachSelectedNodePathDo(self.getWrt)
 | 
						|
 | 
						|
    def getWrt(self, nodePath):
 | 
						|
        nodePath.tDnp2Widget = nodePath.getTransform(SEditor.widget)
 | 
						|
 | 
						|
    def moveWrtWidgetAll(self):
 | 
						|
        self.forEachSelectedNodePathDo(self.moveWrtWidget)
 | 
						|
 | 
						|
    def moveWrtWidget(self, nodePath):
 | 
						|
        nodePath.setTransform(SEditor.widget, nodePath.tDnp2Widget)
 | 
						|
 | 
						|
    def deselectAll(self):
 | 
						|
        self.forEachSelectedNodePathDo(self.deselect)
 | 
						|
 | 
						|
    def highlightAll(self):
 | 
						|
        self.forEachSelectedNodePathDo(DirectNodePath.highlight)
 | 
						|
 | 
						|
    def dehighlightAll(self):
 | 
						|
        self.forEachSelectedNodePathDo(DirectNodePath.dehighlight)
 | 
						|
 | 
						|
    def removeSelected(self):
 | 
						|
        selected = self.last
 | 
						|
        if selected:
 | 
						|
            selected.remove()
 | 
						|
        __builtins__["last"] = self.last = None
 | 
						|
        
 | 
						|
    def removeAll(self):
 | 
						|
        # Remove all selected nodePaths from the Scene Graph
 | 
						|
        self.forEachSelectedNodePathDo(NodePath.remove)
 | 
						|
 | 
						|
    def toggleVisSelected(self):
 | 
						|
        selected = self.last
 | 
						|
        # Toggle visibility of selected node paths
 | 
						|
        if selected:
 | 
						|
            selected.toggleVis()
 | 
						|
 | 
						|
    def toggleVisAll(self):
 | 
						|
        # Toggle viz for all selected node paths
 | 
						|
        self.forEachSelectedNodePathDo(NodePath.toggleVis)
 | 
						|
 | 
						|
    def isolateSelected(self):
 | 
						|
        selected = self.last
 | 
						|
        if selected:
 | 
						|
            selected.isolate()
 | 
						|
 | 
						|
    def getDirectNodePath(self, nodePath):
 | 
						|
        # Get this pointer
 | 
						|
        id = nodePath.id()
 | 
						|
        # First check selected dict
 | 
						|
        dnp = self.getSelectedDict(id)
 | 
						|
        if dnp:
 | 
						|
            return dnp
 | 
						|
        # Otherwise return result of deselected search
 | 
						|
        return self.getDeselectedDict(id)
 | 
						|
 | 
						|
    def getNumSelected(self):
 | 
						|
        return len(self.selectedDict.keys())
 | 
						|
 | 
						|
 | 
						|
class DirectBoundingBox:
 | 
						|
    def __init__(self, nodePath):
 | 
						|
        # Record the node path
 | 
						|
        self.nodePath = nodePath
 | 
						|
        # Compute bounds, min, max, etc.
 | 
						|
        self.computeTightBounds()
 | 
						|
        # Generate the bounding box
 | 
						|
        self.lines = self.createBBoxLines()
 | 
						|
 | 
						|
    def computeTightBounds(self):
 | 
						|
        # Compute bounding box using tighter calcTightBounds function
 | 
						|
        # Need to clear out existing transform on node path
 | 
						|
        tMat = Mat4()
 | 
						|
        tMat.assign(self.nodePath.getMat())
 | 
						|
        self.nodePath.clearMat()
 | 
						|
        # Get bounds
 | 
						|
        self.min = Point3(0)
 | 
						|
        self.max = Point3(0)
 | 
						|
        self.nodePath.calcTightBounds(self.min,self.max)
 | 
						|
        # Calc center and radius
 | 
						|
        self.center = Point3((self.min + self.max)/2.0)
 | 
						|
        self.radius = Vec3(self.max - self.min).length()
 | 
						|
        # Restore transform
 | 
						|
        self.nodePath.setMat(tMat)
 | 
						|
        del tMat
 | 
						|
        
 | 
						|
    def computeBounds(self):
 | 
						|
        self.bounds = self.getBounds()
 | 
						|
        if self.bounds.isEmpty() or self.bounds.isInfinite():
 | 
						|
            self.center = Point3(0)
 | 
						|
            self.radius = 1.0
 | 
						|
        else:
 | 
						|
            self.center = self.bounds.getCenter()
 | 
						|
            self.radius = self.bounds.getRadius()
 | 
						|
        self.min = Point3(self.center - Point3(self.radius))
 | 
						|
        self.max = Point3(self.center + Point3(self.radius))
 | 
						|
        
 | 
						|
    def createBBoxLines(self):
 | 
						|
        # Create a line segments object for the bbox
 | 
						|
        lines = LineNodePath(hidden)
 | 
						|
        lines.node().setName('bboxLines')
 | 
						|
        lines.setColor( VBase4( 1., 0., 0., 1. ) )
 | 
						|
        lines.setThickness( 0.5 )
 | 
						|
 | 
						|
        minX = self.min[0]
 | 
						|
        minY = self.min[1]
 | 
						|
        minZ = self.min[2]
 | 
						|
        maxX = self.max[0]
 | 
						|
        maxY = self.max[1]
 | 
						|
        maxZ = self.max[2]
 | 
						|
        
 | 
						|
        # Bottom face
 | 
						|
        lines.moveTo( minX, minY, minZ )
 | 
						|
        lines.drawTo( maxX, minY, minZ )
 | 
						|
        lines.drawTo( maxX, maxY, minZ )
 | 
						|
        lines.drawTo( minX, maxY, minZ )
 | 
						|
        lines.drawTo( minX, minY, minZ )
 | 
						|
 | 
						|
        # Front Edge/Top face
 | 
						|
        lines.drawTo( minX, minY, maxZ )
 | 
						|
        lines.drawTo( maxX, minY, maxZ )
 | 
						|
        lines.drawTo( maxX, maxY, maxZ )
 | 
						|
        lines.drawTo( minX, maxY, maxZ )
 | 
						|
        lines.drawTo( minX, minY, maxZ )
 | 
						|
 | 
						|
        # Three remaining edges
 | 
						|
        lines.moveTo( maxX, minY, minZ )
 | 
						|
        lines.drawTo( maxX, minY, maxZ )
 | 
						|
        lines.moveTo( maxX, maxY, minZ )
 | 
						|
        lines.drawTo( maxX, maxY, maxZ )
 | 
						|
        lines.moveTo( minX, maxY, minZ )
 | 
						|
        lines.drawTo( minX, maxY, maxZ )
 | 
						|
 | 
						|
        # Create and return bbox lines
 | 
						|
        lines.create()
 | 
						|
        
 | 
						|
        # Make sure bbox is never lit or drawn in wireframe
 | 
						|
        useDirectRenderStyle(lines)
 | 
						|
        
 | 
						|
        return lines
 | 
						|
 | 
						|
    def updateBBoxLines(self):
 | 
						|
        ls = self.lines.lineSegs
 | 
						|
        
 | 
						|
        minX = self.min[0]
 | 
						|
        minY = self.min[1]
 | 
						|
        minZ = self.min[2]
 | 
						|
        maxX = self.max[0]
 | 
						|
        maxY = self.max[1]
 | 
						|
        maxZ = self.max[2]
 | 
						|
        
 | 
						|
        # Bottom face
 | 
						|
        ls.setVertex( 0, minX, minY, minZ )
 | 
						|
        ls.setVertex( 1, maxX, minY, minZ )
 | 
						|
        ls.setVertex( 2, maxX, maxY, minZ )
 | 
						|
        ls.setVertex( 3, minX, maxY, minZ )
 | 
						|
        ls.setVertex( 4, minX, minY, minZ )
 | 
						|
 | 
						|
        # Front Edge/Top face
 | 
						|
        ls.setVertex( 5, minX, minY, maxZ )
 | 
						|
        ls.setVertex( 6, maxX, minY, maxZ )
 | 
						|
        ls.setVertex( 7, maxX, maxY, maxZ )
 | 
						|
        ls.setVertex( 8, minX, maxY, maxZ )
 | 
						|
        ls.setVertex( 9, minX, minY, maxZ )
 | 
						|
 | 
						|
        # Three remaining edges
 | 
						|
        ls.setVertex( 10, maxX, minY, minZ )
 | 
						|
        ls.setVertex( 11, maxX, minY, maxZ )
 | 
						|
        ls.setVertex( 12, maxX, maxY, minZ )
 | 
						|
        ls.setVertex( 13, maxX, maxY, maxZ )
 | 
						|
        ls.setVertex( 14, minX, maxY, minZ )
 | 
						|
        ls.setVertex( 15, minX, maxY, maxZ )
 | 
						|
 | 
						|
    def getBounds(self):
 | 
						|
        # Get a node path's bounds
 | 
						|
        nodeBounds = BoundingSphere()
 | 
						|
        nodeBounds.extendBy(self.nodePath.node().getInternalBound())
 | 
						|
        for child in self.nodePath.getChildrenAsList():
 | 
						|
            nodeBounds.extendBy(child.getBounds())
 | 
						|
        return nodeBounds.makeCopy()
 | 
						|
 | 
						|
    def show(self):
 | 
						|
        self.lines.reparentTo(self.nodePath)
 | 
						|
 | 
						|
    def hide(self):
 | 
						|
        self.lines.reparentTo(hidden)
 | 
						|
        
 | 
						|
    def getCenter(self):
 | 
						|
        return self.center
 | 
						|
 | 
						|
    def getRadius(self):
 | 
						|
        return self.radius
 | 
						|
 | 
						|
    def getMin(self):
 | 
						|
        return self.min
 | 
						|
 | 
						|
    def getMax(self):
 | 
						|
        return self.max
 | 
						|
 | 
						|
    def vecAsString(self, vec):
 | 
						|
        return '%.2f %.2f %.2f' % (vec[0], vec[1], vec[2])
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return (`self.__class__` + 
 | 
						|
                '\nNodePath:\t%s\n' % self.nodePath.getName() +
 | 
						|
                'Min:\t\t%s\n' % self.vecAsString(self.min) +
 | 
						|
                'Max:\t\t%s\n' % self.vecAsString(self.max) +
 | 
						|
                'Center:\t\t%s\n' % self.vecAsString(self.center) +
 | 
						|
                'Radius:\t\t%.2f' % self.radius
 | 
						|
                )
 | 
						|
 | 
						|
 | 
						|
class SelectionQueue(CollisionHandlerQueue):
 | 
						|
    def __init__(self, parentNP = render):
 | 
						|
        # Initialize the superclass
 | 
						|
        CollisionHandlerQueue.__init__(self)
 | 
						|
        # Current index and entry in collision queue
 | 
						|
        self.index = -1
 | 
						|
        self.entry = None
 | 
						|
        self.skipFlags = SKIP_NONE
 | 
						|
        # Create a collision node path attached to the given NP
 | 
						|
        self.collisionNodePath = NodePath(CollisionNode("collisionNP"))
 | 
						|
        self.setParentNP(parentNP)
 | 
						|
        # Don't pay the penalty of drawing this collision ray
 | 
						|
        self.collisionNodePath.hide()
 | 
						|
        self.collisionNode = self.collisionNodePath.node()
 | 
						|
        # Intersect with geometry to begin with
 | 
						|
        self.collideWithGeom()
 | 
						|
        # And a traverser to do the actual collision tests
 | 
						|
        self.ct = CollisionTraverser()
 | 
						|
        # Let the traverser know about the collision node and the queue
 | 
						|
        #Manakel 2/12/2005: replace CollisionNode by its nodepath
 | 
						|
        self.ct.addCollider(self.collisionNodePath, self)
 | 
						|
        # List of objects that can't be selected
 | 
						|
        self.unpickable = UNPICKABLE
 | 
						|
        # Derived class must add Collider to complete initialization
 | 
						|
 | 
						|
    def setParentNP(self, parentNP):
 | 
						|
        # Update collisionNodePath's parent
 | 
						|
        self.collisionNodePath.reparentTo(parentNP)
 | 
						|
 | 
						|
    def addCollider(self, collider):
 | 
						|
        # Inherited class must call this function to specify collider object
 | 
						|
        # Record collision object
 | 
						|
        self.collider = collider
 | 
						|
        # Add the collider to the collision Node
 | 
						|
        self.collisionNode.addSolid( self.collider )
 | 
						|
 | 
						|
    def collideWithBitMask(self, bitMask):
 | 
						|
        # The into collide mask is the bit pattern colliders look at
 | 
						|
        # when deciding whether or not to test for a collision "into"
 | 
						|
        # this collision solid.  Set to all Off so this collision solid
 | 
						|
        # will not be considered in any collision tests
 | 
						|
        self.collisionNode.setIntoCollideMask(BitMask32().allOff())
 | 
						|
        # The from collide mask is the bit pattern *this* collision solid
 | 
						|
        # compares against the into collide mask of candidate collision solids
 | 
						|
        # Turn this mask all off since we're not testing for collisions against
 | 
						|
        # collision solids
 | 
						|
        self.collisionNode.setFromCollideMask(bitMask)
 | 
						|
        # Don't test against actual geometry
 | 
						|
        self.collisionNode.setCollideGeom(0)
 | 
						|
 | 
						|
    def collideWithGeom(self):
 | 
						|
        # The into collide mask is the bit pattern colliders look at
 | 
						|
        # when deciding whether or not to test for a collision "into"
 | 
						|
        # this collision solid.  Set to all Off so this collision solid
 | 
						|
        # will not be considered in any collision tests
 | 
						|
        self.collisionNode.setIntoCollideMask(BitMask32().allOff())
 | 
						|
        # The from collide mask is the bit pattern *this* collision solid
 | 
						|
        # compares against the into collide mask of candidate collision solids
 | 
						|
        # Turn this mask all off since we're not testing for collisions against
 | 
						|
        # collision solids
 | 
						|
        self.collisionNode.setFromCollideMask(BitMask32().allOff())
 | 
						|
        # What we want to test against is actual geometry
 | 
						|
        self.collisionNode.setCollideGeom(1)
 | 
						|
 | 
						|
    def collideWithWidget(self):
 | 
						|
        # This collision node should not be tested against by any other
 | 
						|
        # collision solids
 | 
						|
        self.collisionNode.setIntoCollideMask(BitMask32().allOff())
 | 
						|
        # This collision node will test for collisions with any collision
 | 
						|
        # solids with a bit mask set to 0x80000000
 | 
						|
        mask = BitMask32()
 | 
						|
        mask.setBit(31)
 | 
						|
        self.collisionNode.setFromCollideMask(mask)
 | 
						|
        # But in this case, don't test against actual geometry
 | 
						|
        self.collisionNode.setCollideGeom(0)
 | 
						|
 | 
						|
    def addUnpickable(self, item):
 | 
						|
        if item not in self.unpickable:
 | 
						|
            self.unpickable.append(item)
 | 
						|
 | 
						|
    def removeUnpickable(self, item):
 | 
						|
        if item in self.unpickable:
 | 
						|
            self.unpickable.remove(item)
 | 
						|
 | 
						|
    def setCurrentIndex(self, index):
 | 
						|
        if (index < 0) or (index >= self.getNumEntries()):
 | 
						|
            self.index = -1
 | 
						|
        else:
 | 
						|
            self.index = index
 | 
						|
 | 
						|
    def setCurrentEntry(self, entry):
 | 
						|
        self.entry = entry
 | 
						|
 | 
						|
    def getCurrentEntry(self):
 | 
						|
        return self.entry
 | 
						|
 | 
						|
    def isEntryBackfacing(self, entry):
 | 
						|
        # If dot product of collision point surface normal and
 | 
						|
        # ray from camera to collision point is positive, we are
 | 
						|
        # looking at the backface of the polygon
 | 
						|
        if not entry.hasSurfaceNormal():
 | 
						|
            # Well, no way to tell.  Assume we're not backfacing.
 | 
						|
            return 0
 | 
						|
 | 
						|
        fromNodePath = entry.getFromNodePath()
 | 
						|
        v = Vec3(entry.getSurfacePoint(fromNodePath))
 | 
						|
        n = entry.getSurfaceNormal(fromNodePath)
 | 
						|
        # Convert to camera space for backfacing test
 | 
						|
        if self.collisionNodePath.getParent() != base.cam:
 | 
						|
            # Problem: assumes base.cam is the camera in question
 | 
						|
            p2cam = self.collisionNodePath.getParent().getMat(base.cam)
 | 
						|
            v = Vec3(p2cam.xformPoint(v))
 | 
						|
            n = p2cam.xformVec(n)
 | 
						|
        # Normalize and check angle between to vectors
 | 
						|
        v.normalize()
 | 
						|
        return v.dot(n) >= 0
 | 
						|
 | 
						|
    def findNextCollisionEntry(self, skipFlags = SKIP_NONE):
 | 
						|
        return self.findCollisionEntry(skipFlags, self.index + 1)
 | 
						|
 | 
						|
    def findCollisionEntry(self, skipFlags = SKIP_NONE, startIndex = 0 ):
 | 
						|
        # Init self.index and self.entry
 | 
						|
        self.setCurrentIndex(-1)
 | 
						|
        self.setCurrentEntry(None)
 | 
						|
        # Pick out the closest object that isn't a widget
 | 
						|
        for i in range(startIndex,self.getNumEntries()):
 | 
						|
            entry = self.getEntry(i)
 | 
						|
            nodePath = entry.getIntoNodePath()
 | 
						|
            if (skipFlags & SKIP_HIDDEN) and nodePath.isHidden():
 | 
						|
                # Skip if hidden node
 | 
						|
                pass
 | 
						|
            elif (skipFlags & SKIP_BACKFACE) and self.isEntryBackfacing(entry):
 | 
						|
                # Skip, if backfacing poly
 | 
						|
                pass
 | 
						|
            elif ((skipFlags & SKIP_CAMERA) and
 | 
						|
                  (camera in nodePath.getAncestry())):
 | 
						|
                # Skip if parented to a camera.
 | 
						|
                pass
 | 
						|
            # Can pick unpickable, use the first visible node
 | 
						|
            elif ((skipFlags & SKIP_UNPICKABLE) and
 | 
						|
                  (nodePath.getName() in self.unpickable)):
 | 
						|
                # Skip if in unpickable list
 | 
						|
                pass
 | 
						|
            else:
 | 
						|
                self.setCurrentIndex(i)
 | 
						|
                self.setCurrentEntry(entry)
 | 
						|
                break
 | 
						|
        return self.getCurrentEntry()
 | 
						|
 | 
						|
class SelectionRay(SelectionQueue):
 | 
						|
    def __init__(self, parentNP = render):
 | 
						|
        # Initialize the superclass
 | 
						|
        SelectionQueue.__init__(self, parentNP)
 | 
						|
        self.addCollider(CollisionRay())
 | 
						|
    
 | 
						|
    def pick(self, targetNodePath, xy = None):
 | 
						|
        # Determine ray direction based upon the mouse coordinates
 | 
						|
        if xy:
 | 
						|
            mx = xy[0]
 | 
						|
            my = xy[1]
 | 
						|
        elif direct:
 | 
						|
            mx = SEditor.dr.mouseX
 | 
						|
            my = SEditor.dr.mouseY
 | 
						|
        else:
 | 
						|
            if not base.mouseWatcherNode.hasMouse():
 | 
						|
                # No mouse in window.
 | 
						|
                self.clearEntries()
 | 
						|
                return
 | 
						|
            mx = base.mouseWatcherNode.getMouseX()
 | 
						|
            my = base.mouseWatcherNode.getMouseY()
 | 
						|
        #base.mouseWatcherNode.setDisplayRegion(base.win.getDisplayRegion(0))
 | 
						|
        #mx = base.mouseWatcherNode.getMouseX()+1
 | 
						|
        #my = base.mouseWatcherNode.getMouseY()+1
 | 
						|
        #print base.camNode.getName()
 | 
						|
        #print "Arrived X" + str(mx) + " Arrived Y " + str(my) 
 | 
						|
        self.collider.setFromLens( base.camNode, mx, my )
 | 
						|
 | 
						|
        self.ct.traverse( targetNodePath )
 | 
						|
        self.sortEntries()
 | 
						|
 | 
						|
 | 
						|
    def pickBitMask(self, bitMask = BitMask32.allOff(),
 | 
						|
                    targetNodePath = render,
 | 
						|
                    skipFlags = SKIP_ALL ):
 | 
						|
        self.collideWithBitMask(bitMask)
 | 
						|
        self.pick(targetNodePath)
 | 
						|
        # Determine collision entry
 | 
						|
        return self.findCollisionEntry(skipFlags)
 | 
						|
 | 
						|
    def pickGeom(self, targetNodePath = render, skipFlags = SKIP_ALL,
 | 
						|
                 xy = None):
 | 
						|
        self.collideWithGeom()
 | 
						|
        self.pick(targetNodePath, xy = xy)
 | 
						|
        # Determine collision entry
 | 
						|
        return self.findCollisionEntry(skipFlags)
 | 
						|
 | 
						|
    def pickWidget(self, targetNodePath = render, skipFlags = SKIP_NONE ):
 | 
						|
        self.collideWithWidget()
 | 
						|
        self.pick(targetNodePath)
 | 
						|
        # Determine collision entry
 | 
						|
        return self.findCollisionEntry(skipFlags)
 | 
						|
 | 
						|
    def pick3D(self, targetNodePath, origin, dir):
 | 
						|
        # Determine ray direction based upon the mouse coordinates
 | 
						|
        self.collider.setOrigin( origin )
 | 
						|
        self.collider.setDirection( dir )
 | 
						|
        self.ct.traverse( targetNodePath )
 | 
						|
        self.sortEntries()
 | 
						|
        
 | 
						|
    def pickGeom3D(self, targetNodePath = render,
 | 
						|
                   origin = Point3(0), dir = Vec3(0,0,-1),
 | 
						|
                   skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
 | 
						|
        self.collideWithGeom()
 | 
						|
        self.pick3D(targetNodePath, origin, dir)
 | 
						|
        # Determine collision entry
 | 
						|
        return self.findCollisionEntry(skipFlags)
 | 
						|
 | 
						|
    def pickBitMask3D(self, bitMask = BitMask32.allOff(),
 | 
						|
                      targetNodePath = render,
 | 
						|
                      origin = Point3(0), dir = Vec3(0,0,-1),
 | 
						|
                      skipFlags = SKIP_ALL ):
 | 
						|
        self.collideWithBitMask(bitMask)
 | 
						|
        self.pick3D(targetNodePath, origin, dir)
 | 
						|
        # Determine collision entry
 | 
						|
        return self.findCollisionEntry(skipFlags)
 | 
						|
 | 
						|
 | 
						|
class SelectionSegment(SelectionQueue):
 | 
						|
    # Like a selection ray but with two endpoints instead of an endpoint
 | 
						|
    # and a direction
 | 
						|
    def __init__(self, parentNP = render, numSegments = 1):
 | 
						|
        # Initialize the superclass
 | 
						|
        SelectionQueue.__init__(self, parentNP)
 | 
						|
        self.colliders = []
 | 
						|
        self.numColliders = 0
 | 
						|
        for i in range(numSegments):
 | 
						|
            self.addCollider(CollisionSegment())
 | 
						|
    
 | 
						|
    def addCollider(self, collider):
 | 
						|
        # Record new collision object
 | 
						|
        self.colliders.append(collider)
 | 
						|
        # Add the collider to the collision Node
 | 
						|
        self.collisionNode.addSolid( collider )
 | 
						|
        self.numColliders += 1
 | 
						|
 | 
						|
    def pickGeom(self, targetNodePath = render, endPointList = [],
 | 
						|
                 skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
 | 
						|
        self.collideWithGeom()
 | 
						|
        for i in range(min(len(endPointList), self.numColliders)):
 | 
						|
            pointA, pointB = endPointList[i]
 | 
						|
            collider = self.colliders[i]
 | 
						|
            collider.setPointA( pointA )
 | 
						|
            collider.setPointB( pointB )
 | 
						|
        self.ct.traverse( targetNodePath )
 | 
						|
        # Determine collision entry
 | 
						|
        return self.findCollisionEntry(skipFlags)
 | 
						|
 | 
						|
    def pickBitMask(self, bitMask = BitMask32.allOff(),
 | 
						|
                    targetNodePath = render, endPointList = [],
 | 
						|
                 skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
 | 
						|
        self.collideWithBitMask(bitMask)
 | 
						|
        for i in range(min(len(endPointList), self.numColliders)):
 | 
						|
            pointA, pointB = endPointList[i]
 | 
						|
            collider = self.colliders[i]
 | 
						|
            collider.setPointA( pointA )
 | 
						|
            collider.setPointB( pointB )
 | 
						|
        self.ct.traverse( targetNodePath )
 | 
						|
        # Determine collision entry
 | 
						|
        return self.findCollisionEntry(skipFlags)
 | 
						|
 | 
						|
 | 
						|
class SelectionSphere(SelectionQueue):
 | 
						|
    # Wrapper around collision sphere
 | 
						|
    def __init__(self, parentNP = render, numSpheres = 1):
 | 
						|
        # Initialize the superclass
 | 
						|
        SelectionQueue.__init__(self, parentNP)
 | 
						|
        self.colliders = []
 | 
						|
        self.numColliders = 0
 | 
						|
        for i in range(numSpheres):
 | 
						|
            self.addCollider(CollisionSphere(Point3(0), 1))
 | 
						|
        
 | 
						|
    def addCollider(self, collider):
 | 
						|
        # Record new collision object
 | 
						|
        self.colliders.append(collider)
 | 
						|
        # Add the collider to the collision Node
 | 
						|
        self.collisionNode.addSolid( collider )
 | 
						|
        self.numColliders += 1
 | 
						|
    
 | 
						|
    def setCenter(self, i, center):
 | 
						|
        c = self.colliders[i]
 | 
						|
        c.setCenter(center)
 | 
						|
    
 | 
						|
    def setRadius(self, i, radius):
 | 
						|
        c = self.colliders[i]
 | 
						|
        c.setRadius(radius)
 | 
						|
    
 | 
						|
    def setCenterRadius(self, i, center, radius):
 | 
						|
        c = self.colliders[i]
 | 
						|
        c.setCenter(center)
 | 
						|
        c.setRadius(radius)
 | 
						|
    
 | 
						|
    def isEntryBackfacing(self, entry):
 | 
						|
        # If dot product of collision point surface normal and
 | 
						|
        # ray from sphere origin to collision point is positive, 
 | 
						|
        # center is on the backside of the polygon
 | 
						|
        fromNodePath = entry.getFromNodePath()
 | 
						|
        v = Vec3(entry.getSurfacePoint(fromNodePath) -
 | 
						|
                 entry.getFrom().getCenter())
 | 
						|
        n = entry.getSurfaceNormal(fromNodePath)
 | 
						|
        # If points almost on top of each other, reject face
 | 
						|
        # (treat as backfacing)
 | 
						|
        if v.length() < 0.05:
 | 
						|
            return 1
 | 
						|
        # Normalize and check angle between to vectors
 | 
						|
        v.normalize()
 | 
						|
        return v.dot(n) >= 0
 | 
						|
 | 
						|
 | 
						|
    def pick(self, targetNodePath, skipFlags):
 | 
						|
        self.ct.traverse( targetNodePath )
 | 
						|
        self.sortEntries()
 | 
						|
        return self.findCollisionEntry(skipFlags)
 | 
						|
 | 
						|
    def pickGeom(self, targetNodePath = render,
 | 
						|
                 skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
 | 
						|
        self.collideWithGeom()
 | 
						|
        return self.pick(targetNodePath, skipFlags)
 | 
						|
    
 | 
						|
    def pickBitMask(self, bitMask = BitMask32.allOff(),
 | 
						|
                    targetNodePath = render,
 | 
						|
                    skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
 | 
						|
        self.collideWithBitMask(bitMask)
 | 
						|
        return self.pick(targetNodePath, skipFlags)
 | 
						|
 |