9
votes

I'm working on a simple Three.js demo that uses OrbitControls.js.

I'd like to change the behavior of panning in OrbitControls. Currently, when you pan the camera, it moves the camera in a plane that is perpendicular to the viewing direction. I'd like to change it so that the camera stays a constant distance from the ground plane and moves parallel to it. Google Earth uses a similar control setup.

Edit: I should have mentioned this detail in the first place, but I'd also like the point where you click and start dragging to remain directly under the cursor throughout the entire drag. There needs to be that solid connection between the mouse movement and what the user expects to happen on the screen. Otherwise, it feels as though I'm 'slipping' when I try to move around the scene.

Can someone give me a high-level explanation of how this might be done (with or without OrbitControls.js)?

4
Is your "ground plane" actually a plane (e.g. Flat, or does it have variable height)? - caseygrun
It's actually flat. It's just an infinite plane located at (0,0,0) with an up vector of (0,1,0). - Justin
Oh, so you just want to pan only in the world X/Z directions, maintaining the camera at a fixed Y position? - caseygrun
Correct. The Y position can only change if I rotate the camera around the target point, or if I dolly the camera toward the target point. - Justin
Correct. But I'm not sure we have the same definition of 'rotate the camera.' When you rotate, the camera orbits around target point (which is usually on the plane) and always turns to look at that target point. - Justin

4 Answers

11
votes

EDIT: OrbitControls now supports panning parallel to the "ground plane", and it is the default.

To pan parallel to screen-space (the legacy behavior), set:

controls.screenSpacePanning = true;

Also available is MapControls, which has an API similar to that of Google Earth.

three.js r.94

5
votes

Some time ago I was working on exactly this issue, i.e. adaptation of OrbitControls.js to map navigation.

3
votes

I figured it out. Here's the overview:

  1. Store the mousedown event somewhere.
  2. When the mouse moves, get the new mousedown event.
  3. For each of those points, find the points on the plane where those clicks are located (You'll need to put the points into camera space, transform them into world space, then fire a ray from the camera through each point to find their intersections with the plane. This page explains the ray-plane intersection test).
  4. Subtract the world-space start intersection point from the world-space end intersection point to get the offset.
  5. Subtract that offset from the camera's target point and you're done!

In the case of OrbitControl.js, the camera always looks at the target point, and its position is relative to that point. So when you change the target, the camera moves with it. Since the target always lies on the plane, the camera moves parallel to that plane (as long as you're panning).

1
votes

You should set your camera 'up' to z axe:

camera.up.set(0,0,1)

And then, the main problem with OrbitControl is its panUp() function. It should be fixed.

My pull request : https://github.com/mrdoob/three.js/pull/12727

y axe is relative to camera axes and should be relative to a fixed plan in the world. To define the expected y axe, make a 90° rotation of camera x axe, based on world z axe.

v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
v.applyAxisAngle( new THREE.Vector3( 0, 0, 1 ), Math.PI / 2 );
v.multiplyScalar( distance );
panOffset.add( v )

Enjoy!