Rotate the camera around a point

Good day to all pandas! :smiley:
I always thought my first message at this forums would be a noobish help request :stuck_out_tongue: (although I actually have some noobish questions, but not for today :stuck_out_tongue:).

The kind of rotation I’m talking about is this:

Tiptoe, I took the image you posted in this topic. I hope this doesn’t piss you off or something :slight_smile:.

Anyway, I always thought that it would be really easy to rotate the camera around a point by placing there a dummy node, attach the camera to the node, then rotating the node and dettach the camera from the node.

I tried this, but it never worked :confused:. It seems that the camera needs to stay attached to de dummy node in order for this to work, but the fact is that for the nature of my engine it isn’t a good idea :confused:.
So, I took my time to remember my linear algebra classes and I came with an easy to use code that doesn’t need fancy things :stuck_out_tongue:.

player = someNodePath

# Correct the perspective before getting the distance
oldP = base.cam.getP()
base.cam.setP(0)
	                	
# Get the x and y distance beetwen the player and the camera
x = base.cam.getX(player)
y = base.cam.getY(player)

# The method for getting this angle is up to you
a = someAngleMagnitude
	                	
# Linear transform to rotate around a point
w1 = x * cos(a) - y * sin(a)
w2 = x * sin(a) + y * cos(a)

# Set the new position
base.cam.setX(player, w1)
base.cam.setY(player, w2)
	                	
# Rotate the camera towards the player
base.cam.lookAt(player)

# Reset the pitch to its original value
base.cam.setP(oldP)     

As you can see, the player variable can be any instance of a class that descends from NodePath (or maybe more. I am somewhat new to Panda3D :stuck_out_tongue:).
The only trick here is that if you have a pitched camera then you have to reset it, because this gives you values you are not expecting and your camera would not do what you want.
Also, the angle magnitude is up to you because only you know how you want to make the user moves the camera.
In my case, all this code is running within a task and I am getting the someAngleMagnitude value from a joystick.

As you can see, this is only the main algorithm., but it is quite easy to make it work in any situation you want.

There you are: Clean, easy and small; just as I like algorithms :stuck_out_tongue:.
Hope this would be useful for you :smiley:.

There is an easy way to do this.

You might want to reconsider the nature of your engine to make it a good idea.

You do not want sin and cos calculations every frame.
An easier way to do this:
Create a dummy node.
Reparent camera to dummy node, with some distance.
Rotate dummy node.
Like this:

dummy=render.attachNewNode("dummyNode")
base.camera.reparentTo(dummy)
base.camera.setPos(10,0,0) # 10 = distance between cam and point
base.camera.setH(60) #this will rotate it 60 degrees around the point

the same idea pro-rsoft wrote, but a (should) working example, with some additional stuff (like moving, rotation, etc…):

Just move the mouse to the sides of the screen or use the arrow-keys…

from pandac.PandaModules import *
from direct.gui.DirectGui import *
from direct.interval.IntervalGlobal import *
from direct.showbase.DirectObject import *
from direct.task import Task

def limit( val, minVal, maxVal ):
  return max( minVal, min( val, maxVal ) )

from direct.interval.IntervalGlobal import *

from pandac.PandaModules import Trackball

import pickle

