Here is an explanation of the math involved. We have two constraints:
- Keep the camera at a constant height.
- While the mouse cursor is down, "move" the ground to keep the same location under the cursor at all times.
"Move" is in quotes because we don't want to move the ground, we want to move the camera. Thing is, moving the camera in the opposite direction we would move the ground has the exact same effect, and I find it more intuitive to picture it as if the ground were moving.
With this knowledge, we move on to solving problem #1: what ground position is the mouse is over? First, we get the location of the mouse in world-space:
var mouseScreenPos = new THREE.Vector3();
mouseScreenPos.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouseScreenPos.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
var rayOrigin = (new THREE.Vector3(mouseScreenPos.x, mouseScreenPos.y, 0.0)).unproject(camera);
Then we obtain the direction of the ray under the mouse by adding a bit of depth to get a point "more under" the mouse, and subtracting that from the world position:
var rayPos1 = (new THREE.Vector3(mouseScreenPos.x, mouseScreenPos.y, 1.0)).unproject(camera);
var rayDirection = rayPos1.sub(rayOrigin);
Finally, we can use these two points to get the location in space we want to hold constant. For my purposes I'm calculating where the ray intersects the XZ plane at
y = -5, but this may differ for you depending on where your objects are located.
var ySlope = (planeY - rayOrigin.y) / rayDirection.y;
var xIntersect = rayDirection.x * ySlope + rayOrigin.x;
var zIntersect = rayDirection.z * ySlope + rayOrigin.z;
return new THREE.Vector3(xIntersect, planeY, zIntersect);
Now, we solve problem #2: how much to move the camera? This ends up being fairly easy now that we have the above formula for obtaining the ground position under the mouse. The algorithm goes like this:
- When mouse down is detected:
mouseDownPosition, the current ground position under the mouse
mouseDownCameraPosition, the current position of the camera
- When a mouse move is detected:
- Obtain the new ground position under the mouse and subtract it from
- Subtract that from
camera.position to this final result.
As a final note, I think the Raycaster can aid in the case where there's variable-height terrain, i.e. where we don't know what
planeY above should be upon
mousedown. However, we still need to calculate the ray/plane intersection when the mouse moves, with
planeY set to the Y of the point the raycaster found. Otherwise, our first constraint (keep camera at constant height) will be broken.