DirectSelection.py 25 KB

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