P.S.
P.S.

Reputation: 85

Three.js OrthographicCamera -- Zoom to cursor

My Three.js project uses and OrthographicCamera and OrthographicTrackBallControls for zoom/pan. I'm trying to add functionality to zoom to the cursor position with no luck. First things first, here's how I'm getting mouse position:

var mX = ((event.clientX - offset.left) / renderer.domElement.clientWidth) * 2 - 1;
var mY = -((event.clientY - offset.top) / renderer.domElement.clientHeight) * 2 + 1;
var vector = new THREE.Vector3(mX, mY, 0.5);
vector.unproject(camera);
vector.sub(camera.position);

Through looking on StackOverflow, there seems to be a lot of information on how to do this with PerspectiveCamera, but these methods don't work with OrthographicCamera. I was able to find this example:

https://htmlpreview.github.io/?https://github.com/w3dot0/three.js/blob/973bf1d40ef552dbf19c19654a79f70e2882563d/examples/misc_controls_zoom_to_mouse.html

Which does precisely what I am trying to accomplish, but the code that achieves this is hidden, though I am able to discern that the camera position is being changed.

Another SO question which is similar suggests changing camera.left, camera.right, camera.top and camera.bottom, but I have had no luck with this approach. This approach seems like a possibility, but I dont understand the calculations necessary to get the correct left, right, top and bottom values.

So the way I see it I have two possibilities:

  1. Change camera's left/right/top/bottom to get the correct view rectangle.
  2. Change camera position.

But I don't know how to get the values I need to accomplish either, let alone which is the better approach.

UPDATE 11/16/2018:

I've updated my function to this ( based on https://github.com/w3dot0/three.js/blob/973bf1d40ef552dbf19c19654a79f70e2882563d/examples/misc_controls_zoom_to_mouse.html):

zoomDirection = new THREE.Vector3();
function mousewheel(event) {
    event.preventDefault();
    var amount = event.deltaY / 100;
    var zoom = camera.zoom - amount;
    var offset = el.offset();
    ;
    var mX = amount > 0 ? 0 : ((event.clientX - offset.left) / renderer.domElement.clientWidth) * 2 - 1;
    var mY = amount > 0 ? 0 : -((event.clientY - offset.top) / renderer.domElement.clientHeight) * 2 + 1;

    zoomDirection.set(mX, mY, 0.001)
        .unproject(camera)
        .sub(camera.position)
        .multiplyScalar(amount / zoom);

    camera.position.subVectors(camera.position, zoomDirection);

    orthographictrackBallControls.target.subVectors(orthographictrackBallControls.target, webGl.zoomDirection);
    camera.zoom = zoom;
    camera.updateProjectionMatrix();
}

This seems to work at first: the camera zooms into the mouse point, but then the camera starts to "jump" around after a bit of zooming, with the mesh no longer visible on screen.

Something that might help: I have an axis helper in the screen as well that "flips" when it stops working as expected. When the scene is loaded, the X-axis helper point due left, but when I get to the point where the camera jumps and I no longer see the mesh, the X-axis helper flips to point due right.

Also, if I zoom OUT first, I can zoom in further before the mesh disappears. I'm not sure what this all adds up to but I would appreciate any help.

Upvotes: 1

Views: 2156

Answers (3)

andymel
andymel

Reputation: 5686

I add my working solution (have not tested the other solutions)

export function zoomOrthoCam(orthoCam: OrthographicCamera, zoomFactor: number, zoomCenter: Vector3) {
    
    // 1) Change the Frustum
        // set the new frustum of the cam (this does the zooming)
        // Instead of using the zoom (with orthoCam.zoom = orthoCam.zoom / zoomFactor) 
        // I change the frustum (borders of the viewable area), 
        // this has the same visual effect (but with a valid frustum)
        orthoCam.left =    orthoCam.left      * zoomFactor;
        orthoCam.right =   orthoCam.right     * zoomFactor;
        orthoCam.bottom =  orthoCam.bottom    * zoomFactor;
        orthoCam.top =     orthoCam.top       * zoomFactor;

    // 2) Compensate the cam position
        // I have to move the camera position to compensate for the alternative 
        // zoom center by moving the cam closer or farther away from that zoomCenter 
        // I simply change the distance between zoomCenter and cam by the zoom factor
        const distanceCamToZoomCenter = orthoCam.position.clone().sub(zoomCenter);
        const newDistance = distanceCamToZoomCenter.clone().multiplyScalar(zoomFactor);
        const newCamPos = zoomCenter.clone().add(newDistance);
        orthoCam.position.copy(newCamPos);

    // 3) has to be called when cam parameters are changed
        orthoCam.updateProjectionMatrix();
    
}

Upvotes: 0

Zebedee Mason
Zebedee Mason

Reputation: 21

First week back after New Year and it's taken too long to fix this. Six sides of A4 covered with linear algebra results in

if ( factor !== 1.0 && factor > 0.0 ) {
  const mX =  (event.offsetX / event.target.width ) * 2 - 1;
  const mY = -(event.offsetY / event.target.height) * 2 + 1;
  const vector1 = new THREE.Vector3(mX, mY, 0);
  const vector2 = new THREE.Vector3(0, 0, 0);
  vector1.unproject(this.camera);
  vector2.unproject(this.camera);
  vector1.subVectors(vector1, vector2);

  this.camera.zoom /= factor;
  vector1.multiplyScalar(factor - 1.0);
  this.camera.position.subVectors(this.camera.position, vector1);
  this.controls.target.subVectors(this.controls.target, vector1);
  this.camera.updateProjectionMatrix();
  this.camera.updateMatrix();
}

Note the different calculation of mX, mY so that it is valid for a viewport.

Upvotes: 1

andreas.herz
andreas.herz

Reputation: 127

Implementing the D3-library with its zoom function may seem like a good idea for this case. But giving up the three-controls is in a lot of cases not a deal.

If you want a zoom-behavior like in Google Maps, the following code could be helpful:

const cameraPosition = camera.position.clone();

// my camera.zoom starts with 0.2
if (zoomOld !== 0.2) {
  const xNew = this.curserVector.x + (((cameraPosition.x - this.curserVector.x) * camera.zoom) /zoomOld);
  const yNew = this.curserVector.y + (((cameraPosition.y - this.curserVector.y) * camera.zoom) /zoomOld);
  const diffX = cameraPosition.x - xNew;
  const diffY = cameraPosition.y - yNew;
  camera.position.x += diffX;
  camera.position.y += diffY;
  controls.target.x += diffX;
  controls.target.y += diffY;
}
zoomOld = camera.zoom;

Your other problem could be caused by the frustum. But I don't know, I'm still a newbie with Three xD

Upvotes: 0

Related Questions