DirectCameraControl.py 37 KB

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