3D Tilting Camera from a fixed point - problem

Hi there, I have a problem with a camera I’m trying to set on a fixed point that looks down a vertical tunnel, the camera’s Pitch being -90. Since we’re looking down to a tunnel with the correct pitch, it gives way bottom and top limits, so when I want to look up or down in the tunnel, I’m actually changing the the pitch to -70 and -110 with this:

camera.setHpr(0, -90+mpos.getY()*20, 0)

Now here is the problem: I can’t get the view to change from the left to the right, when the pitch is “up” or “down”! Do I have to mess with Rotation or Yaw? I have tried with Rotation, like this:

camera.setHpr(0, -90+mpos.getY()*20, mpos.getX()*30)

but when the cursor goes from the bottom left to the upper left corner, instead of the camera following it, it crosses the middle to the opposite corner!

I based the camera tilting on “Ball in Maze” and “Looking and Gripping” samples that came with Panda3D.

import direct.directbase.DirectStart
from panda3d.core import Filename,AmbientLight,DirectionalLight
from panda3d.core import PandaNode,NodePath,Camera,TextNode
from panda3d.core import Fog
from direct.task.Task import Task
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.DirectObject import DirectObject
from direct.interval.MetaInterval import Sequence
from direct.interval.LerpInterval import LerpFunc
from direct.interval.FunctionInterval import Func
from math import pi, sin, cos
import sys

#Global variables for the tunnel dimensions and speed of travel
TUNNEL_SEGMENT_LENGTH = 50           
TUNNEL_TIME = 2

# Function to put instructions on the screen
def addInstructions(pos, msg):
    return OnscreenText(text=msg, style=1, fg=(1,1,1,1),
                        pos=(-1.3, pos), align=TextNode.ALeft, scale = .05)

# Function to put title on the screen
def addTitle(text):
    return OnscreenText(text=text, style=1, fg=(1,1,1,1),
                        pos=(1.3,-0.95), align=TextNode.ARight, scale = .07)

class World(DirectObject):

	def __init__(self):

		# Post the instructions
		self.title = addTitle("Marco Oliveira first experiment with Panda3D")
		self.inst1 = addInstructions(0.95, "Se no Passado nao houve registo")
		self.inst2 = addInstructions(0.90, "Da pessoa que eu serei,")
		self.inst3 = addInstructions(0.85, "Quando deixar de ser visto")
		self.inst4 = addInstructions(0.80, "Foi para o Futuro que viajei!")
		
		base.disableMouse() # disable mouse control in order to place camera
		camera.setPosHpr(0, 0, 10, 0, -90, 0) # define coordinates to make the camera face a direction
		base.setBackgroundColor(0, 0, 0) # define end of the tunnel with the color black
		
		### Define world map
		# define fog
		self.fog = Fog('distanceFog')
		self.fog.setColor(0, 0, 0)
		#Set the density/falloff of the fog.  The range is 0-1.
		self.fog.setExpDensity(.08)
		render.setFog(self.fog)
		
		#Load the tunel and start the tunnel
		self.initTunnel()
		self.contTunnel()
		
		# Add task to the task manager
		taskMgr.add(self.spinCameraTask, "spinCameraTask")

	# Code to initialize the tunnel
	def initTunnel(self):
		#Creates the list [None, None, None, None]
		self.tunnel = [None for i in range(4)]
    
		for x in range(4):
			#Load a copy of the tunnel
			self.tunnel[x] = loader.loadModel('/c/Panda3D-1.8.1/samples/Infinite-Tunnel/models/tunnel')
			#The front segment needs to be attached to render
			if x == 0: 
				self.tunnel[x].reparentTo(render)
			#The rest of the segments parent to the previous one, so that by moving
			#the front segement, the entire tunnel is moved
			else:
				self.tunnel[x].reparentTo(self.tunnel[x-1])
			#We have to offset each segment by its length so that they stack onto
			#each other. Otherwise, they would all occupy the same space.
			self.tunnel[x].setPos(0, 0, -TUNNEL_SEGMENT_LENGTH)
			#Now we have a tunnel consisting of 4 repeating segments with a
			#hierarchy like this:
			#render<-tunnel[0]<-tunnel[1]<-tunnel[2]<-tunnel[3]
		
	# This function is called to snap the front of the tunnel to the back
	# to simulate travelling through it
	def contTunnel(self):
		# Use slices to put the front of the list in the back
		self.tunnel = self.tunnel[1:] + self.tunnel[0:1]
		# Set the front segment to 0, which was TUNNEL_LENGTH when
		# the segments started
		self.tunnel[0].setZ(0)
		# Reparent the segment to follow the previous hierarchy
		self.tunnel[0].reparentTo(render)
		self.tunnel[0].setScale(.155, .155, .305)
		self.tunnel[3].reparentTo(self.tunnel[2])
		self.tunnel[3].setZ(-TUNNEL_SEGMENT_LENGTH)
		self.tunnel[3].setScale(1)
		
		self.tunnelMove = Sequence(
			LerpFunc(self.tunnel[0].setZ,
				duration = TUNNEL_TIME,
				fromData = 0,
				toData = TUNNEL_SEGMENT_LENGTH * .305),
			Func(self.contTunnel)
			)
		self.tunnelMove.start()
		
	def spinCameraTask(self, task):
		if base.mouseWatcherNode.hasMouse():
			mpos = base.mouseWatcherNode.getMouse()
			camera.setHpr(0, -90+mpos.getY()*20, 0)
		return Task.cont
			
