Rotate the camera around a point with a mouse

Hi Panda3d community,

I’m currently trying to set up a more intuitive camera view, but I’m racking my brain trying to rotate my camera around a point XD
I’ve already succeeded in doing so, but the rotation is not smooth :frowning:
I think it may be due to a timing problem so I tried to used a doMethodLater but it’s the same.
Here what I’ve already done :slight_smile:

from panda3d.core import *
from math import cos, sin, atan2, acos, sqrt
from direct.showbase.ShowBase import ShowBase

class App(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        self.cam.setPos((100, 0, 0))
        self.model=loader.load_model('teapot')
        self.model.reparent_to(render)
        point = PointLight('Point Light')
        point.set_color((1, 1, 1, 1))
        point = render.attachNewNode(point)
        point.reparentTo(self.model)
        self.render.setLight(point)
        point.setPos((10, 0, 0))
        point.lookAt((0, 0, 0))
        setup_view(self)
		
def setup_view(self):
    """Disable the mouse and set up mouse-view functions"""
    self.disableMouse()    
    # Define camera parameters
    ## Camera angles
    self.camHorAng = 40
    self.camVerAng = 30
    self.camLens.setFov(self.camHorAng, self.camVerAng)
    ## Near/Far plane
    self.camNear = 1    
    self.camLens.setNear(self.camNear)
    self.camFar = 10000
    self.camLens.setFar(self.camFar)
    ## Camera pivot
    self.camPivot = Vec3((0, 0, 0))
    self.cam.lookAt(self.camPivot)
    ## Camera step for changes
    self.camSpeed = .05
    self.camZoomStep = 5
    
    # Set up camera zoom
    self.accept('wheel_up', zoom_in, [self])
    self.accept('wheel_down', zoom_out, [self])
    
    # Set up camera rotation    
    self.accept('mouse2', wheel_down, [self])
    self.accept('mouse2-up', wheel_up, [self])
    self.lastMousePos = None
    self.wheel_pressed = False
    self.taskMgr.add(rotate_view, 'Rotate Camera View', extraArgs=[self], appendTask=True)

# Functions for camera zoom
def zoom_out(self):
    """Translate the camera along the y axis of its matrix to zoom out the view"""
    self.view_changed = True
    self.cam.setPos(self.cam.getMat().xform((0, -self.camZoomStep, 0, 1)).getXyz())

def zoom_in(self):
    """Translate the camera along the y axis its matrix to zoom in the view"""
    self.view_changed = True
    camPos = self.cam.getPos()
    newCamPos = self.cam.getMat().xform((0, self.camZoomStep, 0, 1)).getXyz()    
    self.cam.setPos(newCamPos)
    # In case we get past the camera pivot, we look at it again
    if (newCamPos-camPos).length() >= (self.camPivot-camPos).length():
        self.cam.lookAt(self.camPivot)

# Functions for camera rotation
def wheel_down(self):
    self.wheel_pressed = True
    self.lastMousePos = None

def wheel_up(self):
    self.wheel_pressed = False
    self.lastMousePos = None

def rotate_view(self, task):
    if self.wheel_pressed and self.mouseWatcherNode.hasMouse():
        xm, zm = self.mouseWatcherNode.getMouse()
        if self.lastMousePos is None:
            self.lastMousePos = (xm, zm)
        else:
            r = (self.cam.getPos() - self.camPivot).length()
            dx, dz = xm - self.lastMousePos[0] , zm - self.lastMousePos[1] 
            camMat = Mat4(self.cam.getMat().getUpper3())
            newCamPos_camPivot = Mat4.rotateMat(dx*90, (0, 0, 1)) * Mat4.rotateMat(dz*90, (1, 0, 0)) # new orientation
            newCamPos_camPivot *= Mat4.translateMat((0, -r, 0)) * camMat # new position
            self.cam.setMat(Mat4.translateMat(self.camPivot) * newCamPos_camPivot) # set new position
            self.view_changed = True
            self.lastMousePos = (xm, zm)
    return task.again

app = App()
app.run()

Does someone have an idea concerning this problem ?

Cheers all!

I would recommend to either use a task to manually update the camera position based on delta time (which you can get from globalClock), to achieve smooth motion, or using Lerp Intervals as described here in the manual:
https://www.panda3d.org/manual/?title=Lerp_Intervals&oldid=1219

Hope that helps

edit:
also, if you’re just trying to rotate around a point while looking at it, you could use multiple parented NodePath that connect from the point you want to look at to the camera position and finally calling the .lookAt() method on the camera node to look at the point you want to… (think of it like a selfie stick with free rotation around you with variable length)

1 Like

Here’s a modified version of your code to make it work like it does in my own project:

from panda3d.core import *
from math import cos, sin, atan2, acos, sqrt
from direct.showbase.ShowBase import ShowBase

class App(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        self.model=loader.load_model('teapot')
        self.model.reparent_to(render)
        point = PointLight('Point Light')
        point.set_color((1, 1, 1, 1))
        point = render.attachNewNode(point)
        point.reparentTo(self.model)
        self.render.setLight(point)
        point.setPos((10, 0, 0))
        point.lookAt((0, 0, 0))
        self.setup_view()

    def setup_view(self):
        """Disable the mouse and set up mouse-view functions"""
        self.disableMouse()    
        # Define camera parameters
        ## Camera angles
        self.camHorAng = 40
        self.camVerAng = 30
        self.camLens.setFov(self.camHorAng, self.camVerAng)
        ## Near/Far plane
        self.camNear = 1    
        self.camLens.setNear(self.camNear)
        self.camFar = 10000
        self.camLens.setFar(self.camFar)
        ## Camera pivot
        self.camPivot = self.render.attach_new_node("cam_pivot")
        self.cam.reparent_to(self.camPivot)
        self.cam.set_y(-100.)
        ## Camera step for changes
        self.camSpeed = .05
        self.camZoomStep = 5

        # Set up camera zoom
        self.accept('wheel_up', self.zoom_in)
        self.accept('wheel_down', self.zoom_out)
        
        # Set up camera rotation    
        self.accept('mouse2', self.wheel_down)
        self.accept('mouse2-up', self.wheel_up)
        self.lastMousePos = None
        self.wheel_pressed = False
        self.taskMgr.add(self.rotate_view, 'Rotate Camera View')

    # Functions for camera zoom
    def zoom_out(self):
        """Translate the camera along its local y axis to zoom out the view"""
        self.view_changed = True
        self.cam.set_y(self.cam, -self.camZoomStep)

    def zoom_in(self):
        """Translate the camera along its local y axis to zoom in the view"""
        self.view_changed = True
        self.cam.set_y(self.cam, self.camZoomStep)
        # In case we get past the camera pivot, we look at it again
#        if (newCamPos-camPos).length() >= (self.camPivot-camPos).length():
#            self.cam.lookAt(self.camPivot)

    # Functions for camera rotation
    def wheel_down(self):
        self.wheel_pressed = True
        self.lastMousePos = None

    def wheel_up(self):
        self.wheel_pressed = False
        self.lastMousePos = None

    def rotate_view(self, task):
        if self.wheel_pressed and self.mouseWatcherNode.hasMouse():
            mouse_pos = self.mouseWatcherNode.getMouse()
            if self.lastMousePos is None:
                self.lastMousePos = Point2(mouse_pos)
            else:
                d_heading, d_pitch = (mouse_pos - self.lastMousePos) * 100.
                pivot = self.camPivot
                pivot.set_hpr(pivot.get_h() - d_heading, pivot.get_p() + d_pitch, 0.)
                self.view_changed = True
                self.lastMousePos = Point2(mouse_pos)
        return task.again

app = App()
app.run()

The main change is that the camera pivot is now an actual NodePath to which the camera is reparented.

To orbit around a point, you should first position the pivot at a point of interest and then just rotate that pivot instead of the camera. This will already save you a lot of headaches.

To zoom, you can now simply use the zoom step as a camera offset along its local Y-axis.

Smooth and easy :wink: . Enjoy!

1 Like

Hi Epihaius,

Thx :slight_smile: it works smoothly XD

Cheers all!

Thank you ! This is exactly what I needed!