2
0

DirectCameraControl.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. from PandaObject import *
  2. CAM_MOVE_DURATION = 1.0
  3. Y_AXIS = Vec3(0,1,0)
  4. class DirectCameraControl(PandaObject):
  5. def __init__(self, direct):
  6. # Create the grid
  7. self.direct = direct
  8. self.chan = direct.chan
  9. self.camera = self.chan.camera
  10. self.orthoViewRoll = 0.0
  11. self.lastView = 0
  12. self.coa = Point3(0)
  13. self.coaMarker = loader.loadModel('models/misc/sphere')
  14. self.coaMarker.setColor(1,0,0)
  15. self.coaMarkerPos = Point3(0)
  16. self.relNodePath = render.attachNewNode(NamedNode('targetNode'))
  17. self.zeroBaseVec = VBase3(0)
  18. self.zeroVector = Vec3(0)
  19. self.centerVec = Vec3(0, 1, 0)
  20. self.zeroPoint = Point3(0)
  21. def mouseFlyStart(self, chan):
  22. # Record starting mouse positions
  23. self.initMouseX = chan.mouseX
  24. self.initMouseY = chan.mouseY
  25. # Where are we in the channel?
  26. if ((abs(self.initMouseX) < 0.9) & (abs(self.initMouseY) < 0.9)):
  27. # MOUSE IS IN CENTRAL REGION
  28. # Hide the marker for this kind of motion
  29. self.coaMarker.hide()
  30. # See if the shift key is pressed
  31. if (self.direct.fShift):
  32. # If shift key is pressed, just perform horiz and vert pan:
  33. self.spawnHPPan()
  34. else:
  35. # Otherwise, check for a hit point based on
  36. # current mouse position
  37. # And then spawn task to determine mouse mode
  38. numEntries = self.direct.iRay.pickGeom(
  39. render,chan.mouseX,chan.mouseY)
  40. # Filter out hidden nodes from entry list
  41. indexList = []
  42. for i in range(0,numEntries):
  43. entry = self.direct.iRay.cq.getEntry(i)
  44. node = entry.getIntoNode()
  45. if node.isHidden():
  46. pass
  47. else:
  48. # Not one of the widgets, use it
  49. indexList.append(i)
  50. coa = Point3(0)
  51. if(indexList):
  52. # Start off with first point
  53. minPt = indexList[0]
  54. # Find hit point in camera's space
  55. hitPt = self.direct.iRay.camToHitPt(minPt)
  56. coa.set(hitPt[0],hitPt[1],hitPt[2])
  57. coaDist = Vec3(coa - self.zeroPoint).length()
  58. # Check other intersection points, sorting them
  59. # TBD: Use TBS C++ function to do this
  60. if len(indexList) > 1:
  61. for i in range(1,len(indexList)):
  62. entryNum = indexList[i]
  63. hitPt = self.direct.iRay.camToHitPt(entryNum)
  64. dist = Vec3(hitPt - self.zeroPoint).length()
  65. if (dist < coaDist):
  66. coaDist = dist
  67. coa.set(hitPt[0],hitPt[1],hitPt[2])
  68. minPt = i
  69. # Handle case of bad coa point (too close or too far)
  70. if ((coaDist < (1.1 * self.chan.near)) |
  71. (coaDist > self.chan.far)):
  72. # Put it out in front of the camera
  73. coa.set(0,100,0)
  74. coaDist = 100
  75. else:
  76. # If no intersection point:
  77. # Put coa out in front of the camera
  78. coa.set(0,100,0)
  79. coaDist = 100
  80. # Update coa and marker
  81. self.updateCoa(coa, coaDist)
  82. # Now spawn task to determine mouse fly mode
  83. self.determineMouseFlyMode()
  84. # END MOUSE IN CENTRAL REGION
  85. else:
  86. # Mouse is in outer frame, spawn mouseRotateTask
  87. self.spawnMouseRotateTask()
  88. def mouseFlyStop(self):
  89. taskMgr.removeTasksNamed('determineMouseFlyMode')
  90. taskMgr.removeTasksNamed('manipulateCamera')
  91. # Show the marker
  92. self.coaMarker.show()
  93. # Resize it
  94. self.updateCoaMarkerSize()
  95. def determineMouseFlyMode(self):
  96. # Otherwise, determine mouse fly mode
  97. t = Task.Task(self.determineMouseFlyModeTask)
  98. taskMgr.spawnTaskNamed(t, 'determineMouseFlyMode')
  99. def determineMouseFlyModeTask(self, state):
  100. deltaX = self.chan.mouseX - self.initMouseX
  101. deltaY = self.chan.mouseY - self.initMouseY
  102. if ((abs(deltaX) < 0.1) & (abs(deltaY) < 0.1)):
  103. return Task.cont
  104. else:
  105. if (abs(deltaY) > 0.1):
  106. self.spawnHPanYZoom()
  107. else:
  108. self.spawnXZTranslate()
  109. return Task.done
  110. def updateCoa(self, cam2point, coaDist = None):
  111. self.coa.set(cam2point[0], cam2point[1], cam2point[2])
  112. if coaDist:
  113. self.coaDist = coaDist
  114. else:
  115. self.coaDist = Vec3(self.coa - self.zeroPoint).length()
  116. # Place the marker in render space
  117. self.coaMarker.setPos(self.camera,self.coa)
  118. # Resize it
  119. self.updateCoaMarkerSize(coaDist)
  120. # Record marker pos in render space
  121. self.coaMarkerPos.assign(self.coaMarker.getPos())
  122. def updateCoaMarkerSize(self, coaDist = None):
  123. if not coaDist:
  124. coaDist = Vec3(self.coaMarker.getPos( self.chan.camera )).length()
  125. self.coaMarker.setScale(0.01 * coaDist *
  126. math.tan(deg2Rad(self.chan.fovV)))
  127. def homeCam(self, chan):
  128. chan.camera.setMat(Mat4.identMat())
  129. def uprightCam(self, chan):
  130. taskMgr.removeTasksNamed('manipulateCamera')
  131. currH = chan.camera.getH()
  132. chan.camera.lerpHpr(currH, 0, 0,
  133. CAM_MOVE_DURATION,
  134. other = render,
  135. blendType = 'easeInOut',
  136. task = 'manipulateCamera')
  137. def centerCam(self, chan):
  138. # Chan is a display region context
  139. self.centerCamIn(chan, 1.0)
  140. def centerCamNow(self, chan):
  141. self.centerCamIn(chan, 0.)
  142. def centerCamIn(self, chan, t):
  143. # Chan is a display region context
  144. taskMgr.removeTasksNamed('manipulateCamera')
  145. markerToCam = self.coaMarker.getPos( chan.camera )
  146. dist = Vec3(markerToCam - self.zeroPoint).length()
  147. scaledCenterVec = self.centerVec * dist
  148. delta = markerToCam - scaledCenterVec
  149. self.relNodePath.setPosHpr(chan.camera, Point3(0), Point3(0))
  150. chan.camera.lerpPos(Point3(delta),
  151. CAM_MOVE_DURATION,
  152. other = self.relNodePath,
  153. blendType = 'easeInOut',
  154. task = 'manipulateCamera')
  155. def zoomCam(self, chan, zoomFactor, t):
  156. taskMgr.removeTasksNamed('manipulateCamera')
  157. # Find a point zoom factor times the current separation
  158. # of the widget and cam
  159. zoomPtToCam = self.coaMarker.getPos(chan.camera) * zoomFactor
  160. # Put a target nodePath there
  161. self.relNodePath.setPos(chan.camera, zoomPtToCam)
  162. # Move to that point
  163. chan.camera.lerpPos(self.zeroPoint,
  164. CAM_MOVE_DURATION,
  165. other = self.relNodePath,
  166. blendType = 'easeInOut',
  167. task = 'manipulateCamera')
  168. def SpawnMoveToView(self, chan, view):
  169. # Kill any existing tasks
  170. taskMgr.removeTasksNamed('manipulateCamera')
  171. # Calc hprOffset
  172. hprOffset = VBase3()
  173. if view == 8:
  174. # Try the next roll angle
  175. self.orthoViewRoll = (self.orthoViewRoll + 90.0) % 360.0
  176. # but use the last view
  177. view = self.lastView
  178. else:
  179. self.orthoViewRoll = 0.0
  180. # Adjust offset based on specified view
  181. if view == 1:
  182. hprOffset.set(180., 0., 0.)
  183. elif view == 2:
  184. hprOffset.set(0., 0., 0.)
  185. elif view == 3:
  186. hprOffset.set(90., 0., 0.)
  187. elif view == 4:
  188. hprOffset.set(-90., 0., 0.)
  189. elif view == 5:
  190. hprOffset.set(0., -90., 0.)
  191. elif view == 6:
  192. hprOffset.set(0., 90., 0.)
  193. elif view == 7:
  194. hprOffset.set(135., -35.264, 0.)
  195. # Position target
  196. self.relNodePath.setPosHpr(self.coaMarker, self.zeroBaseVec,
  197. hprOffset)
  198. # Scale center vec by current distance to target
  199. offsetDistance = Vec3(chan.camera.getPos(self.relNodePath) -
  200. self.zeroPoint).length()
  201. scaledCenterVec = self.centerVec * (-1.0 * offsetDistance)
  202. # Now put the relNodePath at that point
  203. self.relNodePath.setPosHpr(self.relNodePath,
  204. scaledCenterVec,
  205. self.zeroBaseVec)
  206. # Record view for next time around
  207. self.lastView = view
  208. chan.camera.lerpPosHpr(self.zeroPoint,
  209. VBase3(0,0,self.orthoViewRoll),
  210. CAM_MOVE_DURATION,
  211. other = self.relNodePath,
  212. blendType = 'easeInOut',
  213. task = 'manipulateCamera')
  214. def swingCamAboutWidget(self, chan, degrees, t):
  215. # Remove existing camera manipulation task
  216. taskMgr.removeTasksNamed('manipulateCamera')
  217. # Coincident with widget
  218. self.relNodePath.setPos(self.coaMarker, self.zeroPoint)
  219. # But aligned with render space
  220. self.relNodePath.setHpr(self.zeroPoint)
  221. parent = self.camera.getParent()
  222. self.camera.wrtReparentTo(self.relNodePath)
  223. manipTask = self.relNodePath.lerpHpr(VBase3(degrees,0,0),
  224. CAM_MOVE_DURATION,
  225. blendType = 'easeInOut',
  226. task = 'manipulateCamera')
  227. # Upon death, reparent Cam to parent
  228. manipTask.parent = parent
  229. manipTask.uponDeath = self.reparentCam
  230. def reparentCam(self, state):
  231. self.camera.wrtReparentTo(state.parent)
  232. def spawnHPanYZoom(self):
  233. # Kill any existing tasks
  234. taskMgr.removeTasksNamed('manipulateCamera')
  235. # hide the marker
  236. self.coaMarker.hide()
  237. # Negate vec to give it the correct sense for mouse motion below
  238. targetVector = self.coa * -1
  239. t = Task.Task(self.HPanYZoomTask)
  240. t.targetVector = targetVector
  241. taskMgr.spawnTaskNamed(t, 'manipulateCamera')
  242. def HPanYZoomTask(self,state):
  243. targetVector = state.targetVector
  244. distToMove = targetVector * self.chan.mouseDeltaY
  245. self.camera.setPosHpr(self.camera,
  246. distToMove[0],
  247. distToMove[1],
  248. distToMove[2],
  249. (0.5 * self.chan.mouseDeltaX *
  250. self.chan.fovH),
  251. 0.0, 0.0)
  252. return Task.cont
  253. def spawnXZTranslateOrHPPan(self):
  254. # Kill any existing tasks
  255. taskMgr.removeTasksNamed('manipulateCamera')
  256. # Hide the marker
  257. self.coaMarker.hide()
  258. t = Task.Task(self.XZTranslateOrHPPanTask)
  259. t.scaleFactor = (self.coaDist / self.chan.near)
  260. taskMgr.spawnTaskNamed(t, 'manipulateCamera')
  261. def XZTranslateOrHPPanTask(self, state):
  262. if self.direct.fShift:
  263. self.camera.setHpr(self.camera,
  264. (0.5 * self.chan.mouseDeltaX *
  265. self.chan.fovH),
  266. (-0.5 * self.chan.mouseDeltaY *
  267. self.chan.fovV),
  268. 0.0)
  269. else:
  270. self.camera.setPos(self.camera,
  271. (-0.5 * self.chan.mouseDeltaX *
  272. self.chan.nearWidth *
  273. state.scaleFactor),
  274. 0.0,
  275. (-0.5 * self.chan.mouseDeltaY *
  276. self.chan.nearHeight *
  277. state.scaleFactor))
  278. return Task.cont
  279. def spawnXZTranslate(self):
  280. # Kill any existing tasks
  281. taskMgr.removeTasksNamed('manipulateCamera')
  282. # Hide the marker
  283. self.coaMarker.hide()
  284. t = Task.Task(self.XZTranslateTask)
  285. t.scaleFactor = (self.coaDist / self.chan.near)
  286. taskMgr.spawnTaskNamed(t, 'manipulateCamera')
  287. def XZTranslateTask(self,state):
  288. self.camera.setPos(self.camera,
  289. (-0.5 * self.chan.mouseDeltaX *
  290. self.chan.nearWidth *
  291. state.scaleFactor),
  292. 0.0,
  293. (-0.5 * self.chan.mouseDeltaY *
  294. self.chan.nearHeight *
  295. state.scaleFactor))
  296. return Task.cont
  297. def spawnMouseRotateTask(self):
  298. # Kill any existing tasks
  299. taskMgr.removeTasksNamed('manipulateCamera')
  300. # Set at markers position in render coordinates
  301. self.relNodePath.setPos(self.coaMarkerPos)
  302. self.relNodePath.setHpr(self.camera, self.zeroPoint)
  303. t = Task.Task(self.mouseRotateTask)
  304. t.wrtMat = self.camera.getMat( self.relNodePath )
  305. taskMgr.spawnTaskNamed(t, 'manipulateCamera')
  306. def mouseRotateTask(self, state):
  307. wrtMat = state.wrtMat
  308. self.relNodePath.setHpr(self.relNodePath,
  309. (-0.5 * self.chan.mouseDeltaX * 180.0),
  310. (0.5 * self.chan.mouseDeltaY * 180.0),
  311. 0.0)
  312. self.camera.setMat(self.relNodePath, wrtMat)
  313. return Task.cont
  314. def spawnHPPan(self):
  315. # Kill any existing tasks
  316. taskMgr.removeTasksNamed('manipulateCamera')
  317. # Hide the marker
  318. self.coaMarker.hide()
  319. t = Task.Task(self.HPPanTask)
  320. taskMgr.spawnTaskNamed(t, 'manipulateCamera')
  321. def HPPanTask(self, state):
  322. self.camera.setHpr(self.camera,
  323. (0.5 * self.chan.mouseDeltaX *
  324. self.chan.fovH),
  325. (-0.5 * self.chan.mouseDeltaY *
  326. self.chan.fovV),
  327. 0.0)
  328. return Task.cont
  329. def fitOnWidget(self):
  330. # Fit the node on the screen
  331. # stop any ongoing tasks
  332. taskMgr.removeTasksNamed('manipulateCamera')
  333. # How big is the node?
  334. nodeScale = self.direct.widget.scalingNode.getScale(render)
  335. maxScale = max(nodeScale[0],nodeScale[1],nodeScale[2])
  336. maxDim = min(self.chan.nearWidth, self.chan.nearHeight)
  337. # At what distance does the object fill 30% of the screen?
  338. # Assuming radius of 1 on widget
  339. camY = self.chan.near * (2.0 * maxScale)/(0.3 * maxDim)
  340. # What is the vector through the center of the screen?
  341. centerVec = Y_AXIS * camY
  342. # Where is the node relative to the viewpoint
  343. vWidget2Camera = self.direct.widget.getPos(self.camera)
  344. # How far do you move the camera to be this distance from the node?
  345. deltaMove = vWidget2Camera - centerVec
  346. # Move a target there
  347. self.relNodePath.setPos(self.camera, deltaMove)
  348. parent = self.camera.getParent()
  349. self.camera.wrtReparentTo(self.relNodePath)
  350. fitTask = self.camera.lerpPos(Point3(0,0,0),
  351. CAM_MOVE_DURATION,
  352. blendType = 'easeInOut',
  353. task = 'manipulateCamera')
  354. # Upon death, reparent Cam to parent
  355. fitTask.parent = parent
  356. fitTask.uponDeath = self.reparentCam
  357. def enableMouseFly(self):
  358. self.enableMouseInteraction()
  359. self.enableHotKeys()
  360. self.coaMarker.reparentTo(render)
  361. def enableMouseInteraction(self):
  362. # disable C++ fly interface
  363. base.disableMouse()
  364. # Accept middle mouse events
  365. self.accept('handleMouse2', self.mouseFlyStart, [self.chan])
  366. self.accept('handleMouse2Up', self.mouseFlyStop)
  367. def enableHotKeys(self):
  368. t = CAM_MOVE_DURATION
  369. self.accept('u', self.uprightCam, [self.chan])
  370. self.accept('c', self.centerCamIn, [self.chan, 0.5])
  371. self.accept('h', self.homeCam, [self.chan])
  372. self.accept('f', self.fitOnWidget)
  373. for i in range(1,9):
  374. self.accept(`i`, self.SpawnMoveToView, [self.chan, i])
  375. self.accept('9', self.swingCamAboutWidget, [self.chan, -90.0, t])
  376. self.accept('0', self.swingCamAboutWidget, [self.chan, 90.0, t])
  377. self.accept('`', self.removeManipulateCameraTask)
  378. self.accept('=', self.zoomCam, [self.chan, 0.5, t])
  379. self.accept('+', self.zoomCam, [self.chan, 0.5, t])
  380. self.accept('-', self.zoomCam, [self.chan, -2.0, t])
  381. self.accept('_', self.zoomCam, [self.chan, -2.0, t])
  382. def disableMouseFly(self):
  383. # Hide the marker
  384. self.coaMarker.reparentTo(hidden)
  385. # Ignore middle mouse events
  386. self.ignore('handleMouse2')
  387. self.ignore('handleMouse2Up')
  388. self.ignore('u')
  389. self.ignore('c')
  390. self.ignore('h')
  391. self.ignore('f')
  392. for i in range(0,10):
  393. self.ignore(`i`)
  394. self.ignore('=')
  395. self.ignore('+')
  396. self.ignore('-')
  397. self.ignore('_')
  398. self.ignore('`')
  399. def removeManipulateCameraTask(self):
  400. taskMgr.removeTasksNamed('manipulateCamera')