Zooming in with Three.js OrthographicCamera by focusing on the cursor位置

In my Three.js project, I am using an OrthographicCamera and OrthographicTrackBallControls for zooming and panning. I am attempting to implement a functionality to zoom to the cursor position, but so far, I have been unsuccessful. To start, here is how I am retrieving the 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);

After researching on StackOverflow, I found that there is a lot of information on achieving this with a PerspectiveCamera, but those methods do not work with the OrthographicCamera I am using. There is an example here that demonstrates what I am trying to achieve, but the code responsible for it is hidden, although it appears that the camera position is being adjusted.

Another similar question on StackOverflow suggests modifying camera.left, camera.right, camera.top, and camera.bottom, but I have not had success with this approach. While it seems like a viable option, I am unsure about the calculations required to obtain the correct values for these properties.

As I see it, I have two potential approaches:

  1. Adjust the camera's left/right/top/bottom to obtain the correct view rectangle.
  2. Modify the camera's position.

However, I do not know how to determine the values needed for either method, let alone which approach is preferable.

UPDATE 11/16/2018:

I have updated my function based on this example:

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();
}

Initially, this seems to work as intended: the camera zooms to the mouse point. However, after some zooming, the camera begins to "jump" erratically, with the mesh no longer appearing on the screen.

An indicator that might help is that I have an axis helper on the screen that changes orientation when the issue arises. When the scene loads, the X-axis helper points to the left, but when the camera starts jumping and the mesh disappears, the X-axis helper rotates to point right.

Additionally, if I first zoom out, I can then zoom in further before the mesh vanishes. I am uncertain of the significance of these observations, but any assistance would be greatly appreciated.

Answer №1

Coming back to the grind after celebrating the start of the New Year, the struggle to solve this issue has been a lengthy one. Six pages of A4 paper filled with linear algebra formulas culminates 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();
}

Take note of the unique calculation method for mX and mY to ensure accuracy for a viewport.

Answer №2

Utilizing the D3-library with its zoom feature may appear to be a suitable solution in this scenario. However, abandoning the three-controls is often not an ideal option.

If you desire a zoom behavior similar to that of Google Maps, the code snippet below could be beneficial:

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;

Another issue you may encounter could be related to the frustum. However, I'm still a beginner with Three xD so I may not have all the answers.

Answer №3

This is my unique solution to the problem (I have not tested any other solutions)

