DirectCameraControl.py 37 KB

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