# calculating size of 3d space unit in pixels on screen

Hi All, thanks in advance for any help anyone can offer.

I’m modifying the rts scroller camera from this post:

[Real-Time Strategy Camera)

I’ve got it setup almost entirely except for one problem. I am scrolling over a 3d mesh (flat plane for now) that is 128X128 and centered on the origin. Scrolling works fine, zooming works fine, but my clamping system needs to be improved. I obviously don’t want to over scroll the mesh, so I calculate the center of my scene and clamp my X and Y camera position to be within the map size, shifted by the distance of my zoom and angle of camera view. This works fine. I am clamping successfully, but only based on the coordinate at the center of my screen. What I really need to do is clamp based when my viewable area will display outside of my ground plane. This number can be thought of my as current clamp coords minus 1/2 of a screen in any direction.

Basically I need to know how to calculate how many units in 3d space == 1/2 a 2d screen, or I need a new approach for clamping to my groundplane.

Here is my code:

``````from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import VBase3, Point2, Point3

class gamecam(DirectObject):
def __init__(self):
self._gameCam = base.cam
base.camLens.setFov(40.0)
base.camLens.setFocalLength(35.0)
self._gameCam.setPos(0,0,30)

self.tolerance = 35             # How many pixels from the edge of the window
self.cameraMoveDist = 14        # How many units to move the camera
self.cameraZBounds = [25, 60]
self.cameraXBounds = [-64,64]
self.cameraYBounds = [-64,64]

#base.disableMouse()
self._gameCam.lookAt(0,15,0)    # Make the camera look at the floater (creates angle)

self.accept( 'wheel_up', self.zoom, [1] )       # Listen for mouse scrolling
self.accept( 'wheel_down', self.zoom, [-1] )    # for zooming

# Get the position of the cursor if it is in the window
if( base.mouseWatcherNode.hasMouse() ):
movementDirection = VBase3(0, 0, 0) # Which direction to move the camera
md = base.win.getPointer(0)
x = md.getX()
y = md.getY()

# If the cursor is on the edges, move the camera
if( x + self.tolerance > 640 ):
movementDirection.setX(1)
elif( x < self.tolerance ):
movementDirection.setX(-1)
if( y + self.tolerance > 480 ):
movementDirection.setY(-1)
elif( y < self.tolerance ):
movementDirection.setY(1)

if movementDirection.x != 0 or movementDirection.y !=0:
self.moveCamera( movementDirection )

def moveCamera(self, dir):
deltaPos = dir * self.cameraMoveDist * globalClock.getDt()
cpos = self._gameCam.getPos() + deltaPos
zoomModifiedBound = tan(radians(26.5651)) * self._gameCam.getPos().getZ() #This num is specific to this: self._gameCam.lookAt(0,15,0).

cpos.x = min (max(cpos.x, self.cameraXBounds[0]),self.cameraXBounds[1])
cpos.y = min (max(cpos.y, self.cameraYBounds[0] - zoomModifiedBound),self.cameraYBounds[1] - zoomModifiedBound)

self._gameCam.setPos( cpos )

# Determines the vector from the camera to the floater and moves the
# camera closer to or farther from the floater along the vector
def zoom(self, dir):
camToFloater = VBase3(0, 0.15,0) - VBase3(0,0,0.30)
camToFloater *= dir

newPos = self._gameCam.getPos() + camToFloater
z = newPos.getZ()

if( z < self.cameraZBounds[1] and z > self.cameraZBounds[0]):
self._gameCam.setPos(newPos)

def setXBounds(self, arr2):
self.cameraXBounds = arr2

def setYBounds(self, arr2):
self.cameraYBounds = arr2``````

Well, self answer that is 75% correct:

``````from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import VBase3, Point2, Point3
from math import tan, radians, fabs

class gamecam(DirectObject):
def __init__(self):
self._gameCam = base.cam
self.viewOffset = 15

base.camLens.setFov(40.0)
base.camLens.setFocalLength(28.0)
self._gameCam.setPos(0,0,30)

self.tolerance = 35             # How many pixels from the edge of the window
self.cameraMoveDist = 14        # How many units to move the camera

self.cameraZBounds = [25, 60]
self.cameraXBounds = [-64,64]
self.cameraYBounds = [-64,64]

base.disableMouse()
self._gameCam.lookAt(0,self.viewOffset,0)    # Make the camera look at the offset (creates angle)

self.accept( 'wheel_up', self.zoom, [1] )       # Listen for mouse scrolling
self.accept( 'wheel_down', self.zoom, [-1] )    # for zooming

# Get the position of the cursor if it is in the window
if( base.mouseWatcherNode.hasMouse() ):
movementDirection = VBase3(0, 0, 0) # Which direction to move the camera
md = base.win.getPointer(0)
x = md.getX()
y = md.getY()

# If the cursor is on the edges, move the camera
if( x + self.tolerance > 640 ):
movementDirection.setX(1)
elif( x < self.tolerance ):
movementDirection.setX(-1)
if( y + self.tolerance > 480 ):
movementDirection.setY(-1)
elif( y < self.tolerance ):
movementDirection.setY(1)

if movementDirection.x != 0 or movementDirection.y !=0:
self.moveCamera( movementDirection )

def moveCamera(self, dir):
deltaPos = dir * self.cameraMoveDist * globalClock.getDt()
cpos = self._gameCam.getPos() + deltaPos

camAngle = 90 - fabs(base.cam.getHpr().getY())
fov = base.camLens.getFov()
outOfViewAngleY = camAngle - fov.getY()/2
outOfViewAngleX = (90-fov.getX())/2
camZ = self._gameCam.getPos().getZ()

camYToScreenTop = tan(radians(camAngle + outOfViewAngleY)) * camZ

print camYToScreenBottom, camYToScreenTop
cpos.x = min (max(cpos.x, self.cameraXBounds[0] + camXToScreenEdge),self.cameraXBounds[1] - camXToScreenEdge)
cpos.y = min (max(cpos.y, self.cameraYBounds[0] - camYToScreenBottom),self.cameraYBounds[1] - camYToScreenTop)

self._gameCam.setPos( cpos )

# Determines the vector from the camera to the floater and moves the
# camera closer to or farther from the floater along the vector
def zoom(self, dir):
camToFloater = VBase3(0,self.viewOffset/100,0) - VBase3(0,0,0.3)
camToFloater *= dir

newPos = self._gameCam.getPos() + camToFloater
z = newPos.getZ()

if( z < self.cameraZBounds[1] and z > self.cameraZBounds[0]):
self._gameCam.setPos(newPos)
self.moveCamera(VBase3(0,0,0))

def setXBounds(self, arr2):
self.cameraXBounds = arr2

def setYBounds(self, arr2):
self.cameraYBounds = arr2``````

This works for clamping in all directions except the top edge of the screen. I’m not grasping something for the top, because as far as I can tell this is the appropriate way to calculate it, but I still overshoot by a few units.

If anyone has ideas I appreciate it!
Thanks

Finally looked at this again. The fix is:

camYToScreenTop = tan(radians(camAngle + fov.getY()/2)) * camZ