It probably isn’t necessary to go to such great lengths. The code below is my attempt to combine the built-in trackball behavior with the custom functionality you described:
from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
class NavigationManager:
def __init__(self, showbase, cam_vec):
self.showbase = showbase
self.task_mgr = showbase.task_mgr
self.mouse_watcher = showbase.mouseWatcherNode
self.cam = showbase.camera
self.cam_vec = cam_vec
self.mouse_prev = Point2()
self.roll_speed = 100.
self.dolly_speed = 10.
self.listener = listener = DirectObject()
listener.accept_once("l", self.lock_cam)
def lock_cam(self):
self.showbase.disable_mouse()
mat = Mat4()
rotate_to(mat, Vec3.forward(), self.cam_vec.normalized())
tmp_node = NodePath("tmp")
tmp_node.set_mat(mat)
self.cam.set_pos(self.showbase.cam.get_pos(self.showbase.render))
self.cam.set_hpr(tmp_node.get_hpr())
self.showbase.cam.set_mat(Mat4.ident_mat())
self.listener.accept_once("mouse1", self.start_roll)
self.listener.accept_once("mouse3", self.start_dolly)
self.listener.accept_once("u", self.unlock_cam)
def unlock_cam(self):
xform = self.cam.get_transform().get_inverse()
pos = xform.get_pos()
hpr = xform.get_hpr()
self.showbase.mouseInterfaceNode.set_pos(pos)
self.showbase.mouseInterfaceNode.set_hpr(hpr)
self.showbase.enable_mouse()
self.task_mgr.remove("transform_cam")
self.listener.ignore("mouse1")
self.listener.ignore("mouse3")
self.listener.ignore("mouse1-up")
self.listener.ignore("mouse3-up")
self.listener.accept_once("l", self.lock_cam)
def start_roll(self):
self.listener.ignore("mouse3")
self.listener.accept_once("mouse1-up", self.stop_roll)
self.mouse_prev = Point2(self.mouse_watcher.get_mouse())
self.task_mgr.add(self.roll_cam, "transform_cam")
def stop_roll(self):
self.task_mgr.remove("transform_cam")
self.listener.accept_once("mouse1", self.start_roll)
self.listener.accept_once("mouse3", self.start_dolly)
def roll_cam(self, task):
if self.mouse_watcher.has_mouse():
mouse_pos = self.mouse_watcher.get_mouse()
d = (mouse_pos - self.mouse_prev)[1]
self.cam.set_r(self.cam, d * self.roll_speed)
self.mouse_prev = Point2(mouse_pos)
return task.cont
def start_dolly(self):
self.listener.ignore("mouse1")
self.listener.accept_once("mouse3-up", self.stop_dolly)
self.mouse_prev = Point2(self.mouse_watcher.get_mouse())
self.task_mgr.add(self.dolly_cam, "transform_cam")
def stop_dolly(self):
self.task_mgr.remove("transform_cam")
self.listener.accept_once("mouse3", self.start_dolly)
self.listener.accept_once("mouse1", self.start_roll)
def dolly_cam(self, task):
if self.mouse_watcher.has_mouse():
mouse_pos = self.mouse_watcher.get_mouse()
d = (mouse_pos - self.mouse_prev)[1]
self.cam.set_y(self.cam, d * self.dolly_speed)
self.mouse_prev = Point2(mouse_pos)
return task.cont
showbase = ShowBase()
smiley = showbase.loader.load_model("smiley")
smiley.reparent_to(showbase.render)
dir_vec = Vec3(-1., 1., -1.)
nav_mgr = NavigationManager(showbase, dir_vec)
showbase.run()
To lock the camera to the desired direction vector (in the code sample, this is a hard-coded dir_vec
vector), press L
on the keyboard. Then you can test the rolling and dollying of the camera (which I think is what you wanted, judging from your description) by left- (resp. right-) dragging the mouse vertically. To unlock the camera again, press U
. Using the smiley model as a guide, you should see that the camera remains in the same position and orientation when doing so. The built-in trackball controls will once again be active.
Hopefully this will save you some time and work.
Note that the smiley will suddenly jump to a different position on screen when locking the camera. This is normal, as the orientation of the camera is changed, but its position isn’t. Perhaps it would be worthwhile to lerp the camera hpr using a LerpHprInterval
so your users don’t feel too disoriented when this happens.
EDIT:
Forgot to mention that setting the orientation of the camera to look in the direction of the specified vector is accomplished using a call to rotate_to
, which I would like to personally nominate as the most underrated/underused math function in Panda
.