seSelection.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. #################################################################
  2. # seSelection.py
  3. # Originally from DirectSelection.py
  4. # Altered by Yi-Hong Lin, [email protected], 2004
  5. #
  6. # We didn't change anything essential.
  7. # Just because we customized the seSession from DirectSession,
  8. # So we need related files can follow the change.
  9. # However, we don't want to change anything inside the original directool
  10. # to let them can work with our scene editor.
  11. # (If we do change original directools, it will force user has to install the latest version of OUR Panda)
  12. #
  13. #################################################################
  14. from pandac.PandaModules import GeomNode
  15. from direct.directtools.DirectGlobals import *
  16. from direct.directtools.DirectUtil import *
  17. from seGeometry import *
  18. from direct.showbase.DirectObject import *
  19. from quad import *
  20. COA_ORIGIN = 0
  21. COA_CENTER = 1
  22. # MRM: To do: handle broken node paths in selected and deselected dicts
  23. class DirectNodePath(NodePath):
  24. # A node path augmented with info, bounding box, and utility methods
  25. def __init__(self, nodePath):
  26. # Initialize the superclass
  27. NodePath.__init__(self)
  28. self.assign(nodePath)
  29. # Create a bounding box
  30. self.bbox = DirectBoundingBox(self)
  31. center = self.bbox.getCenter()
  32. # Create matrix to hold the offset between the nodepath
  33. # and its center of action (COA)
  34. self.mCoa2Dnp = Mat4(Mat4.identMat())
  35. if SEditor.coaMode == COA_CENTER:
  36. self.mCoa2Dnp.setRow(3, Vec4(center[0], center[1], center[2], 1))
  37. # Transform from nodePath to widget
  38. self.tDnp2Widget = TransformState.makeIdentity()
  39. def highlight(self):
  40. self.bbox.show()
  41. def dehighlight(self):
  42. self.bbox.hide()
  43. def getCenter(self):
  44. return self.bbox.getCenter()
  45. def getRadius(self):
  46. return self.bbox.getRadius()
  47. def getMin(self):
  48. return self.bbox.getMin()
  49. def getMax(self):
  50. return self.bbox.getMax()
  51. class SelectedNodePaths(DirectObject):
  52. def __init__(self):
  53. self.reset()
  54. def reset(self):
  55. self.selectedDict = {}
  56. self.deselectedDict = {}
  57. __builtins__["last"] = self.last = None
  58. def select(self, nodePath, fMultiSelect = 0):
  59. """ Select the specified node path. Multiselect as required """
  60. # Do nothing if nothing selected
  61. if not nodePath:
  62. print 'Nothing selected!!'
  63. return None
  64. # Reset selected objects and highlight if multiSelect is false
  65. if not fMultiSelect:
  66. self.deselectAll()
  67. # Get this pointer
  68. id = nodePath.id()
  69. # First see if its already in the selected dictionary
  70. dnp = self.getSelectedDict(id)
  71. # If so, we're done
  72. if not dnp:
  73. # See if it is in the deselected dictionary
  74. dnp = self.getDeselectedDict(id)
  75. if dnp:
  76. # Remove it from the deselected dictionary
  77. del(self.deselectedDict[id])
  78. # Show its bounding box
  79. dnp.highlight()
  80. else:
  81. # Didn't find it, create a new selectedNodePath instance
  82. dnp = DirectNodePath(nodePath)
  83. # Show its bounding box
  84. dnp.highlight()
  85. # Add it to the selected dictionary
  86. self.selectedDict[dnp.id()] = dnp
  87. # And update last
  88. __builtins__["last"] = self.last = dnp
  89. return dnp
  90. def deselect(self, nodePath):
  91. """ Deselect the specified node path """
  92. # Get this pointer
  93. id = nodePath.id()
  94. # See if it is in the selected dictionary
  95. dnp = self.getSelectedDict(id)
  96. if dnp:
  97. # It was selected:
  98. # Hide its bounding box
  99. dnp.dehighlight()
  100. # Remove it from the selected dictionary
  101. del(self.selectedDict[id])
  102. # And keep track of it in the deselected dictionary
  103. self.deselectedDict[id] = dnp
  104. # Send a message
  105. messenger.send('DIRECT_deselectedNodePath', [dnp])
  106. return dnp
  107. def getSelectedAsList(self):
  108. """
  109. Return a list of all selected node paths. No verification of
  110. connectivity is performed on the members of the list
  111. """
  112. return self.selectedDict.values()[:]
  113. def __getitem__(self,index):
  114. return self.getSelectedAsList()[index]
  115. def getSelectedDict(self, id):
  116. """
  117. Search selectedDict for node path, try to repair broken node paths.
  118. """
  119. dnp = self.selectedDict.get(id, None)
  120. if dnp:
  121. return dnp
  122. else:
  123. # Not in selected dictionary
  124. return None
  125. def getDeselectedAsList(self):
  126. return self.deselectedDict.values()[:]
  127. def getDeselectedDict(self, id):
  128. """
  129. Search deselectedDict for node path, try to repair broken node paths.
  130. """
  131. dnp = self.deselectedDict.get(id, None)
  132. if dnp:
  133. # Yes
  134. return dnp
  135. else:
  136. # Not in deselected dictionary
  137. return None
  138. def forEachSelectedNodePathDo(self, func):
  139. """
  140. Perform given func on selected node paths. No node path
  141. connectivity verification performed
  142. """
  143. selectedNodePaths = self.getSelectedAsList()
  144. for nodePath in selectedNodePaths:
  145. func(nodePath)
  146. def forEachDeselectedNodePathDo(self, func):
  147. """
  148. Perform given func on deselected node paths. No node path
  149. connectivity verification performed
  150. """
  151. deselectedNodePaths = self.getDeselectedAsList()
  152. for nodePath in deselectedNodePaths:
  153. func(nodePath)
  154. def getWrtAll(self):
  155. self.forEachSelectedNodePathDo(self.getWrt)
  156. def getWrt(self, nodePath):
  157. nodePath.tDnp2Widget = nodePath.getTransform(SEditor.widget)
  158. def moveWrtWidgetAll(self):
  159. self.forEachSelectedNodePathDo(self.moveWrtWidget)
  160. def moveWrtWidget(self, nodePath):
  161. nodePath.setTransform(SEditor.widget, nodePath.tDnp2Widget)
  162. def deselectAll(self):
  163. self.forEachSelectedNodePathDo(self.deselect)
  164. def highlightAll(self):
  165. self.forEachSelectedNodePathDo(DirectNodePath.highlight)
  166. def dehighlightAll(self):
  167. self.forEachSelectedNodePathDo(DirectNodePath.dehighlight)
  168. def removeSelected(self):
  169. selected = self.last
  170. if selected:
  171. selected.remove()
  172. __builtins__["last"] = self.last = None
  173. def removeAll(self):
  174. # Remove all selected nodePaths from the Scene Graph
  175. self.forEachSelectedNodePathDo(NodePath.remove)
  176. def toggleVisSelected(self):
  177. selected = self.last
  178. # Toggle visibility of selected node paths
  179. if selected:
  180. selected.toggleVis()
  181. def toggleVisAll(self):
  182. # Toggle viz for all selected node paths
  183. self.forEachSelectedNodePathDo(NodePath.toggleVis)
  184. def isolateSelected(self):
  185. selected = self.last
  186. if selected:
  187. selected.isolate()
  188. def getDirectNodePath(self, nodePath):
  189. # Get this pointer
  190. id = nodePath.id()
  191. # First check selected dict
  192. dnp = self.getSelectedDict(id)
  193. if dnp:
  194. return dnp
  195. # Otherwise return result of deselected search
  196. return self.getDeselectedDict(id)
  197. def getNumSelected(self):
  198. return len(self.selectedDict.keys())
  199. class DirectBoundingBox:
  200. def __init__(self, nodePath):
  201. # Record the node path
  202. self.nodePath = nodePath
  203. # Compute bounds, min, max, etc.
  204. self.computeTightBounds()
  205. # Generate the bounding box
  206. self.lines = self.createBBoxLines()
  207. def computeTightBounds(self):
  208. # Compute bounding box using tighter calcTightBounds function
  209. # Need to clear out existing transform on node path
  210. tMat = Mat4()
  211. tMat.assign(self.nodePath.getMat())
  212. self.nodePath.clearMat()
  213. # Get bounds
  214. self.min = Point3(0)
  215. self.max = Point3(0)
  216. self.nodePath.calcTightBounds(self.min,self.max)
  217. # Calc center and radius
  218. self.center = Point3((self.min + self.max)/2.0)
  219. self.radius = Vec3(self.max - self.min).length()
  220. # Restore transform
  221. self.nodePath.setMat(tMat)
  222. del tMat
  223. def computeBounds(self):
  224. self.bounds = self.getBounds()
  225. if self.bounds.isEmpty() or self.bounds.isInfinite():
  226. self.center = Point3(0)
  227. self.radius = 1.0
  228. else:
  229. self.center = self.bounds.getCenter()
  230. self.radius = self.bounds.getRadius()
  231. self.min = Point3(self.center - Point3(self.radius))
  232. self.max = Point3(self.center + Point3(self.radius))
  233. def createBBoxLines(self):
  234. # Create a line segments object for the bbox
  235. lines = LineNodePath(hidden)
  236. lines.node().setName('bboxLines')
  237. lines.setColor( VBase4( 1., 0., 0., 1. ) )
  238. lines.setThickness( 0.5 )
  239. minX = self.min[0]
  240. minY = self.min[1]
  241. minZ = self.min[2]
  242. maxX = self.max[0]
  243. maxY = self.max[1]
  244. maxZ = self.max[2]
  245. # Bottom face
  246. lines.moveTo( minX, minY, minZ )
  247. lines.drawTo( maxX, minY, minZ )
  248. lines.drawTo( maxX, maxY, minZ )
  249. lines.drawTo( minX, maxY, minZ )
  250. lines.drawTo( minX, minY, minZ )
  251. # Front Edge/Top face
  252. lines.drawTo( minX, minY, maxZ )
  253. lines.drawTo( maxX, minY, maxZ )
  254. lines.drawTo( maxX, maxY, maxZ )
  255. lines.drawTo( minX, maxY, maxZ )
  256. lines.drawTo( minX, minY, maxZ )
  257. # Three remaining edges
  258. lines.moveTo( maxX, minY, minZ )
  259. lines.drawTo( maxX, minY, maxZ )
  260. lines.moveTo( maxX, maxY, minZ )
  261. lines.drawTo( maxX, maxY, maxZ )
  262. lines.moveTo( minX, maxY, minZ )
  263. lines.drawTo( minX, maxY, maxZ )
  264. # Create and return bbox lines
  265. lines.create()
  266. # Make sure bbox is never lit or drawn in wireframe
  267. useDirectRenderStyle(lines)
  268. return lines
  269. def updateBBoxLines(self):
  270. ls = self.lines.lineSegs
  271. minX = self.min[0]
  272. minY = self.min[1]
  273. minZ = self.min[2]
  274. maxX = self.max[0]
  275. maxY = self.max[1]
  276. maxZ = self.max[2]
  277. # Bottom face
  278. ls.setVertex( 0, minX, minY, minZ )
  279. ls.setVertex( 1, maxX, minY, minZ )
  280. ls.setVertex( 2, maxX, maxY, minZ )
  281. ls.setVertex( 3, minX, maxY, minZ )
  282. ls.setVertex( 4, minX, minY, minZ )
  283. # Front Edge/Top face
  284. ls.setVertex( 5, minX, minY, maxZ )
  285. ls.setVertex( 6, maxX, minY, maxZ )
  286. ls.setVertex( 7, maxX, maxY, maxZ )
  287. ls.setVertex( 8, minX, maxY, maxZ )
  288. ls.setVertex( 9, minX, minY, maxZ )
  289. # Three remaining edges
  290. ls.setVertex( 10, maxX, minY, minZ )
  291. ls.setVertex( 11, maxX, minY, maxZ )
  292. ls.setVertex( 12, maxX, maxY, minZ )
  293. ls.setVertex( 13, maxX, maxY, maxZ )
  294. ls.setVertex( 14, minX, maxY, minZ )
  295. ls.setVertex( 15, minX, maxY, maxZ )
  296. def getBounds(self):
  297. # Get a node path's bounds
  298. nodeBounds = BoundingSphere()
  299. nodeBounds.extendBy(self.nodePath.node().getInternalBound())
  300. for child in self.nodePath.getChildren():
  301. nodeBounds.extendBy(child.getBounds())
  302. return nodeBounds.makeCopy()
  303. def show(self):
  304. self.lines.reparentTo(self.nodePath)
  305. def hide(self):
  306. self.lines.reparentTo(hidden)
  307. def getCenter(self):
  308. return self.center
  309. def getRadius(self):
  310. return self.radius
  311. def getMin(self):
  312. return self.min
  313. def getMax(self):
  314. return self.max
  315. def vecAsString(self, vec):
  316. return '%.2f %.2f %.2f' % (vec[0], vec[1], vec[2])
  317. def __repr__(self):
  318. return (`self.__class__` +
  319. '\nNodePath:\t%s\n' % self.nodePath.getName() +
  320. 'Min:\t\t%s\n' % self.vecAsString(self.min) +
  321. 'Max:\t\t%s\n' % self.vecAsString(self.max) +
  322. 'Center:\t\t%s\n' % self.vecAsString(self.center) +
  323. 'Radius:\t\t%.2f' % self.radius
  324. )
  325. class SelectionQueue(CollisionHandlerQueue):
  326. def __init__(self, parentNP = render):
  327. # Initialize the superclass
  328. CollisionHandlerQueue.__init__(self)
  329. # Current index and entry in collision queue
  330. self.index = -1
  331. self.entry = None
  332. self.skipFlags = SKIP_NONE
  333. # Create a collision node path attached to the given NP
  334. self.collisionNodePath = NodePath(CollisionNode("collisionNP"))
  335. self.setParentNP(parentNP)
  336. # Don't pay the penalty of drawing this collision ray
  337. self.collisionNodePath.hide()
  338. self.collisionNode = self.collisionNodePath.node()
  339. # Intersect with geometry to begin with
  340. self.collideWithGeom()
  341. # And a traverser to do the actual collision tests
  342. self.ct = CollisionTraverser()
  343. # Let the traverser know about the collision node and the queue
  344. #Manakel 2/12/2005: replace CollisionNode by its nodepath
  345. self.ct.addCollider(self.collisionNodePath, self)
  346. # List of objects that can't be selected
  347. self.unpickable = UNPICKABLE
  348. # Derived class must add Collider to complete initialization
  349. def setParentNP(self, parentNP):
  350. # Update collisionNodePath's parent
  351. self.collisionNodePath.reparentTo(parentNP)
  352. def addCollider(self, collider):
  353. # Inherited class must call this function to specify collider object
  354. # Record collision object
  355. self.collider = collider
  356. # Add the collider to the collision Node
  357. self.collisionNode.addSolid( self.collider )
  358. def collideWithBitMask(self, bitMask):
  359. # The into collide mask is the bit pattern colliders look at
  360. # when deciding whether or not to test for a collision "into"
  361. # this collision solid. Set to all Off so this collision solid
  362. # will not be considered in any collision tests
  363. self.collisionNode.setIntoCollideMask(BitMask32().allOff())
  364. # The from collide mask is the bit pattern *this* collision solid
  365. # compares against the into collide mask of candidate collision solids
  366. # Turn this mask all off since we're not testing for collisions against
  367. # collision solids
  368. self.collisionNode.setFromCollideMask(bitMask)
  369. def collideWithGeom(self):
  370. # The into collide mask is the bit pattern colliders look at
  371. # when deciding whether or not to test for a collision "into"
  372. # this collision solid. Set to all Off so this collision solid
  373. # will not be considered in any collision tests
  374. self.collisionNode.setIntoCollideMask(BitMask32().allOff())
  375. # The from collide mask is the bit pattern *this* collision solid
  376. # compares against the into collide mask of candidate collision solids
  377. # Turn this mask all off since we're not testing for collisions against
  378. # collision solids, but we do want to test against geometry
  379. self.collisionNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
  380. def collideWithWidget(self):
  381. # This collision node should not be tested against by any other
  382. # collision solids
  383. self.collisionNode.setIntoCollideMask(BitMask32().allOff())
  384. # This collision node will test for collisions with any collision
  385. # solids with a bit mask set to 0x80000000
  386. mask = BitMask32()
  387. mask.setBit(31)
  388. self.collisionNode.setFromCollideMask(mask)
  389. def addUnpickable(self, item):
  390. if item not in self.unpickable:
  391. self.unpickable.append(item)
  392. def removeUnpickable(self, item):
  393. if item in self.unpickable:
  394. self.unpickable.remove(item)
  395. def setCurrentIndex(self, index):
  396. if (index < 0) or (index >= self.getNumEntries()):
  397. self.index = -1
  398. else:
  399. self.index = index
  400. def setCurrentEntry(self, entry):
  401. self.entry = entry
  402. def getCurrentEntry(self):
  403. return self.entry
  404. def isEntryBackfacing(self, entry):
  405. # If dot product of collision point surface normal and
  406. # ray from camera to collision point is positive, we are
  407. # looking at the backface of the polygon
  408. if not entry.hasSurfaceNormal():
  409. # Well, no way to tell. Assume we're not backfacing.
  410. return 0
  411. fromNodePath = entry.getFromNodePath()
  412. v = Vec3(entry.getSurfacePoint(fromNodePath))
  413. n = entry.getSurfaceNormal(fromNodePath)
  414. # Convert to camera space for backfacing test
  415. if self.collisionNodePath.getParent() != base.cam:
  416. # Problem: assumes base.cam is the camera in question
  417. p2cam = self.collisionNodePath.getParent().getMat(base.cam)
  418. v = Vec3(p2cam.xformPoint(v))
  419. n = p2cam.xformVec(n)
  420. # Normalize and check angle between to vectors
  421. v.normalize()
  422. return v.dot(n) >= 0
  423. def findNextCollisionEntry(self, skipFlags = SKIP_NONE):
  424. return self.findCollisionEntry(skipFlags, self.index + 1)
  425. def findCollisionEntry(self, skipFlags = SKIP_NONE, startIndex = 0 ):
  426. # Init self.index and self.entry
  427. self.setCurrentIndex(-1)
  428. self.setCurrentEntry(None)
  429. # Pick out the closest object that isn't a widget
  430. for i in range(startIndex,self.getNumEntries()):
  431. entry = self.getEntry(i)
  432. nodePath = entry.getIntoNodePath()
  433. if (skipFlags & SKIP_HIDDEN) and nodePath.isHidden():
  434. # Skip if hidden node
  435. pass
  436. elif (skipFlags & SKIP_BACKFACE) and self.isEntryBackfacing(entry):
  437. # Skip, if backfacing poly
  438. pass
  439. elif ((skipFlags & SKIP_CAMERA) and
  440. (camera in nodePath.getAncestors())):
  441. # Skip if parented to a camera.
  442. pass
  443. # Can pick unpickable, use the first visible node
  444. elif ((skipFlags & SKIP_UNPICKABLE) and
  445. (nodePath.getName() in self.unpickable)):
  446. # Skip if in unpickable list
  447. pass
  448. else:
  449. self.setCurrentIndex(i)
  450. self.setCurrentEntry(entry)
  451. break
  452. return self.getCurrentEntry()
  453. class SelectionRay(SelectionQueue):
  454. def __init__(self, parentNP = render):
  455. # Initialize the superclass
  456. SelectionQueue.__init__(self, parentNP)
  457. self.addCollider(CollisionRay())
  458. def pick(self, targetNodePath, xy = None):
  459. # Determine ray direction based upon the mouse coordinates
  460. if xy:
  461. mx = xy[0]
  462. my = xy[1]
  463. elif direct:
  464. mx = SEditor.dr.mouseX
  465. my = SEditor.dr.mouseY
  466. else:
  467. if not base.mouseWatcherNode.hasMouse():
  468. # No mouse in window.
  469. self.clearEntries()
  470. return
  471. mx = base.mouseWatcherNode.getMouseX()
  472. my = base.mouseWatcherNode.getMouseY()
  473. #base.mouseWatcherNode.setDisplayRegion(base.win.getDisplayRegion(0))
  474. #mx = base.mouseWatcherNode.getMouseX()+1
  475. #my = base.mouseWatcherNode.getMouseY()+1
  476. #print base.camNode.getName()
  477. #print "Arrived X" + str(mx) + " Arrived Y " + str(my)
  478. self.collider.setFromLens( base.camNode, mx, my )
  479. self.ct.traverse( targetNodePath )
  480. self.sortEntries()
  481. def pickBitMask(self, bitMask = BitMask32.allOff(),
  482. targetNodePath = render,
  483. skipFlags = SKIP_ALL ):
  484. self.collideWithBitMask(bitMask)
  485. self.pick(targetNodePath)
  486. # Determine collision entry
  487. return self.findCollisionEntry(skipFlags)
  488. def pickGeom(self, targetNodePath = render, skipFlags = SKIP_ALL,
  489. xy = None):
  490. self.collideWithGeom()
  491. self.pick(targetNodePath, xy = xy)
  492. # Determine collision entry
  493. return self.findCollisionEntry(skipFlags)
  494. def pickWidget(self, targetNodePath = render, skipFlags = SKIP_NONE ):
  495. self.collideWithWidget()
  496. self.pick(targetNodePath)
  497. # Determine collision entry
  498. return self.findCollisionEntry(skipFlags)
  499. def pick3D(self, targetNodePath, origin, dir):
  500. # Determine ray direction based upon the mouse coordinates
  501. self.collider.setOrigin( origin )
  502. self.collider.setDirection( dir )
  503. self.ct.traverse( targetNodePath )
  504. self.sortEntries()
  505. def pickGeom3D(self, targetNodePath = render,
  506. origin = Point3(0), dir = Vec3(0,0,-1),
  507. skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
  508. self.collideWithGeom()
  509. self.pick3D(targetNodePath, origin, dir)
  510. # Determine collision entry
  511. return self.findCollisionEntry(skipFlags)
  512. def pickBitMask3D(self, bitMask = BitMask32.allOff(),
  513. targetNodePath = render,
  514. origin = Point3(0), dir = Vec3(0,0,-1),
  515. skipFlags = SKIP_ALL ):
  516. self.collideWithBitMask(bitMask)
  517. self.pick3D(targetNodePath, origin, dir)
  518. # Determine collision entry
  519. return self.findCollisionEntry(skipFlags)
  520. class SelectionSegment(SelectionQueue):
  521. # Like a selection ray but with two endpoints instead of an endpoint
  522. # and a direction
  523. def __init__(self, parentNP = render, numSegments = 1):
  524. # Initialize the superclass
  525. SelectionQueue.__init__(self, parentNP)
  526. self.colliders = []
  527. self.numColliders = 0
  528. for i in range(numSegments):
  529. self.addCollider(CollisionSegment())
  530. def addCollider(self, collider):
  531. # Record new collision object
  532. self.colliders.append(collider)
  533. # Add the collider to the collision Node
  534. self.collisionNode.addSolid( collider )
  535. self.numColliders += 1
  536. def pickGeom(self, targetNodePath = render, endPointList = [],
  537. skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
  538. self.collideWithGeom()
  539. for i in range(min(len(endPointList), self.numColliders)):
  540. pointA, pointB = endPointList[i]
  541. collider = self.colliders[i]
  542. collider.setPointA( pointA )
  543. collider.setPointB( pointB )
  544. self.ct.traverse( targetNodePath )
  545. # Determine collision entry
  546. return self.findCollisionEntry(skipFlags)
  547. def pickBitMask(self, bitMask = BitMask32.allOff(),
  548. targetNodePath = render, endPointList = [],
  549. skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
  550. self.collideWithBitMask(bitMask)
  551. for i in range(min(len(endPointList), self.numColliders)):
  552. pointA, pointB = endPointList[i]
  553. collider = self.colliders[i]
  554. collider.setPointA( pointA )
  555. collider.setPointB( pointB )
  556. self.ct.traverse( targetNodePath )
  557. # Determine collision entry
  558. return self.findCollisionEntry(skipFlags)
  559. class SelectionSphere(SelectionQueue):
  560. # Wrapper around collision sphere
  561. def __init__(self, parentNP = render, numSpheres = 1):
  562. # Initialize the superclass
  563. SelectionQueue.__init__(self, parentNP)
  564. self.colliders = []
  565. self.numColliders = 0
  566. for i in range(numSpheres):
  567. self.addCollider(CollisionSphere(Point3(0), 1))
  568. def addCollider(self, collider):
  569. # Record new collision object
  570. self.colliders.append(collider)
  571. # Add the collider to the collision Node
  572. self.collisionNode.addSolid( collider )
  573. self.numColliders += 1
  574. def setCenter(self, i, center):
  575. c = self.colliders[i]
  576. c.setCenter(center)
  577. def setRadius(self, i, radius):
  578. c = self.colliders[i]
  579. c.setRadius(radius)
  580. def setCenterRadius(self, i, center, radius):
  581. c = self.colliders[i]
  582. c.setCenter(center)
  583. c.setRadius(radius)
  584. def isEntryBackfacing(self, entry):
  585. # If dot product of collision point surface normal and
  586. # ray from sphere origin to collision point is positive,
  587. # center is on the backside of the polygon
  588. fromNodePath = entry.getFromNodePath()
  589. v = Vec3(entry.getSurfacePoint(fromNodePath) -
  590. entry.getFrom().getCenter())
  591. n = entry.getSurfaceNormal(fromNodePath)
  592. # If points almost on top of each other, reject face
  593. # (treat as backfacing)
  594. if v.length() < 0.05:
  595. return 1
  596. # Normalize and check angle between to vectors
  597. v.normalize()
  598. return v.dot(n) >= 0
  599. def pick(self, targetNodePath, skipFlags):
  600. self.ct.traverse( targetNodePath )
  601. self.sortEntries()
  602. return self.findCollisionEntry(skipFlags)
  603. def pickGeom(self, targetNodePath = render,
  604. skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
  605. self.collideWithGeom()
  606. return self.pick(targetNodePath, skipFlags)
  607. def pickBitMask(self, bitMask = BitMask32.allOff(),
  608. targetNodePath = render,
  609. skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
  610. self.collideWithBitMask(bitMask)
  611. return self.pick(targetNodePath, skipFlags)