seSelection.py 25 KB

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