DirectCameraControl.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. from direct.showbase.DirectObject import DirectObject
  2. from DirectUtil import *
  3. from DirectGeometry import *
  4. from DirectGlobals import *
  5. from direct.interval.IntervalGlobal import Sequence, Func
  6. from direct.directnotify import DirectNotifyGlobal
  7. from direct.task import Task
  8. CAM_MOVE_DURATION = 1.2
  9. COA_MARKER_SF = 0.0075
  10. Y_AXIS = Vec3(0, 1, 0)
  11. class DirectCameraControl(DirectObject):
  12. notify = DirectNotifyGlobal.directNotify.newCategory('DirectCameraControl')
  13. def __init__(self):
  14. # Create the grid
  15. self.startT = 0.0
  16. self.startF = 0
  17. self.orthoViewRoll = 0.0
  18. self.lastView = 0
  19. self.coa = Point3(0, 100, 0)
  20. self.coaMarker = loader.loadModel('models/misc/sphere')
  21. self.coaMarker.setName('DirectCameraCOAMarker')
  22. self.coaMarker.setTransparency(1)
  23. self.coaMarker.setColor(1, 0, 0, 0)
  24. self.coaMarker.setPos(0, 100, 0)
  25. useDirectRenderStyle(self.coaMarker)
  26. self.coaMarkerPos = Point3(0)
  27. self.coaMarkerColorIval = None
  28. self.fLockCOA = 0
  29. self.nullHitPointCount = 0
  30. self.cqEntries = []
  31. self.coaMarkerRef = base.direct.group.attachNewNode('coaMarkerRef')
  32. self.camManipRef = base.direct.group.attachNewNode('camManipRef')
  33. t = CAM_MOVE_DURATION
  34. self.actionEvents = [
  35. ['DIRECT-mouse1', self.mouseRotateStart],
  36. ['DIRECT-mouse1Up', self.mouseDollyStop],
  37. ['DIRECT-mouse2', self.mouseFlyStart],
  38. ['DIRECT-mouse2Up', self.mouseFlyStop],
  39. ['DIRECT-mouse3', self.mouseDollyStart],
  40. ['DIRECT-mouse3Up', self.mouseDollyStop],
  41. ]
  42. self.keyEvents = [
  43. ['c', self.centerCamIn, 0.5],
  44. ['f', self.fitOnWidget], # Note: This function doesn't work as intended
  45. ['h', self.homeCam],
  46. ['shift-v', self.toggleMarkerVis],
  47. ['m', self.moveToFit], # Note: This function doesn't work as intended; the object dissappears and screen flashes
  48. ['n', self.pickNextCOA],
  49. ['u', self.orbitUprightCam],
  50. ['shift-u', self.uprightCam],
  51. [`1`, self.spawnMoveToView, 1],
  52. [`2`, self.spawnMoveToView, 2],
  53. [`3`, self.spawnMoveToView, 3],
  54. [`4`, self.spawnMoveToView, 4],
  55. [`5`, self.spawnMoveToView, 5],
  56. [`6`, self.spawnMoveToView, 6],
  57. [`7`, self.spawnMoveToView, 7],
  58. [`8`, self.spawnMoveToView, 8],
  59. ['9', self.swingCamAboutWidget, -90.0, t],
  60. ['0', self.swingCamAboutWidget, 90.0, t],
  61. ['`', self.removeManipulateCameraTask],
  62. ['=', self.zoomCam, 0.5, t],
  63. ['+', self.zoomCam, 0.5, t],
  64. ['-', self.zoomCam, -2.0, t],
  65. ['_', self.zoomCam, -2.0, t],
  66. ]
  67. # set this to true to prevent the camera from rolling
  68. self.lockRoll = False
  69. # NIK - flag to determine whether to use maya camera controls
  70. self.useMayaCamControls = 0
  71. self.altDown = 0
  72. def toggleMarkerVis(self):
  73. ## if base.direct.cameraControl.coaMarker.isHidden():
  74. ## base.direct.cameraControl.coaMarker.show()
  75. ## else:
  76. ## base.direct.cameraControl.coaMarker.hide()
  77. if self.coaMarker.isHidden():
  78. self.coaMarker.show()
  79. else:
  80. self.coaMarker.hide()
  81. def mouseRotateStart(self, modifiers):
  82. if self.useMayaCamControls and modifiers == 4: # alt is pressed - use maya controls
  83. # base.direct.pushUndo([base.direct.camera]) # Wasteful use of undo
  84. self.spawnMouseRotateTask()
  85. def mouseDollyStart(self, modifiers):
  86. if self.useMayaCamControls and modifiers == 4: # alt is pressed - use maya controls
  87. # Hide the marker for this kind of motion
  88. self.coaMarker.hide()
  89. # Record time of start of mouse interaction
  90. self.startT= globalClock.getFrameTime()
  91. self.startF = globalClock.getFrameCount()
  92. # If the cam is orthogonal, spawn differentTask
  93. if (hasattr(base.direct.cam.node(), "getLens") and
  94. base.direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
  95. self.spawnOrthoZoom()
  96. else:
  97. # Start manipulation
  98. self.spawnHPanYZoom()
  99. def mouseDollyStop(self):
  100. taskMgr.remove('manipulateCamera')
  101. def mouseFlyStart(self, modifiers):
  102. # Record undo point
  103. # base.direct.pushUndo([base.direct.camera]) # Wasteful use of undo
  104. if self.useMayaCamControls and modifiers == 4: # alt is down, use maya controls
  105. # Hide the marker for this kind of motion
  106. self.coaMarker.hide()
  107. # Record time of start of mouse interaction
  108. self.startT= globalClock.getFrameTime()
  109. self.startF = globalClock.getFrameCount()
  110. # Start manipulation
  111. self.spawnXZTranslate()
  112. self.altDown = 1
  113. elif not self.useMayaCamControls:
  114. # Where are we in the display region?
  115. if ((abs(base.direct.dr.mouseX) < 0.9) and (abs(base.direct.dr.mouseY) < 0.9)):
  116. # MOUSE IS IN CENTRAL REGION
  117. # Hide the marker for this kind of motion
  118. self.coaMarker.hide()
  119. # Record time of start of mouse interaction
  120. self.startT= globalClock.getFrameTime()
  121. self.startF = globalClock.getFrameCount()
  122. # Start manipulation
  123. self.spawnXZTranslateOrHPanYZoom()
  124. # END MOUSE IN CENTRAL REGION
  125. else:
  126. if ((abs(base.direct.dr.mouseX) > 0.9) and
  127. (abs(base.direct.dr.mouseY) > 0.9)):
  128. # Mouse is in corners, spawn roll task
  129. self.spawnMouseRollTask()
  130. else:
  131. # Mouse is in outer frame, spawn mouseRotateTask
  132. self.spawnMouseRotateTask()
  133. if not modifiers == 4:
  134. self.altDown = 0
  135. def mouseFlyStop(self):
  136. taskMgr.remove('manipulateCamera')
  137. stopT = globalClock.getFrameTime()
  138. deltaT = stopT - self.startT
  139. stopF = globalClock.getFrameCount()
  140. deltaF = stopF - self.startF
  141. ## No reason this shouldn't work with Maya cam on
  142. # if not self.useMayaCamControls and (deltaT <= 0.25) or (deltaF <= 1):
  143. # Do this when not trying to manipulate camera
  144. if not self.altDown:
  145. # Check for a hit point based on
  146. # current mouse position
  147. # Allow intersection with unpickable objects
  148. # And then spawn task to determine mouse mode
  149. # Don't intersect with hidden or backfacing objects
  150. skipFlags = SKIP_HIDDEN | SKIP_BACKFACE
  151. # Skip camera (and its children), unless control key is pressed
  152. skipFlags |= SKIP_CAMERA * (1 - base.getControl())
  153. self.computeCOA(base.direct.iRay.pickGeom(skipFlags = skipFlags))
  154. # Record reference point
  155. self.coaMarkerRef.iPosHprScale(base.cam)
  156. # Record entries
  157. self.cqEntries = []
  158. for i in range(base.direct.iRay.getNumEntries()):
  159. self.cqEntries.append(base.direct.iRay.getEntry(i))
  160. # Show the marker
  161. self.coaMarker.show()
  162. # Resize it
  163. self.updateCoaMarkerSize()
  164. def mouseFlyStartTopWin(self):
  165. print "Moving mouse 2 in new window"
  166. #altIsDown = base.getAlt()
  167. #if altIsDown:
  168. # print "Alt is down"
  169. def mouseFlyStopTopWin(self):
  170. print "Stopping mouse 2 in new window"
  171. def spawnXZTranslateOrHPanYZoom(self):
  172. # Kill any existing tasks
  173. taskMgr.remove('manipulateCamera')
  174. # Spawn the new task
  175. t = Task.Task(self.XZTranslateOrHPanYZoomTask)
  176. # For HPanYZoom
  177. t.zoomSF = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
  178. taskMgr.add(t, 'manipulateCamera')
  179. def spawnXZTranslateOrHPPan(self):
  180. # Kill any existing tasks
  181. taskMgr.remove('manipulateCamera')
  182. # Spawn new task
  183. taskMgr.add(self.XZTranslateOrHPPanTask,
  184. 'manipulateCamera')
  185. def spawnXZTranslate(self):
  186. # Kill any existing tasks
  187. taskMgr.remove('manipulateCamera')
  188. # Spawn new task
  189. taskMgr.add(self.XZTranslateTask, 'manipulateCamera')
  190. def spawnHPanYZoom(self):
  191. # Kill any existing tasks
  192. taskMgr.remove('manipulateCamera')
  193. # Spawn new task
  194. t = Task.Task(self.HPanYZoomTask)
  195. t.zoomSF = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
  196. taskMgr.add(t, 'manipulateCamera')
  197. def spawnOrthoZoom(self):
  198. # Kill any existing tasks
  199. taskMgr.remove('manipulateCamera')
  200. # Spawn new task
  201. t = Task.Task(self.OrthoZoomTask)
  202. taskMgr.add(t, 'manipulateCamera')
  203. def spawnHPPan(self):
  204. # Kill any existing tasks
  205. taskMgr.remove('manipulateCamera')
  206. # Spawn new task
  207. taskMgr.add(self.HPPanTask, 'manipulateCamera')
  208. def XZTranslateOrHPanYZoomTask(self, state):
  209. if base.direct.fShift:
  210. return self.XZTranslateTask(state)
  211. else:
  212. return self.HPanYZoomTask(state)
  213. def XZTranslateOrHPPanTask(self, state):
  214. if base.direct.fShift:
  215. # Panning action
  216. return self.HPPanTask(state)
  217. else:
  218. # Translation action
  219. return self.XZTranslateTask(state)
  220. def XZTranslateTask(self, state):
  221. coaDist = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
  222. xlateSF = (coaDist / base.direct.dr.near)
  223. base.direct.camera.setPos(base.direct.camera,
  224. (-0.5 * base.direct.dr.mouseDeltaX *
  225. base.direct.dr.nearWidth *
  226. xlateSF),
  227. 0.0,
  228. (-0.5 * base.direct.dr.mouseDeltaY *
  229. base.direct.dr.nearHeight *
  230. xlateSF))
  231. return Task.cont
  232. def HPanYZoomTask(self, state):
  233. # If the cam is orthogonal, don't rotate or zoom.
  234. if (hasattr(base.direct.cam.node(), "getLens") and
  235. base.direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
  236. return
  237. if base.direct.fControl:
  238. moveDir = Vec3(self.coaMarker.getPos(base.direct.camera))
  239. # If marker is behind camera invert vector
  240. if moveDir[1] < 0.0:
  241. moveDir.assign(moveDir * -1)
  242. moveDir.normalize()
  243. else:
  244. moveDir = Vec3(Y_AXIS)
  245. if self.useMayaCamControls : # use maya controls
  246. moveDir.assign(moveDir * ((base.direct.dr.mouseDeltaX -1.0 * base.direct.dr.mouseDeltaY)
  247. * state.zoomSF))
  248. hVal = 0.0
  249. else:
  250. moveDir.assign(moveDir * (-1.0 * base.direct.dr.mouseDeltaY *
  251. state.zoomSF))
  252. if base.direct.dr.mouseDeltaY > 0.0:
  253. moveDir.setY(moveDir[1] * 1.0)
  254. hVal = 0.5 * base.direct.dr.mouseDeltaX * base.direct.dr.fovH
  255. base.direct.camera.setPosHpr(base.direct.camera,
  256. moveDir[0],
  257. moveDir[1],
  258. moveDir[2],
  259. hVal,
  260. 0.0, 0.0)
  261. if (self.lockRoll == True):
  262. # flatten roll
  263. base.direct.camera.setR(0)
  264. return Task.cont
  265. def OrthoZoomTask(self, state):
  266. filmSize = base.direct.camNode.getLens().getFilmSize()
  267. factor = (base.direct.dr.mouseDeltaX -1.0 * base.direct.dr.mouseDeltaY) * 0.1
  268. x = base.direct.dr.getWidth()
  269. y = base.direct.dr.getHeight()
  270. base.direct.dr.orthoFactor -= factor
  271. base.direct.dr.updateFilmSize(x, y)
  272. return Task.cont
  273. def HPPanTask(self, state):
  274. base.direct.camera.setHpr(base.direct.camera,
  275. (0.5 * base.direct.dr.mouseDeltaX *
  276. base.direct.dr.fovH),
  277. (-0.5 * base.direct.dr.mouseDeltaY *
  278. base.direct.dr.fovV),
  279. 0.0)
  280. return Task.cont
  281. def spawnMouseRotateTask(self):
  282. # Kill any existing tasks
  283. taskMgr.remove('manipulateCamera')
  284. # Set at markers position in render coordinates
  285. self.camManipRef.setPos(self.coaMarkerPos)
  286. self.camManipRef.setHpr(base.direct.camera, ZERO_POINT)
  287. t = Task.Task(self.mouseRotateTask)
  288. if abs(base.direct.dr.mouseX) > 0.9:
  289. t.constrainedDir = 'y'
  290. else:
  291. t.constrainedDir = 'x'
  292. taskMgr.add(t, 'manipulateCamera')
  293. def mouseRotateTask(self, state):
  294. # If the cam is orthogonal, don't rotate.
  295. if (hasattr(base.direct.cam.node(), "getLens") and
  296. base.direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
  297. return
  298. # If moving outside of center, ignore motion perpendicular to edge
  299. if ((state.constrainedDir == 'y') and (abs(base.direct.dr.mouseX) > 0.9)):
  300. deltaX = 0
  301. deltaY = base.direct.dr.mouseDeltaY
  302. elif ((state.constrainedDir == 'x') and (abs(base.direct.dr.mouseY) > 0.9)):
  303. deltaX = base.direct.dr.mouseDeltaX
  304. deltaY = 0
  305. else:
  306. deltaX = base.direct.dr.mouseDeltaX
  307. deltaY = base.direct.dr.mouseDeltaY
  308. if base.direct.fShift:
  309. base.direct.camera.setHpr(base.direct.camera,
  310. (deltaX * base.direct.dr.fovH),
  311. (-deltaY * base.direct.dr.fovV),
  312. 0.0)
  313. if (self.lockRoll == True):
  314. # flatten roll
  315. base.direct.camera.setR(0)
  316. self.camManipRef.setPos(self.coaMarkerPos)
  317. self.camManipRef.setHpr(base.direct.camera, ZERO_POINT)
  318. else:
  319. wrt = base.direct.camera.getTransform(self.camManipRef)
  320. self.camManipRef.setHpr(self.camManipRef,
  321. (-1 * deltaX * 180.0),
  322. (deltaY * 180.0),
  323. 0.0)
  324. if (self.lockRoll == True):
  325. # flatten roll
  326. self.camManipRef.setR(0)
  327. base.direct.camera.setTransform(self.camManipRef, wrt)
  328. return Task.cont
  329. def spawnMouseRollTask(self):
  330. # Kill any existing tasks
  331. taskMgr.remove('manipulateCamera')
  332. # Set at markers position in render coordinates
  333. self.camManipRef.setPos(self.coaMarkerPos)
  334. self.camManipRef.setHpr(base.direct.camera, ZERO_POINT)
  335. t = Task.Task(self.mouseRollTask)
  336. t.coaCenter = getScreenXY(self.coaMarker)
  337. t.lastAngle = getCrankAngle(t.coaCenter)
  338. # Store the camera/manipRef offset transform
  339. t.wrt = base.direct.camera.getTransform(self.camManipRef)
  340. taskMgr.add(t, 'manipulateCamera')
  341. def mouseRollTask(self, state):
  342. wrt = state.wrt
  343. angle = getCrankAngle(state.coaCenter)
  344. deltaAngle = angle - state.lastAngle
  345. state.lastAngle = angle
  346. self.camManipRef.setHpr(self.camManipRef, 0, 0, deltaAngle)
  347. if (self.lockRoll == True):
  348. # flatten roll
  349. self.camManipRef.setR(0)
  350. base.direct.camera.setTransform(self.camManipRef, wrt)
  351. return Task.cont
  352. def lockCOA(self):
  353. self.fLockCOA = 1
  354. base.direct.message('COA Lock On')
  355. def unlockCOA(self):
  356. self.fLockCOA = 0
  357. base.direct.message('COA Lock Off')
  358. def toggleCOALock(self):
  359. self.fLockCOA = 1 - self.fLockCOA
  360. if self.fLockCOA:
  361. base.direct.message('COA Lock On')
  362. else:
  363. base.direct.message('COA Lock Off')
  364. def pickNextCOA(self):
  365. """ Cycle through collision handler entries """
  366. if self.cqEntries:
  367. # Get next entry and rotate entries
  368. entry = self.cqEntries[0]
  369. self.cqEntries = self.cqEntries[1:] + self.cqEntries[:1]
  370. # Filter out object's under camera
  371. nodePath = entry.getIntoNodePath()
  372. if base.direct.camera not in nodePath.getAncestors():
  373. # Compute new hit point
  374. hitPt = entry.getSurfacePoint(entry.getFromNodePath())
  375. # Move coa marker to new point
  376. self.updateCoa(hitPt, ref = self.coaMarkerRef)
  377. else:
  378. # Remove offending entry
  379. self.cqEntries = self.cqEntries[:-1]
  380. self.pickNextCOA()
  381. def computeCOA(self, entry):
  382. coa = Point3(0)
  383. dr = base.direct.drList.getCurrentDr()
  384. if self.fLockCOA:
  385. # COA is locked, use existing point
  386. # Use existing point
  387. coa.assign(self.coaMarker.getPos(base.direct.camera))
  388. # Reset hit point count
  389. self.nullHitPointCount = 0
  390. elif entry:
  391. # Got a hit point (hit point is in camera coordinates)
  392. # Set center of action
  393. hitPt = entry.getSurfacePoint(entry.getFromNodePath())
  394. hitPtDist = Vec3(hitPt).length()
  395. coa.assign(hitPt)
  396. # Handle case of bad coa point (too close or too far)
  397. if ((hitPtDist < (1.1 * dr.near)) or
  398. (hitPtDist > dr.far)):
  399. # Just use existing point
  400. coa.assign(self.coaMarker.getPos(base.direct.camera))
  401. # Reset hit point count
  402. self.nullHitPointCount = 0
  403. else:
  404. # Increment null hit point count
  405. self.nullHitPointCount = (self.nullHitPointCount + 1) % 7
  406. # No COA lock and no intersection point
  407. # Use a point out in front of camera
  408. # Distance to point increases on multiple null hit points
  409. # MRM: Would be nice to be able to control this
  410. # At least display it
  411. dist = pow(10.0, self.nullHitPointCount)
  412. base.direct.message('COA Distance: ' + `dist`)
  413. coa.set(0, dist, 0)
  414. # Compute COA Dist
  415. coaDist = Vec3(coa - ZERO_POINT).length()
  416. if coaDist < (1.1 * dr.near):
  417. coa.set(0, 100, 0)
  418. coaDist = 100
  419. # Update coa and marker
  420. self.updateCoa(coa, coaDist = coaDist)
  421. def updateCoa(self, ref2point, coaDist = None, ref = None):
  422. self.coa.set(ref2point[0], ref2point[1], ref2point[2])
  423. if not coaDist:
  424. coaDist = Vec3(self.coa - ZERO_POINT).length()
  425. # Place the marker in render space
  426. if ref == None:
  427. # KEH: use the current display region
  428. # ref = base.cam
  429. ref = base.direct.drList.getCurrentDr().cam
  430. self.coaMarker.setPos(ref, self.coa)
  431. pos = self.coaMarker.getPos()
  432. self.coaMarker.setPosHprScale(pos, Vec3(0), Vec3(1))
  433. # Resize it
  434. self.updateCoaMarkerSize(coaDist)
  435. # Record marker pos in render space
  436. self.coaMarkerPos.assign(self.coaMarker.getPos())
  437. def updateCoaMarkerSizeOnDeath(self, state):
  438. # Needed because tasks pass in state as first arg
  439. self.updateCoaMarkerSize()
  440. def updateCoaMarkerSize(self, coaDist = None):
  441. if not coaDist:
  442. coaDist = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
  443. # Nominal size based on default 30 degree vertical FOV
  444. # Need to adjust size based on distance and current FOV
  445. sf = COA_MARKER_SF * coaDist * (base.direct.drList.getCurrentDr().fovV/30.0)
  446. if sf == 0.0:
  447. sf = 0.1
  448. self.coaMarker.setScale(sf)
  449. # Lerp color to fade out
  450. if self.coaMarkerColorIval:
  451. self.coaMarkerColorIval.finish()
  452. self.coaMarkerColorIval = Sequence(
  453. Func(self.coaMarker.unstash),
  454. self.coaMarker.colorInterval(1.5, Vec4(1, 0, 0, 0),
  455. startColor = Vec4(1, 0, 0, 1),
  456. blendType = 'easeInOut'),
  457. Func(self.coaMarker.stash)
  458. )
  459. self.coaMarkerColorIval.start()
  460. def homeCam(self):
  461. # Record undo point
  462. base.direct.pushUndo([base.direct.camera])
  463. base.direct.camera.reparentTo(render)
  464. base.direct.camera.clearMat()
  465. # Resize coa marker
  466. self.updateCoaMarkerSize()
  467. def uprightCam(self):
  468. taskMgr.remove('manipulateCamera')
  469. # Record undo point
  470. base.direct.pushUndo([base.direct.camera])
  471. # Pitch camera till upright
  472. currH = base.direct.camera.getH()
  473. base.direct.camera.lerpHpr(currH, 0, 0,
  474. CAM_MOVE_DURATION,
  475. other = render,
  476. blendType = 'easeInOut',
  477. task = 'manipulateCamera')
  478. def orbitUprightCam(self):
  479. taskMgr.remove('manipulateCamera')
  480. # Record undo point
  481. base.direct.pushUndo([base.direct.camera])
  482. # Transform camera z axis to render space
  483. mCam2Render = Mat4(Mat4.identMat()) # [gjeon] fixed to give required argument
  484. mCam2Render.assign(base.direct.camera.getMat(render))
  485. zAxis = Vec3(mCam2Render.xformVec(Z_AXIS))
  486. zAxis.normalize()
  487. # Compute rotation angle needed to upright cam
  488. orbitAngle = rad2Deg(math.acos(CLAMP(zAxis.dot(Z_AXIS), -1, 1)))
  489. # Check angle
  490. if orbitAngle < 0.1:
  491. # Already upright
  492. return
  493. # Compute orthogonal axis of rotation
  494. rotAxis = Vec3(zAxis.cross(Z_AXIS))
  495. rotAxis.normalize()
  496. # Find angle between rot Axis and render X_AXIS
  497. rotAngle = rad2Deg(math.acos(CLAMP(rotAxis.dot(X_AXIS), -1, 1)))
  498. # Determine sign or rotation angle
  499. if rotAxis[1] < 0:
  500. rotAngle *= -1
  501. # Position ref CS at coa marker with xaxis aligned with rot axis
  502. self.camManipRef.setPos(self.coaMarker, Vec3(0))
  503. self.camManipRef.setHpr(render, rotAngle, 0, 0)
  504. # Reparent Cam to ref Coordinate system
  505. parent = base.direct.camera.getParent()
  506. base.direct.camera.wrtReparentTo(self.camManipRef)
  507. # Rotate ref CS to final orientation
  508. t = self.camManipRef.lerpHpr(rotAngle, orbitAngle, 0,
  509. CAM_MOVE_DURATION,
  510. other = render,
  511. blendType = 'easeInOut',
  512. task = 'manipulateCamera')
  513. # Upon death, reparent Cam to parent
  514. t.parent = parent
  515. t.setUponDeath(self.reparentCam)
  516. def centerCam(self):
  517. self.centerCamIn(1.0)
  518. def centerCamNow(self):
  519. self.centerCamIn(0.)
  520. def centerCamIn(self, t):
  521. taskMgr.remove('manipulateCamera')
  522. # Record undo point
  523. base.direct.pushUndo([base.direct.camera])
  524. # Determine marker location
  525. markerToCam = self.coaMarker.getPos(base.direct.camera)
  526. dist = Vec3(markerToCam - ZERO_POINT).length()
  527. scaledCenterVec = Y_AXIS * dist
  528. delta = markerToCam - scaledCenterVec
  529. self.camManipRef.setPosHpr(base.direct.camera, Point3(0), Point3(0))
  530. t = base.direct.camera.lerpPos(Point3(delta),
  531. CAM_MOVE_DURATION,
  532. other = self.camManipRef,
  533. blendType = 'easeInOut',
  534. task = 'manipulateCamera')
  535. t.setUponDeath(self.updateCoaMarkerSizeOnDeath)
  536. def zoomCam(self, zoomFactor, t):
  537. taskMgr.remove('manipulateCamera')
  538. # Record undo point
  539. base.direct.pushUndo([base.direct.camera])
  540. # Find a point zoom factor times the current separation
  541. # of the widget and cam
  542. zoomPtToCam = self.coaMarker.getPos(base.direct.camera) * zoomFactor
  543. # Put a target nodePath there
  544. self.camManipRef.setPos(base.direct.camera, zoomPtToCam)
  545. # Move to that point
  546. t = base.direct.camera.lerpPos(ZERO_POINT,
  547. CAM_MOVE_DURATION,
  548. other = self.camManipRef,
  549. blendType = 'easeInOut',
  550. task = 'manipulateCamera')
  551. t.setUponDeath(self.updateCoaMarkerSizeOnDeath)
  552. def spawnMoveToView(self, view):
  553. # Kill any existing tasks
  554. taskMgr.remove('manipulateCamera')
  555. # Record undo point
  556. base.direct.pushUndo([base.direct.camera])
  557. # Calc hprOffset
  558. hprOffset = VBase3()
  559. if view == 8:
  560. # Try the next roll angle
  561. self.orthoViewRoll = (self.orthoViewRoll + 90.0) % 360.0
  562. # but use the last view
  563. view = self.lastView
  564. else:
  565. self.orthoViewRoll = 0.0
  566. # Adjust offset based on specified view
  567. if view == 1:
  568. hprOffset.set(180., 0., 0.)
  569. elif view == 2:
  570. hprOffset.set(0., 0., 0.)
  571. elif view == 3:
  572. hprOffset.set(90., 0., 0.)
  573. elif view == 4:
  574. hprOffset.set(-90., 0., 0.)
  575. elif view == 5:
  576. hprOffset.set(0., -90., 0.)
  577. elif view == 6:
  578. hprOffset.set(0., 90., 0.)
  579. elif view == 7:
  580. hprOffset.set(135., -35.264, 0.)
  581. # Position target
  582. self.camManipRef.setPosHpr(self.coaMarker, ZERO_VEC,
  583. hprOffset)
  584. # Scale center vec by current distance to target
  585. offsetDistance = Vec3(base.direct.camera.getPos(self.camManipRef) -
  586. ZERO_POINT).length()
  587. scaledCenterVec = Y_AXIS * (-1.0 * offsetDistance)
  588. # Now put the camManipRef at that point
  589. self.camManipRef.setPosHpr(self.camManipRef,
  590. scaledCenterVec,
  591. ZERO_VEC)
  592. # Record view for next time around
  593. self.lastView = view
  594. t = base.direct.camera.lerpPosHpr(ZERO_POINT,
  595. VBase3(0, 0, self.orthoViewRoll),
  596. CAM_MOVE_DURATION,
  597. other = self.camManipRef,
  598. blendType = 'easeInOut',
  599. task = 'manipulateCamera')
  600. t.setUponDeath(self.updateCoaMarkerSizeOnDeath)
  601. def swingCamAboutWidget(self, degrees, t):
  602. # Remove existing camera manipulation task
  603. taskMgr.remove('manipulateCamera')
  604. # Record undo point
  605. base.direct.pushUndo([base.direct.camera])
  606. # Coincident with widget
  607. self.camManipRef.setPos(self.coaMarker, ZERO_POINT)
  608. # But aligned with render space
  609. self.camManipRef.setHpr(ZERO_POINT)
  610. parent = base.direct.camera.getParent()
  611. base.direct.camera.wrtReparentTo(self.camManipRef)
  612. manipTask = self.camManipRef.lerpHpr(VBase3(degrees, 0, 0),
  613. CAM_MOVE_DURATION,
  614. blendType = 'easeInOut',
  615. task = 'manipulateCamera')
  616. # Upon death, reparent Cam to parent
  617. manipTask.parent = parent
  618. manipTask.setUponDeath(self.reparentCam)
  619. def reparentCam(self, state):
  620. base.direct.camera.wrtReparentTo(state.parent)
  621. self.updateCoaMarkerSize()
  622. def fitOnWidget(self, nodePath = 'None Given'):
  623. # Fit the node on the screen
  624. # stop any ongoing tasks
  625. taskMgr.remove('manipulateCamera')
  626. # How big is the node?
  627. nodeScale = base.direct.widget.scalingNode.getScale(render)
  628. maxScale = max(nodeScale[0], nodeScale[1], nodeScale[2])
  629. maxDim = min(base.direct.dr.nearWidth, base.direct.dr.nearHeight)
  630. # At what distance does the object fill 30% of the screen?
  631. # Assuming radius of 1 on widget
  632. camY = base.direct.dr.near * (2.0 * maxScale)/(0.3 * maxDim)
  633. # What is the vector through the center of the screen?
  634. centerVec = Y_AXIS * camY
  635. # Where is the node relative to the viewpoint
  636. vWidget2Camera = base.direct.widget.getPos(base.direct.camera)
  637. # How far do you move the camera to be this distance from the node?
  638. deltaMove = vWidget2Camera - centerVec
  639. # Move a target there
  640. try:
  641. self.camManipRef.setPos(base.direct.camera, deltaMove)
  642. except Exception:
  643. self.notify.debug
  644. parent = base.direct.camera.getParent()
  645. base.direct.camera.wrtReparentTo(self.camManipRef)
  646. fitTask = base.direct.camera.lerpPos(Point3(0, 0, 0),
  647. CAM_MOVE_DURATION,
  648. blendType = 'easeInOut',
  649. task = 'manipulateCamera')
  650. # Upon death, reparent Cam to parent
  651. fitTask.parent = parent
  652. fitTask.setUponDeath(self.reparentCam)
  653. def moveToFit(self):
  654. # How big is the active widget?
  655. widgetScale = base.direct.widget.scalingNode.getScale(render)
  656. maxScale = max(widgetScale[0], widgetScale[1], widgetScale[2])
  657. # At what distance does the widget fill 50% of the screen?
  658. camY = ((2 * base.direct.dr.near * (1.5 * maxScale)) /
  659. min(base.direct.dr.nearWidth, base.direct.dr.nearHeight))
  660. # Find a point this distance along the Y axis
  661. # MRM: This needs to be generalized to support non uniform frusta
  662. centerVec = Y_AXIS * camY
  663. # Before moving, record the relationship between the selected nodes
  664. # and the widget, so that this can be maintained
  665. base.direct.selected.getWrtAll()
  666. # Push state onto undo stack
  667. base.direct.pushUndo(base.direct.selected)
  668. # Remove the task to keep the widget attached to the object
  669. taskMgr.remove('followSelectedNodePath')
  670. # Spawn a task to keep the selected objects with the widget
  671. taskMgr.add(self.stickToWidgetTask, 'stickToWidget')
  672. # Spawn a task to move the widget
  673. t = base.direct.widget.lerpPos(Point3(centerVec),
  674. CAM_MOVE_DURATION,
  675. other = base.direct.camera,
  676. blendType = 'easeInOut',
  677. task = 'moveToFitTask')
  678. t.setUponDeath(lambda state: taskMgr.remove('stickToWidget'))
  679. def stickToWidgetTask(self, state):
  680. # Move the objects with the widget
  681. base.direct.selected.moveWrtWidgetAll()
  682. # Continue
  683. return Task.cont
  684. def enableMouseFly(self, fKeyEvents = 1):
  685. # disable C++ fly interface
  686. base.disableMouse()
  687. # Enable events
  688. for event in self.actionEvents:
  689. self.accept(event[0], event[1], extraArgs = event[2:])
  690. if fKeyEvents:
  691. for event in self.keyEvents:
  692. self.accept(event[0], event[1], extraArgs = event[2:])
  693. # Show marker
  694. self.coaMarker.reparentTo(base.direct.group)
  695. def disableMouseFly(self):
  696. # Hide the marker
  697. self.coaMarker.reparentTo(hidden)
  698. # Ignore events
  699. for event in self.actionEvents:
  700. self.ignore(event[0])
  701. for event in self.keyEvents:
  702. self.ignore(event[0])
  703. # Kill tasks
  704. self.removeManipulateCameraTask()
  705. taskMgr.remove('stickToWidget')
  706. base.enableMouse()
  707. def removeManipulateCameraTask(self):
  708. taskMgr.remove('manipulateCamera')