function adjustOrthoCamZoom(orthoCam, zoomFactor, zoomCenter) {
    
    // 1) Modify the Frustum
        // Adjust the camera's frustum to achieve zoom
        // Instead of changing the zoom property directly, manipulate the frustum
        // This ensures a valid frustum and same visual effect
        orthoCam.left = orthoCam.left * zoomFactor;
        orthoCam.right = orthoCam.right * zoomFactor;
        orthoCam.bottom = orthoCam.bottom * zoomFactor;
        orthoCam.top = orthoCam.top * zoomFactor;

    // 2) Adjust the Camera Position
        // Move the camera closer or farther from the zoom center to compensate for zoom
        const distanceCamToZoomCenter = orthoCam.position.clone().sub(zoomCenter);
        const newDistance = distanceCamToZoomCenter.clone().multiplyScalar(zoomFactor);
        const newCamPos = zoomCenter.clone().add(newDistance);
        orthoCam.position.copy(newCamPos);

    // 3) Update camera projection matrix
        orthoCam.updateProjectionMatrix();
    
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Troubleshooting V-model errors within VueJS components

Just dipping into VueJS and trying out a chat feature from a tutorial. I noticed the tutorial uses v-model in a component, but when I replicate it, the component doesn't display on the screen and the console throws a "text is not defined" error. Strug ...

Why is it that injecting javascript within innerHTML seems to work in this case?

According to the discussion on this thread, it is generally believed that injecting javascript in innerHTML is not supposed to function as expected. However, there are instances where this unconventional method seems to work: <BR> Below is an examp ...

The DataType.MultilineText field in my modal popup partial view is causing all validation to fail

I encountered a peculiar issue recently. After creating a new asp.net mvc5 application, I decided to upgrade it to asp.net mvc-5.2.2. The problem arose with the following field:- [Required] [StringLength(200)] public string Name { get; set; } When ren ...

Tips for manipulating rows in HTML using jQuery when the id attribute is added

Apologies in advance for any language errors in my explanation I am working on an input table where each row has a unique ID, and the input in each row affects the next row. As new rows are added, I have implemented an incremental numbering system for the ...

Is it possible to log out a user in JavaScript by simply removing cookies?

Hey there, I currently have a node.js server running in the background which handles user login processes (I didn't create the backend code). app.use(function (req, res, next) { var nodeSSPI = require('node-sspi'); var nodeSSPIObj = n ...

Tips for transferring POST body data to a different route without losing any information

Assuming I have the following route: app.post('/category',function(req,res){ res.redirect('/category/item'); }) In this scenario, the user will submit data to the '/category' route and then automatically be redirected ...

Tips for changing the TextField variant when it receives input focus and keeping the focus using Material-UI

When a user focuses on the input, I'd like to change the variant of the TextField. The code snippet below accomplishes this, but the input loses focus. This means the user has to click again on the input to focus and start typing. import React, { useS ...

Guide to ensuring the navbar remains at the top of the webpage following an image

I am attempting to create a sticky navbar that stays at the top of the page once the header has been scrolled past. It should have a similar effect as shown in this example on codepen.io, but with the addition of an image that stretches across most of the ...

The toggle password visibility eye is experiencing an error with an Uncaught ReferenceError: An invalid assignment is being attempted in the code

Here's a code I wrote for toggling password visibility, but I encountered an error: HTML: <!--Hide/Show password--> <button class="form-password__visibility" type="button" onclick="$('#inp ...

Is it necessary for the scope to be separated?

In the document object model (DOM), I have two straightforward directives that are responsible for creating similar elements. Both directives have an isolated scope. Each directive has an ng-click attribute that calls a method to display a message. One d ...

Tracking in node.js to detect the creation of a new file

I'm currently working on a node.js script that requires immediate action upon the creation of a file with a specific name in a designated directory. While one way to achieve this could be through continuous calls to fs.readdir until the desired file i ...

The express js backend (mongodb) is returning {error:{ }}

I'm learning how to POST Data to a Mongo DB using Express.js. Recently, I picked up Express.js for backend server development and this is my app.js const express = require("express"); const mongoose = require("mongoose"); require("dotenv/config") con ...

Discover the method for selecting an element within a table that includes a style attribute with padding using Jquery

Looking for a way to identify a td element with the padding style in a table I've attempted to locate a td element with the padding style attribute in a table $(Table).find('td[style="padding:"]') or $(Table).find('td[style] ...

Update the user information by using its unique identifier at a specific location

I am currently working on editing user data with a specific MongoDB instance. When I click on the user's details, a modal popup appears with an update button below it. My goal is to have the user data updated when this button is clicked for the partic ...

Having trouble updating an array in a mongoose document?

Need help with updating an array in a document by adding or replacing objects based on certain conditions? It seems like only the $set parameter is working for you. Here's a look at my mongoose schema: var cartSchema = mongoose.Schema({ mail: Stri ...

Interactive html5 canvas game with swiping mechanism

Currently, I am in the research phase for a new HTML5 canvas game that I want to create as a personal project to enhance my skills. Surprisingly, I have not been able to find any valuable articles or tutorials online about creating swipe-based games. The ...

Enhance Vue functionality by adding a versatile mutation that can be utilized across

I am facing a challenge where I have a mutation that needs to be reused in multiple Vuex modules but should only modify the state at each module level. Is there a way to separate out this mutation so it can be easily added to each module's mutations w ...

What is the reason behind functions not requiring (args) when called with parameters?

Have you ever noticed the difference in behavior between these two code snippets? var foo = document.getElementById("foo"); foo.addEventListener("keydown", foo); function foo(e){ console.log(e.keyCode); } And this one: var foo = docum ...

Choose a list in order to create a new list

If I were to choose a specific state in Nigeria, what steps should I follow to generate a drop-down menu with a list of local governments within that state? ...

At what point does the cleanup function in UseEffect activate the clearInterval() function?

In the process of creating a timer for a Tenzie game, there is an onClick function that changes the state of isTimerActive from false to true when a user clicks a button. The initial value of tenzie state is also set to false, but once the user completes t ...