I have an Orthographic camera where the position is { x:0, y:100, z:0 } and is pointing/looking at { x:0, y:0, z:0 }. At this point, I'm able to capture the mouse movement and translate it to make the pan correctly. If the mouse goes 10 px down/y I just have to move Z in the 3D world.

The problem is that I don't know how to calculate if the camera position is in perspective, let's say:

position: { x:50, y:50, z:50 }
lookAt: { x:0, y:0, z:0 }

I guess I have to use some trigonometry, but I'm very lost, to be honest. Any guide would be very helpful.


2 Answers 2


You can construct the camera's forward direction vector as:

forward = normalize(lookAt - position)

Then you can construct a vector pointing to the camera's right like so:

right = normalize(cross(worldUp, forward))

(Or the negation of this if you're in a right-handed coordinate system)

And lastly you can construct a vector pointing along the camera's local up direction like so:

up = cross(forward, right)

(Again, negate if you're in a right-handed coordinate system)

Then you can pan the camera by adding a multiple of these right and up vectors to its position. You'll likely want to move the lookAt point by the same amount, unless your goal is to orbit around the point.

  • \$\begingroup\$ Sorry, what normalize() cross() and worldUp are? \$\endgroup\$
    – Enzo
    Dec 3, 2018 at 18:15
  • 1
    \$\begingroup\$ Normalize means dividing a vector by its length, to produce a unit vector. Cross is the vector cross product, which produces a vector perpendicular to the two input vectors. worldUp is a unit vector in the direction you designate as "up" in your world (eg. it might be {0,1,0} if you think of the y axis as your up direction). These are pretty common terms in the linear algebra used in games, so you might want to read through some vector tutorials to get a grounding in these tools. \$\endgroup\$
    – DMGregory
    Dec 3, 2018 at 18:22
  • \$\begingroup\$ Would you recommend me a book to learn all of this? \$\endgroup\$
    – Enzo
    Dec 3, 2018 at 18:24
  • 1
    \$\begingroup\$ No, I wouldn't. I learned this stuff a long time ago, so I won't be an expert on what materials are best for learners today. Ask a search engine — they'll have a much more up to date catalogue of popular sources than I have. ;) \$\endgroup\$
    – DMGregory
    Dec 3, 2018 at 18:33
  • 1
    \$\begingroup\$ No, it does not mean that. Never do I say "worldUp is the amount the player has moved their mouse." It is the direction that you as the game developer choose to represent the vertical direction in your game world. For instance, in Unity's coordinate system, the y axis points upward, so worldUp is (0,1,0). In 3DS Max, the z axis points upward, so worldUp is (0,0,1). It is a constant set by your coordinate system, not a product of user input. \$\endgroup\$
    – DMGregory
    Dec 5, 2018 at 13:18

Here is the code explained by @DMGregory in Three.js

function panCamera({ position, lookAt, x, y }) {
    const worldUp = new THREE.Vector3(0, 1, 0).normalize()
    const distance = lookAt.clone().sub(position).normalize()
    const right = distance.clone().cross(worldUp).normalize()
    const up = distance.clone().cross(right)
    position = position.clone().add(right).add(up)
    lookAt = lookAt.clone().add(right).add(up)
    return { position, lookAt }

// Usage
const x = 5 // user moves 5px the mouse to the right
const y = 10 // 10px to the bottom

// getting a new position and lookAt
const { position, lookAt } = panCamera({ 
    position: new THREE.Vector3(50, 50, 50), // perspective example 
    lookAt: new THREE.Vector3(0, 0, 0),
    x, y

// Applying new cordinates
camera.position.set(position.x, position.y, position.z) // we can't just pass position as an argument :(

Here a full example with zoom using d3 to capture the mouse gestures: https://codepen.io/josema/pen/zMeNry?editors=0010

  • \$\begingroup\$ Note that some of the math we do in the panCamera method might strictly duplicate work already being done in the camera.lookAt method - so you might be able to take shortcuts by reading the camera's local up & right vectors straight out of the orientation data that camera.lookAt configures. \$\endgroup\$
    – DMGregory
    Dec 5, 2018 at 17:59
  • \$\begingroup\$ Not sure if I understand you \$\endgroup\$
    – Enzo
    Dec 5, 2018 at 18:16
  • 1
    \$\begingroup\$ The work we do to construct an up & right vector to define our movement plane? That's the same thing cameras need to do internally to be able to project the 3D scene onto the 2D plane of the image. So there's very likely some matrix or other orientation data inside the camera type we can just use instead of remaking the vectors ourselves. Similarly, since we're not changing the camera's orientation, we might not need to repeat this work by calling lookAt() after the pan. These aren't things you have to change, just opportunities for future exploration. ;) \$\endgroup\$
    – DMGregory
    Dec 5, 2018 at 18:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .