DirectCameraControl.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. from PandaObject import *
  2. from DirectGeometry import *
  3. CAM_MOVE_DURATION = 1.2
  4. COA_MARKER_SF = 0.0075
  5. Y_AXIS = Vec3(0,1,0)
  6. class DirectCameraControl(PandaObject):
  7. def __init__(self):
  8. # Create the grid
  9. self.orthoViewRoll = 0.0
  10. self.lastView = 0
  11. self.coa = Point3(0,100,0)
  12. self.coaDist = 100
  13. self.coaMarker = loader.loadModel('models/misc/sphere')
  14. self.coaMarker.setName('DirectCameraCOAMarker')
  15. self.coaMarker.setColor(1,0,0)
  16. self.coaMarker.setPos(0,100,0)
  17. useDirectRenderStyle(self.coaMarker)
  18. self.coaMarkerPos = Point3(0)
  19. self.camManipRef = direct.group.attachNewNode('camManipRef')
  20. t = CAM_MOVE_DURATION
  21. self.actionEvents = [
  22. ['handleMouse2', self.mouseFlyStart],
  23. ['handleMouse2Up', self.mouseFlyStop],
  24. ['c', self.centerCamIn, 0.5],
  25. ['f', self.fitOnWidget],
  26. ['h', self.homeCam],
  27. ['m', self.moveToFit],
  28. ['u', self.orbitUprightCam],
  29. ['U', self.uprightCam],
  30. [`1`, self.spawnMoveToView, 1],
  31. [`2`, self.spawnMoveToView, 2],
  32. [`3`, self.spawnMoveToView, 3],
  33. [`4`, self.spawnMoveToView, 4],
  34. [`5`, self.spawnMoveToView, 5],
  35. [`6`, self.spawnMoveToView, 6],
  36. [`7`, self.spawnMoveToView, 7],
  37. [`8`, self.spawnMoveToView, 8],
  38. ['9', self.swingCamAboutWidget, -90.0, t],
  39. ['0', self.swingCamAboutWidget, 90.0, t],
  40. ['`', self.removeManipulateCameraTask],
  41. ['=', self.zoomCam, 0.5, t],
  42. ['+', self.zoomCam, 0.5, t],
  43. ['-', self.zoomCam, -2.0, t],
  44. ['_', self.zoomCam, -2.0, t],
  45. ]
  46. def mouseFlyStart(self):
  47. # Record undo point
  48. direct.pushUndo([direct.camera])
  49. # Where are we in the display region?
  50. if ((abs(direct.dr.mouseX) < 0.9) & (abs(direct.dr.mouseY) < 0.9)):
  51. # MOUSE IS IN CENTRAL REGION
  52. # Hide the marker for this kind of motion
  53. self.coaMarker.hide()
  54. # Check for a hit point based on
  55. # current mouse position
  56. # Allow intersection with unpickable objects
  57. # And then spawn task to determine mouse mode
  58. node, hitPt, hitPtDist = direct.iRay.pickGeom(
  59. fIntersectUnpickable = 1)
  60. coa = Point3(0)
  61. if node:
  62. # Set center of action
  63. coa.assign(hitPt)
  64. coaDist = hitPtDist
  65. # Handle case of bad coa point (too close or too far)
  66. if ((coaDist < (1.1 * direct.dr.near)) |
  67. (coaDist > direct.dr.far)):
  68. # Just use existing point
  69. coa.assign(self.coaMarker.getPos(direct.camera))
  70. coaDist = Vec3(coa - ZERO_POINT).length()
  71. if coaDist < (1.1 * direct.dr.near):
  72. coa.set(0,100,0)
  73. coaDist = 100
  74. else:
  75. # If no intersection point:
  76. # Use existing point
  77. coa.assign(self.coaMarker.getPos(direct.camera))
  78. coaDist = Vec3(coa - ZERO_POINT).length()
  79. # Check again its not to close
  80. if coaDist < (1.1 * direct.dr.near):
  81. coa.set(0,100,0)
  82. coaDist = 100
  83. # Update coa and marker
  84. self.updateCoa(coa, coaDist)
  85. # Start manipulation
  86. self.spawnXZTranslateOrHPanYZoom()
  87. # END MOUSE IN CENTRAL REGION
  88. else:
  89. if ((abs(direct.dr.mouseX) > 0.9) & (abs(direct.dr.mouseY) > 0.9)):
  90. # Mouse is in corners, spawn roll task
  91. self.spawnMouseRollTask()
  92. else:
  93. # Mouse is in outer frame, spawn mouseRotateTask
  94. self.spawnMouseRotateTask()
  95. def mouseFlyStop(self):
  96. taskMgr.removeTasksNamed('manipulateCamera')
  97. # Show the marker
  98. self.coaMarker.show()
  99. # Resize it
  100. self.updateCoaMarkerSize()
  101. def spawnXZTranslateOrHPanYZoom(self):
  102. # Kill any existing tasks
  103. taskMgr.removeTasksNamed('manipulateCamera')
  104. # Spawn the new task
  105. t = Task.Task(self.XZTranslateOrHPanYZoomTask)
  106. # For HPanYZoom
  107. t.zoomSF = Vec3(self.coa).length()
  108. taskMgr.spawnTaskNamed(t, 'manipulateCamera')
  109. def spawnXZTranslateOrHPPan(self):
  110. # Kill any existing tasks
  111. taskMgr.removeTasksNamed('manipulateCamera')
  112. # Spawn new task
  113. taskMgr.spawnMethodNamed(self.XZTranslateOrHPPanTask,
  114. 'manipulateCamera')
  115. def spawnXZTranslate(self):
  116. # Kill any existing tasks
  117. taskMgr.removeTasksNamed('manipulateCamera')
  118. # Spawn new task
  119. taskMgr.spawnMethodNamed(self.XZTranslateTask, 'manipulateCamera')
  120. def spawnHPanYZoom(self):
  121. # Kill any existing tasks
  122. taskMgr.removeTasksNamed('manipulateCamera')
  123. # Spawn new task
  124. t = Task.Task(self.HPanYZoomTask)
  125. t.zoomSF = Vec3(self.coa).length()
  126. taskMgr.spawnTaskNamed(t, 'manipulateCamera')
  127. def spawnHPPan(self):
  128. # Kill any existing tasks
  129. taskMgr.removeTasksNamed('manipulateCamera')
  130. # Spawn new task
  131. taskMgr.spawnMethodNamed(self.HPPanTask, 'manipulateCamera')
  132. def XZTranslateOrHPanYZoomTask(self, state):
  133. if direct.fShift:
  134. return self.XZTranslateTask(state)
  135. else:
  136. return self.HPanYZoomTask(state)
  137. def XZTranslateOrHPPanTask(self, state):
  138. if direct.fShift:
  139. # Panning action
  140. return self.HPPanTask(state)
  141. else:
  142. # Translation action
  143. return self.XZTranslateTask(state)
  144. def XZTranslateTask(self,state):
  145. coaDist = Vec3(self.coaMarker.getPos(direct.camera)).length()
  146. xlateSF = (coaDist / direct.dr.near)
  147. direct.camera.setPos(direct.camera,
  148. (-0.5 * direct.dr.mouseDeltaX *
  149. direct.dr.nearWidth *
  150. xlateSF),
  151. 0.0,
  152. (-0.5 * direct.dr.mouseDeltaY *
  153. direct.dr.nearHeight *
  154. xlateSF))
  155. return Task.cont
  156. def HPanYZoomTask(self,state):
  157. if direct.fControl:
  158. moveDir = Vec3(Y_AXIS)
  159. else:
  160. moveDir = Vec3(self.coaMarker.getPos(direct.camera))
  161. # If marker is behind camera invert vector
  162. if moveDir[1] < 0.0:
  163. moveDir.assign(moveDir * -1)
  164. moveDir.normalize()
  165. moveDir.assign(moveDir * (-2.0 * direct.dr.mouseDeltaY *
  166. state.zoomSF))
  167. direct.camera.setPosHpr(direct.camera,
  168. moveDir[0],
  169. moveDir[1],
  170. moveDir[2],
  171. (0.5 * direct.dr.mouseDeltaX *
  172. direct.dr.fovH),
  173. 0.0, 0.0)
  174. return Task.cont
  175. def HPPanTask(self, state):
  176. direct.camera.setHpr(direct.camera,
  177. (0.5 * direct.dr.mouseDeltaX *
  178. direct.dr.fovH),
  179. (-0.5 * direct.dr.mouseDeltaY *
  180. direct.dr.fovV),
  181. 0.0)
  182. return Task.cont
  183. def spawnMouseRotateTask(self):
  184. # Kill any existing tasks
  185. taskMgr.removeTasksNamed('manipulateCamera')
  186. # Set at markers position in render coordinates
  187. self.camManipRef.setPos(self.coaMarkerPos)
  188. self.camManipRef.setHpr(direct.camera, ZERO_POINT)
  189. t = Task.Task(self.mouseRotateTask)
  190. if abs(direct.dr.mouseX) > 0.9:
  191. t.constrainedDir = 'y'
  192. else:
  193. t.constrainedDir = 'x'
  194. taskMgr.spawnTaskNamed(t, 'manipulateCamera')
  195. def mouseRotateTask(self, state):
  196. # If moving outside of center, ignore motion perpendicular to edge
  197. if ((state.constrainedDir == 'y') & (abs(direct.dr.mouseX) > 0.9)):
  198. deltaX = 0
  199. deltaY = direct.dr.mouseDeltaY
  200. elif ((state.constrainedDir == 'x') & (abs(direct.dr.mouseY) > 0.9)):
  201. deltaX = direct.dr.mouseDeltaX
  202. deltaY = 0
  203. else:
  204. deltaX = direct.dr.mouseDeltaX
  205. deltaY = direct.dr.mouseDeltaY
  206. if direct.fShift:
  207. direct.camera.setHpr(direct.camera,
  208. (deltaX * direct.dr.fovH),
  209. (-deltaY * direct.dr.fovV),
  210. 0.0)
  211. self.camManipRef.setPos(self.coaMarkerPos)
  212. self.camManipRef.setHpr(direct.camera, ZERO_POINT)
  213. else:
  214. wrtMat = direct.camera.getMat( self.camManipRef )
  215. self.camManipRef.setHpr(self.camManipRef,
  216. (-1 * deltaX * 180.0),
  217. (deltaY * 180.0),
  218. 0.0)
  219. direct.camera.setMat(self.camManipRef, wrtMat)
  220. return Task.cont
  221. def spawnMouseRollTask(self):
  222. # Kill any existing tasks
  223. taskMgr.removeTasksNamed('manipulateCamera')
  224. # Set at markers position in render coordinates
  225. self.camManipRef.setPos(self.coaMarkerPos)
  226. self.camManipRef.setHpr(direct.camera, ZERO_POINT)
  227. t = Task.Task(self.mouseRollTask)
  228. t.coaCenter = getScreenXY(self.coaMarker)
  229. t.lastAngle = getCrankAngle(t.coaCenter)
  230. t.wrtMat = direct.camera.getMat( self.camManipRef )
  231. taskMgr.spawnTaskNamed(t, 'manipulateCamera')
  232. def mouseRollTask(self, state):
  233. wrtMat = state.wrtMat
  234. angle = getCrankAngle(state.coaCenter)
  235. deltaAngle = angle - state.lastAngle
  236. state.lastAngle = angle
  237. self.camManipRef.setHpr(self.camManipRef, 0, 0, -deltaAngle)
  238. direct.camera.setMat(self.camManipRef, wrtMat)
  239. return Task.cont
  240. def updateCoa(self, cam2point, coaDist = None):
  241. self.coa.set(cam2point[0], cam2point[1], cam2point[2])
  242. if coaDist:
  243. self.coaDist = coaDist
  244. else:
  245. self.coaDist = Vec3(self.coa - ZERO_POINT).length()
  246. # Place the marker in render space
  247. self.coaMarker.setPos(direct.camera,self.coa)
  248. # Resize it
  249. self.updateCoaMarkerSize(coaDist)
  250. # Record marker pos in render space
  251. self.coaMarkerPos.assign(self.coaMarker.getPos())
  252. def updateCoaMarkerSizeOnDeath(self, state):
  253. # Needed because tasks pass in state as first arg
  254. self.updateCoaMarkerSize()
  255. def updateCoaMarkerSize(self, coaDist = None):
  256. if not coaDist:
  257. coaDist = Vec3(self.coaMarker.getPos( direct.camera )).length()
  258. self.coaMarker.setScale(COA_MARKER_SF * coaDist *
  259. math.tan(deg2Rad(direct.dr.fovV)))
  260. def homeCam(self):
  261. # Record undo point
  262. direct.pushUndo([direct.camera])
  263. direct.camera.setMat(Mat4.identMat())
  264. # Resize coa marker
  265. self.updateCoaMarkerSize()
  266. def uprightCam(self):
  267. taskMgr.removeTasksNamed('manipulateCamera')
  268. # Record undo point
  269. direct.pushUndo([direct.camera])
  270. # Pitch camera till upright
  271. currH = direct.camera.getH()
  272. direct.camera.lerpHpr(currH, 0, 0,
  273. CAM_MOVE_DURATION,
  274. other = render,
  275. blendType = 'easeInOut',
  276. task = 'manipulateCamera')
  277. def orbitUprightCam(self):
  278. taskMgr.removeTasksNamed('manipulateCamera')
  279. # Record undo point
  280. direct.pushUndo([direct.camera])
  281. # Transform camera z axis to render space
  282. mCam2Render = camera.getMat(render)
  283. zAxis = Vec3(mCam2Render.xformVec(Z_AXIS))
  284. zAxis.normalize()
  285. # Compute rotation angle needed to upright cam
  286. orbitAngle = rad2Deg(math.acos(CLAMP(zAxis.dot(Z_AXIS),-1,1)))
  287. # Check angle
  288. if orbitAngle < 0.1:
  289. # Already upright
  290. return
  291. # Compute orthogonal axis of rotation
  292. rotAxis = Vec3(zAxis.cross(Z_AXIS))
  293. rotAxis.normalize()
  294. # Find angle between rot Axis and render X_AXIS
  295. rotAngle = rad2Deg(math.acos(CLAMP(rotAxis.dot(X_AXIS),-1,1)))
  296. # Determine sign or rotation angle
  297. if rotAxis[1] < 0:
  298. rotAngle *= -1
  299. # Position ref CS at coa marker with xaxis aligned with rot axis
  300. self.camManipRef.setPos(self.coaMarker, Vec3(0))
  301. self.camManipRef.setHpr(render, rotAngle, 0, 0)
  302. # Reparent Cam to ref Coordinate system
  303. parent = direct.camera.getParent()
  304. direct.camera.wrtReparentTo(self.camManipRef)
  305. # Rotate ref CS to final orientation
  306. t = self.camManipRef.lerpHpr(rotAngle, orbitAngle, 0,
  307. CAM_MOVE_DURATION,
  308. other = render,
  309. blendType = 'easeInOut',
  310. task = 'manipulateCamera')
  311. # Upon death, reparent Cam to parent
  312. t.parent = parent
  313. t.uponDeath = self.reparentCam
  314. def centerCam(self):
  315. self.centerCamIn(1.0)
  316. def centerCamNow(self):
  317. self.centerCamIn(0.)
  318. def centerCamIn(self, t):
  319. taskMgr.removeTasksNamed('manipulateCamera')
  320. # Record undo point
  321. direct.pushUndo([direct.camera])
  322. # Determine marker location
  323. markerToCam = self.coaMarker.getPos( direct.camera )
  324. dist = Vec3(markerToCam - ZERO_POINT).length()
  325. scaledCenterVec = Y_AXIS * dist
  326. delta = markerToCam - scaledCenterVec
  327. self.camManipRef.setPosHpr(direct.camera, Point3(0), Point3(0))
  328. t = direct.camera.lerpPos(Point3(delta),
  329. CAM_MOVE_DURATION,
  330. other = self.camManipRef,
  331. blendType = 'easeInOut',
  332. task = 'manipulateCamera')
  333. t.uponDeath = self.updateCoaMarkerSizeOnDeath
  334. def zoomCam(self, zoomFactor, t):
  335. taskMgr.removeTasksNamed('manipulateCamera')
  336. # Record undo point
  337. direct.pushUndo([direct.camera])
  338. # Find a point zoom factor times the current separation
  339. # of the widget and cam
  340. zoomPtToCam = self.coaMarker.getPos(direct.camera) * zoomFactor
  341. # Put a target nodePath there
  342. self.camManipRef.setPos(direct.camera, zoomPtToCam)
  343. # Move to that point
  344. t = direct.camera.lerpPos(ZERO_POINT,
  345. CAM_MOVE_DURATION,
  346. other = self.camManipRef,
  347. blendType = 'easeInOut',
  348. task = 'manipulateCamera')
  349. t.uponDeath = self.updateCoaMarkerSizeOnDeath
  350. def spawnMoveToView(self, view):
  351. # Kill any existing tasks
  352. taskMgr.removeTasksNamed('manipulateCamera')
  353. # Record undo point
  354. direct.pushUndo([direct.camera])
  355. # Calc hprOffset
  356. hprOffset = VBase3()
  357. if view == 8:
  358. # Try the next roll angle
  359. self.orthoViewRoll = (self.orthoViewRoll + 90.0) % 360.0
  360. # but use the last view
  361. view = self.lastView
  362. else:
  363. self.orthoViewRoll = 0.0
  364. # Adjust offset based on specified view
  365. if view == 1:
  366. hprOffset.set(180., 0., 0.)
  367. elif view == 2:
  368. hprOffset.set(0., 0., 0.)
  369. elif view == 3:
  370. hprOffset.set(90., 0., 0.)
  371. elif view == 4:
  372. hprOffset.set(-90., 0., 0.)
  373. elif view == 5:
  374. hprOffset.set(0., -90., 0.)
  375. elif view == 6:
  376. hprOffset.set(0., 90., 0.)
  377. elif view == 7:
  378. hprOffset.set(135., -35.264, 0.)
  379. # Position target
  380. self.camManipRef.setPosHpr(self.coaMarker, ZERO_VEC,
  381. hprOffset)
  382. # Scale center vec by current distance to target
  383. offsetDistance = Vec3(direct.camera.getPos(self.camManipRef) -
  384. ZERO_POINT).length()
  385. scaledCenterVec = Y_AXIS * (-1.0 * offsetDistance)
  386. # Now put the camManipRef at that point
  387. self.camManipRef.setPosHpr(self.camManipRef,
  388. scaledCenterVec,
  389. ZERO_VEC)
  390. # Record view for next time around
  391. self.lastView = view
  392. t = direct.camera.lerpPosHpr(ZERO_POINT,
  393. VBase3(0,0,self.orthoViewRoll),
  394. CAM_MOVE_DURATION,
  395. other = self.camManipRef,
  396. blendType = 'easeInOut',
  397. task = 'manipulateCamera')
  398. t.uponDeath = self.updateCoaMarkerSizeOnDeath
  399. def swingCamAboutWidget(self, degrees, t):
  400. # Remove existing camera manipulation task
  401. taskMgr.removeTasksNamed('manipulateCamera')
  402. # Record undo point
  403. direct.pushUndo([direct.camera])
  404. # Coincident with widget
  405. self.camManipRef.setPos(self.coaMarker, ZERO_POINT)
  406. # But aligned with render space
  407. self.camManipRef.setHpr(ZERO_POINT)
  408. parent = direct.camera.getParent()
  409. direct.camera.wrtReparentTo(self.camManipRef)
  410. manipTask = self.camManipRef.lerpHpr(VBase3(degrees,0,0),
  411. CAM_MOVE_DURATION,
  412. blendType = 'easeInOut',
  413. task = 'manipulateCamera')
  414. # Upon death, reparent Cam to parent
  415. manipTask.parent = parent
  416. manipTask.uponDeath = self.reparentCam
  417. def reparentCam(self, state):
  418. direct.camera.wrtReparentTo(state.parent)
  419. self.updateCoaMarkerSize()
  420. def fitOnWidget(self, nodePath = 'None Given'):
  421. # Fit the node on the screen
  422. # stop any ongoing tasks
  423. taskMgr.removeTasksNamed('manipulateCamera')
  424. # How big is the node?
  425. nodeScale = direct.widget.scalingNode.getScale(render)
  426. maxScale = max(nodeScale[0],nodeScale[1],nodeScale[2])
  427. maxDim = min(direct.dr.nearWidth, direct.dr.nearHeight)
  428. # At what distance does the object fill 30% of the screen?
  429. # Assuming radius of 1 on widget
  430. camY = direct.dr.near * (2.0 * maxScale)/(0.3 * maxDim)
  431. # What is the vector through the center of the screen?
  432. centerVec = Y_AXIS * camY
  433. # Where is the node relative to the viewpoint
  434. vWidget2Camera = direct.widget.getPos(direct.camera)
  435. # How far do you move the camera to be this distance from the node?
  436. deltaMove = vWidget2Camera - centerVec
  437. # Move a target there
  438. self.camManipRef.setPos(direct.camera, deltaMove)
  439. parent = direct.camera.getParent()
  440. direct.camera.wrtReparentTo(self.camManipRef)
  441. fitTask = direct.camera.lerpPos(Point3(0,0,0),
  442. CAM_MOVE_DURATION,
  443. blendType = 'easeInOut',
  444. task = 'manipulateCamera')
  445. # Upon death, reparent Cam to parent
  446. fitTask.parent = parent
  447. fitTask.uponDeath = self.reparentCam
  448. def moveToFit(self):
  449. # How bit is the active widget?
  450. widgetScale = direct.widget.scalingNode.getScale(render)
  451. maxScale = max(widgetScale[0], widgetScale[1], widgetScale[2])
  452. # At what distance does the widget fill 50% of the screen?
  453. camY = ((2 * direct.dr.near * (1.5 * maxScale)) /
  454. min(direct.dr.nearWidth, direct.dr.nearHeight))
  455. # Find a point this distance along the Y axis
  456. # MRM: This needs to be generalized to support non uniform frusta
  457. centerVec = Y_AXIS * camY
  458. # Before moving, record the relationship between the selected nodes
  459. # and the widget, so that this can be maintained
  460. direct.selected.getWrtAll()
  461. # Push state onto undo stack
  462. direct.pushUndo(direct.selected)
  463. # Remove the task to keep the widget attached to the object
  464. taskMgr.removeTasksNamed('followSelectedNodePath')
  465. # Spawn a task to keep the selected objects with the widget
  466. taskMgr.spawnMethodNamed(self.stickToWidgetTask, 'stickToWidget')
  467. # Spawn a task to move the widget
  468. t = direct.widget.lerpPos(Point3(centerVec),
  469. CAM_MOVE_DURATION,
  470. other = direct.camera,
  471. blendType = 'easeInOut',
  472. task = 'moveToFitTask')
  473. t.uponDeath = lambda state: taskMgr.removeTasksNamed('stickToWidget')
  474. def stickToWidgetTask(self, state):
  475. # Move the objects with the widget
  476. direct.selected.moveWrtWidgetAll()
  477. # Continue
  478. return Task.cont
  479. def enableMouseFly(self):
  480. # disable C++ fly interface
  481. base.disableMouse()
  482. # Enable events
  483. for event in self.actionEvents:
  484. self.accept(event[0], event[1], extraArgs = event[2:])
  485. # Show marker
  486. self.coaMarker.reparentTo(direct.group)
  487. def disableMouseFly(self):
  488. # Hide the marker
  489. self.coaMarker.reparentTo(hidden)
  490. # Ignore events
  491. for event in self.actionEvents:
  492. self.ignore(event[0])
  493. def removeManipulateCameraTask(self):
  494. taskMgr.removeTasksNamed('manipulateCamera')