DirectSelection.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. from direct.showbase.DirectObject import DirectObject
  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 base.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 base.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 base.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(base.direct.widget)
  164. def moveWrtWidgetAll(self):
  165. self.forEachSelectedNodePathDo(self.moveWrtWidget)
  166. def moveWrtWidget(self, nodePath):
  167. nodePath.setTransform(base.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. self.ct.setRespectPrevTransform(False)
  363. # Let the traverser know about the collision node and the queue
  364. self.ct.addCollider(self.collisionNodePath, self)
  365. # List of objects that can't be selected
  366. self.unpickable = UNPICKABLE
  367. # Derived class must add Collider to complete initialization
  368. def setParentNP(self, parentNP):
  369. # Update collisionNodePath's parent
  370. self.collisionNodePath.reparentTo(parentNP)
  371. def addCollider(self, collider):
  372. # Inherited class must call this function to specify collider object
  373. # Record collision object
  374. self.collider = collider
  375. # Add the collider to the collision Node
  376. self.collisionNode.addSolid(self.collider)
  377. def collideWithBitMask(self, bitMask):
  378. # The into collide mask is the bit pattern colliders look at
  379. # when deciding whether or not to test for a collision "into"
  380. # this collision solid. Set to all Off so this collision solid
  381. # will not be considered in any collision tests
  382. self.collisionNode.setIntoCollideMask(BitMask32().allOff())
  383. # The from collide mask is the bit pattern *this* collision solid
  384. # compares against the into collide mask of candidate collision solids
  385. # Turn this mask all off since we're not testing for collisions against
  386. # collision solids
  387. self.collisionNode.setFromCollideMask(bitMask)
  388. def collideWithGeom(self):
  389. # The into collide mask is the bit pattern colliders look at
  390. # when deciding whether or not to test for a collision "into"
  391. # this collision solid. Set to all Off so this collision solid
  392. # will not be considered in any collision tests
  393. self.collisionNode.setIntoCollideMask(BitMask32().allOff())
  394. # The from collide mask is the bit pattern *this* collision solid
  395. # compares against the into collide mask of candidate collision solids
  396. # Turn this mask all off since we're not testing for collisions against
  397. # collision solids
  398. self.collisionNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
  399. def collideWithWidget(self):
  400. # This collision node should not be tested against by any other
  401. # collision solids
  402. self.collisionNode.setIntoCollideMask(BitMask32().allOff())
  403. # This collision node will test for collisions with any collision
  404. # solids with a bit mask set to 0x80000000
  405. mask = BitMask32()
  406. mask.setWord(0x80000000)
  407. self.collisionNode.setFromCollideMask(mask)
  408. def addUnpickable(self, item):
  409. if item not in self.unpickable:
  410. self.unpickable.append(item)
  411. def removeUnpickable(self, item):
  412. if item in self.unpickable:
  413. self.unpickable.remove(item)
  414. def setCurrentIndex(self, index):
  415. if (index < 0) or (index >= self.getNumEntries()):
  416. self.index = -1
  417. else:
  418. self.index = index
  419. def setCurrentEntry(self, entry):
  420. self.entry = entry
  421. def getCurrentEntry(self):
  422. return self.entry
  423. def isEntryBackfacing(self, entry):
  424. # If dot product of collision point surface normal and
  425. # ray from camera to collision point is positive, we are
  426. # looking at the backface of the polygon
  427. if not entry.hasSurfaceNormal():
  428. # Well, no way to tell. Assume we're not backfacing.
  429. return 0
  430. fromNodePath = entry.getFromNodePath()
  431. v = Vec3(entry.getSurfacePoint(fromNodePath))
  432. n = entry.getSurfaceNormal(fromNodePath)
  433. # Convert to camera space for backfacing test
  434. if self.collisionNodePath.getParent() != base.cam:
  435. # Problem: assumes base.cam is the camera in question
  436. p2cam = self.collisionNodePath.getParent().getMat(base.cam)
  437. v = Vec3(p2cam.xformPoint(v))
  438. n = p2cam.xformVec(n)
  439. # Normalize and check angle between to vectors
  440. v.normalize()
  441. return v.dot(n) >= 0
  442. def findNextCollisionEntry(self, skipFlags = SKIP_NONE):
  443. return self.findCollisionEntry(skipFlags, self.index + 1)
  444. def findCollisionEntry(self, skipFlags = SKIP_NONE, startIndex = 0):
  445. # Init self.index and self.entry
  446. self.setCurrentIndex(-1)
  447. self.setCurrentEntry(None)
  448. # Pick out the closest object that isn't a widget
  449. for i in range(startIndex, self.getNumEntries()):
  450. entry = self.getEntry(i)
  451. nodePath = entry.getIntoNodePath()
  452. if (skipFlags & SKIP_HIDDEN) and nodePath.isHidden():
  453. # Skip if hidden node
  454. pass
  455. elif (skipFlags & SKIP_BACKFACE) and self.isEntryBackfacing(entry):
  456. # Skip, if backfacing poly
  457. pass
  458. elif ((skipFlags & SKIP_CAMERA) and
  459. (camera in nodePath.getAncestry())):
  460. # Skip if parented to a camera.
  461. pass
  462. # Can pick unpickable, use the first visible node
  463. elif ((skipFlags & SKIP_UNPICKABLE) and
  464. (nodePath.getName() in self.unpickable)):
  465. # Skip if in unpickable list
  466. pass
  467. else:
  468. self.setCurrentIndex(i)
  469. self.setCurrentEntry(entry)
  470. break
  471. return self.getCurrentEntry()
  472. class SelectionRay(SelectionQueue):
  473. def __init__(self, parentNP = render):
  474. # Initialize the superclass
  475. SelectionQueue.__init__(self, parentNP)
  476. self.addCollider(CollisionRay())
  477. def pick(self, targetNodePath, xy = None):
  478. # Determine ray direction based upon the mouse coordinates
  479. if xy:
  480. mx = xy[0]
  481. my = xy[1]
  482. elif direct:
  483. mx = base.direct.dr.mouseX
  484. my = base.direct.dr.mouseY
  485. else:
  486. if not base.mouseWatcherNode.hasMouse():
  487. # No mouse in window.
  488. self.clearEntries()
  489. return
  490. mx = base.mouseWatcherNode.getMouseX()
  491. my = base.mouseWatcherNode.getMouseY()
  492. self.collider.setFromLens(base.camNode, mx, my)
  493. self.ct.traverse(targetNodePath)
  494. self.sortEntries()
  495. def pickBitMask(self, bitMask = BitMask32.allOff(),
  496. targetNodePath = render,
  497. skipFlags = SKIP_ALL):
  498. self.collideWithBitMask(bitMask)
  499. self.pick(targetNodePath)
  500. # Determine collision entry
  501. return self.findCollisionEntry(skipFlags)
  502. def pickGeom(self, targetNodePath = render, skipFlags = SKIP_ALL,
  503. xy = None):
  504. self.collideWithGeom()
  505. self.pick(targetNodePath, xy = xy)
  506. # Determine collision entry
  507. return self.findCollisionEntry(skipFlags)
  508. def pickWidget(self, targetNodePath = render, skipFlags = SKIP_NONE):
  509. self.collideWithWidget()
  510. self.pick(targetNodePath)
  511. # Determine collision entry
  512. return self.findCollisionEntry(skipFlags)
  513. def pick3D(self, targetNodePath, origin, dir):
  514. # Determine ray direction based upon the mouse coordinates
  515. self.collider.setOrigin(origin)
  516. self.collider.setDirection(dir)
  517. self.ct.traverse(targetNodePath)
  518. self.sortEntries()
  519. def pickGeom3D(self, targetNodePath = render,
  520. origin = Point3(0), dir = Vec3(0, 0, -1),
  521. skipFlags = SKIP_HIDDEN | SKIP_CAMERA):
  522. self.collideWithGeom()
  523. self.pick3D(targetNodePath, origin, dir)
  524. # Determine collision entry
  525. return self.findCollisionEntry(skipFlags)
  526. def pickBitMask3D(self, bitMask = BitMask32.allOff(),
  527. targetNodePath = render,
  528. origin = Point3(0), dir = Vec3(0, 0, -1),
  529. skipFlags = SKIP_ALL):
  530. self.collideWithBitMask(bitMask)
  531. self.pick3D(targetNodePath, origin, dir)
  532. # Determine collision entry
  533. return self.findCollisionEntry(skipFlags)
  534. class SelectionSegment(SelectionQueue):
  535. # Like a selection ray but with two endpoints instead of an endpoint
  536. # and a direction
  537. def __init__(self, parentNP = render, numSegments = 1):
  538. # Initialize the superclass
  539. SelectionQueue.__init__(self, parentNP)
  540. self.colliders = []
  541. self.numColliders = 0
  542. for i in range(numSegments):
  543. self.addCollider(CollisionSegment())
  544. def addCollider(self, collider):
  545. # Record new collision object
  546. self.colliders.append(collider)
  547. # Add the collider to the collision Node
  548. self.collisionNode.addSolid(collider)
  549. self.numColliders += 1
  550. def pickGeom(self, targetNodePath = render, endPointList = [],
  551. skipFlags = SKIP_HIDDEN | SKIP_CAMERA):
  552. self.collideWithGeom()
  553. for i in range(min(len(endPointList), self.numColliders)):
  554. pointA, pointB = endPointList[i]
  555. collider = self.colliders[i]
  556. collider.setPointA(pointA)
  557. collider.setPointB(pointB)
  558. self.ct.traverse(targetNodePath)
  559. # Determine collision entry
  560. return self.findCollisionEntry(skipFlags)
  561. def pickBitMask(self, bitMask = BitMask32.allOff(),
  562. targetNodePath = render, endPointList = [],
  563. skipFlags = SKIP_HIDDEN | SKIP_CAMERA):
  564. self.collideWithBitMask(bitMask)
  565. for i in range(min(len(endPointList), self.numColliders)):
  566. pointA, pointB = endPointList[i]
  567. collider = self.colliders[i]
  568. collider.setPointA(pointA)
  569. collider.setPointB(pointB)
  570. self.ct.traverse(targetNodePath)
  571. # Determine collision entry
  572. return self.findCollisionEntry(skipFlags)
  573. class SelectionSphere(SelectionQueue):
  574. # Wrapper around collision sphere
  575. def __init__(self, parentNP = render, numSpheres = 1):
  576. # Initialize the superclass
  577. SelectionQueue.__init__(self, parentNP)
  578. self.colliders = []
  579. self.numColliders = 0
  580. for i in range(numSpheres):
  581. self.addCollider(CollisionSphere(Point3(0), 1))
  582. def addCollider(self, collider):
  583. # Record new collision object
  584. self.colliders.append(collider)
  585. # Add the collider to the collision Node
  586. self.collisionNode.addSolid(collider)
  587. self.numColliders += 1
  588. def setCenter(self, i, center):
  589. c = self.colliders[i]
  590. c.setCenter(center)
  591. def setRadius(self, i, radius):
  592. c = self.colliders[i]
  593. c.setRadius(radius)
  594. def setCenterRadius(self, i, center, radius):
  595. c = self.colliders[i]
  596. c.setCenter(center)
  597. c.setRadius(radius)
  598. def isEntryBackfacing(self, entry):
  599. # If dot product of collision point surface normal and
  600. # ray from sphere origin to collision point is positive,
  601. # center is on the backside of the polygon
  602. fromNodePath = entry.getFromNodePath()
  603. v = Vec3(entry.getSurfacePoint(fromNodePath) -
  604. entry.getFrom().getCenter())
  605. n = entry.getSurfaceNormal(fromNodePath)
  606. # If points almost on top of each other, reject face
  607. # (treat as backfacing)
  608. if v.length() < 0.05:
  609. return 1
  610. # Normalize and check angle between to vectors
  611. v.normalize()
  612. return v.dot(n) >= 0
  613. def pick(self, targetNodePath, skipFlags):
  614. self.ct.traverse(targetNodePath)
  615. self.sortEntries()
  616. return self.findCollisionEntry(skipFlags)
  617. def pickGeom(self, targetNodePath = render,
  618. skipFlags = SKIP_HIDDEN | SKIP_CAMERA):
  619. self.collideWithGeom()
  620. return self.pick(targetNodePath, skipFlags)
  621. def pickBitMask(self, bitMask = BitMask32.allOff(),
  622. targetNodePath = render,
  623. skipFlags = SKIP_HIDDEN | SKIP_CAMERA):
  624. self.collideWithBitMask(bitMask)
  625. return self.pick(targetNodePath, skipFlags)