tunnel = World()
run()

Sorry for the double post, but I gotta post an update to my code:

def spinCameraTask(self, task):
		if base.mouseWatcherNode.hasMouse():
			mpos = base.mouseWatcherNode.getMouse()
			if mpos.getY() < 0:
				if mpos.getX() < 0:
					camera.setHpr(mpos.getX()*-30, -90+mpos.getY()*20, mpos.getX()*30)
				else:
					camera.setHpr(mpos.getX()*-30, -90+mpos.getY()*20, mpos.getX()*30)
			elif mpos.getY() > 0:
				if mpos.getX() > 0:
					camera.setHpr(mpos.getX()*30, -90+mpos.getY()*20, mpos.getX()*-30)
				else:
					camera.setHpr(mpos.getX()*30, -90+mpos.getY()*20, mpos.getX()*-30)
				
		return Task.cont

With this I can get the camera to tilt in the direction of the cursor, and it has a smooth transition between the Top Left and Top Right corners, and Bottom Left and Bottom Right corners. However, when I go from the Bottom to the Top, in whatever side, the camera goes to the center first, and when the cursor crosses the X axis, the image skips a little.

Wild guess, might this have to do with the if-statements on the y-axis position?:

if mpos.getY() < 0:
         ....
elif mpos.getY() > 0:
         ....

to

if mpos.getY() < 0:
         ....
else:
         ....

(as you did for the mpos.getX() sections)
If the y position is 0 it doesnt change the HPR which might cause the small hick-up whn the position is changed further. If this is not the solution, could you clarify your sentence ‘skips a little’ a bit more?

EDIT: btw, at this point your if statement for posX doesnt do anything;

       camera.setHpr(mpos.getX()*30, -90+mpos.getY()*20, mpos.getX()*-30)
else:
       camera.setHpr(mpos.getX()*30, -90+mpos.getY()*20, mpos.getX()*-30)

has twice the same code, unless my eyes play tricks on me :slight_smile:

Thanks for the help, but I figured out my problem. I’ve hit a Gimbal lock by setting the camera’s pitch to -90º, which makes Yaw and Roll axis parallel, therefore not moving as I desired. I tried to work around this problem without knowing about it, and that’s why I have too much code… for nothing! :blush:

Am i correct then that you should look into quarternions? (another way of representing an angle, but with 4 digits, that does not have problems when something is rotated 90 degrees, never worked with them but thats what i understood when someone explained them to me)

I could do that as an experiment, but then I don’t want the camera to fully rotate around all axis for what I have in mind. Again, thanks for the help! I shall bother you with more question when I get to quaternions =P