# a 3th person camera, which looks at the self.cameraLookatNode
class thirdCameraClass(DirectObject):
  def __init__(self):
    # disable default mouse movements
    base.disableMouse()
    
    # create a fixed 45 degree view angle
    camera.setP(-60)
    camera.setPos(-25,-25,50)
    
    self.camTask = taskMgr.add(self.camLoop,'camLoop')
    
    self.cameraLookatNode = render.attachNewNode('cameraLookatNode')
    self.cameraLookatNode.setH( -45 )
    self.cameraPositionNode = self.cameraLookatNode.attachNewNode('cameraPositionNode')
    self.cameraPositionNode.setPos( Vec3( 0, -15, 0 ) )
    # update task of view angle
    taskMgr.add( self.cameraRotationTask, 'cameraRotationTask', -1 )
    
    
    self.keyMap = dict()
    keybindings = { "arrow_left"  : "rotate_left"
                  , "arrow_right" : "rotate_right"
                  , "arrow_up"    : "move_forward"
                  , "arrow_down"  : "move_backward" }
    
    for key, bind in keybindings.items():
      self.accept( key, self.setKey, [bind,1])
      self.accept( key+"-up", self.setKey, [bind,0])
      self.keyMap[bind] = 0
    
    self.keyCalls = { "rotate_left"   : self.turn_left
                    , "rotate_right"  : self.turn_right
                    , "move_forward"  : self.move_forward
                    , "move_backward" : self.move_backward }
    
    taskMgr.doMethodLater(1.0/30, self.handleKey, 'camera_key_handler')
  
  #Records the state of the arrow keys
  def setKey( self, key, value ):
    self.keyMap[key] = value
  
  def handleKey( self, task ):
    for key, value in self.keyCalls.items():
      if self.keyMap[key]:
        value()
    return Task.again
  
  def cameraRotationTask( self, task ):
    lookAtPos = self.cameraLookatNode.getPos(render)
    relPos = self.cameraPositionNode.getPos()
    v = relPos.getY()
    # changes the viewangle to more flat when zooming down
    self.cameraLookatNode.setP( v * 1.3 )
    pos = self.cameraPositionNode.getPos(render)
    # look at the positition
    camera.setPos( pos )
    camera.lookAt( lookAtPos )
    return Task.cont
  
  def camLoop(self, task):
    """ move the camera if the mouse touches the edges of the screen
    """
    if not base.mouseWatcherNode.hasMouse():
        return Task.cont
    
    timer = globalClock.getDt()
    
    mpos = base.mouseWatcherNode.getMouse()
    mousePosX = mpos.getX()
    mousePosY = mpos.getY()
    
    if mousePosX > 0.9:
      self.move_left( timer )
      
    if mousePosX < -0.9:
      self.move_right( timer )
    
    if mousePosY > 0.9:
      self.move_forward( timer )
    
    if mousePosY < -0.9:
      self.move_backward( timer )
    
    return Task.cont
  
  def turn_left(self):
    self.cameraLookatNode.setH( self.cameraLookatNode.getH() - 5 )

  def turn_right(self):
    self.cameraLookatNode.setH( self.cameraLookatNode.getH() + 5 )
  
  def move_forward( self, timer=1.0/30 ):
    multiplier = self.cameraPositionNode.getPos().getY() * timer / 2.0
    diffPos = camera.getPos( render ) - self.cameraLookatNode.getPos( render )
    diffPos.normalize()
    self.cameraLookatNode.setX( self.cameraLookatNode.getX() + diffPos.getX() * multiplier )
    self.cameraLookatNode.setY( self.cameraLookatNode.getY() + diffPos.getY() * multiplier )
    self.limit_movement()
  
  def move_backward( self, timer=1.0/30 ):
    multiplier = self.cameraPositionNode.getPos().getY() * timer / 2.0
    diffPos = camera.getPos( render ) - self.cameraLookatNode.getPos( render )
    diffPos.normalize()
    self.cameraLookatNode.setX( self.cameraLookatNode.getX() - diffPos.getX() * multiplier )
    self.cameraLookatNode.setY( self.cameraLookatNode.getY() - diffPos.getY() * multiplier )
    self.limit_movement()
  
  def move_left( self, timer=1.0/30 ):
    multiplier = self.cameraPositionNode.getPos().getY() * timer / 2.0
    diffPos = camera.getPos( render ) - self.cameraLookatNode.getPos( render )
    diffPos.normalize()
    self.cameraLookatNode.setX( self.cameraLookatNode.getX() + diffPos.getY() * multiplier )
    self.cameraLookatNode.setY( self.cameraLookatNode.getY() - diffPos.getX() * multiplier )
    self.limit_movement()
  
  def move_right( self, timer=1.0/30 ):
    multiplier = self.cameraPositionNode.getPos().getY() * timer / 2.0
    diffPos = camera.getPos( render ) - self.cameraLookatNode.getPos( render )
    diffPos.normalize()
    self.cameraLookatNode.setX( self.cameraLookatNode.getX() - diffPos.getY() * multiplier )
    self.cameraLookatNode.setY( self.cameraLookatNode.getY() + diffPos.getX() * multiplier )
    self.limit_movement()
  
  def limit_movement( self ):
    # limit position of camera look-at position
    self.cameraLookatNode.setX( limit( self.cameraLookatNode.getX(), 0, 86 ) )
    self.cameraLookatNode.setY( limit( self.cameraLookatNode.getY(), 0, 86 ) )
    pos = self.cameraLookatNode.getPos(render)
    pos = [pos[0], pos[1], pos[2]]
  
if __name__ == '__main__':
  print "limit test limit(5,0,10)", limit(5,0,10)
  print "limit test limit(-5,0,10)", limit(-5,0,10)
  print "limit test limit(15,0,10)", limit(15,0,10)
from thirdCamera import thirdCameraClass
self.thirdCamera = thirdCameraClass()

This is a code snipplet i wrote quite a while ago, and is part of a bigger project, so it may contain obsolete stuff…

Well, thanks for all your suggestions and opinions. I’m sure they all will be useful for me :slight_smile:.

The fact is that I’m making an RPG and I wanted to use a Zelda Ocarina of Time like camera system.
Basically, when you move forward the camera follows you, when you move towards the camera it lets you get closer than when you move forward. Once you are at certain distance it will begin to move backwards.
When you move left or right the camera stays in its position but it changes its heading in order to face to you.

That’s why for my system it’s not a good idea to have the camera atached to a dummy node all the time. I think is easier to move the camera when it is supposed to move than moving the camera all the time and then reposition and rotate it so that it seems it stayed static O_o.

Yup. In fact, the snippet I posted is only the core of the rotation system.
Im my real code it’s running within a task because you can rotate the camera using a joystick and Pygame needs you to poll every frame for state changes in the joystick (… at least I think so :stuck_out_tongue:).

Of course, I made the pertinent corrections and testings on the stick position so that the camera rotates when it is supposed to.
Don’t worry, I have tested the camera for a loooooong time and it works just as a player would expect :slight_smile:.

By the way, thank you for posting your code, Hypnos :open_mouth: .
I need to take my time and review it step by step :smiley:.

As I said, thank you for your comments :slight_smile:.