Determine the camera's new location following a rotation around a specific point on the mesh

I'm currently working on implementing the following scenario:

  1. When the user clicks on a mesh, focus the perspective camera on that point
  2. Allow the user to rotate the camera using orbitControls
  3. After rotation, adjust the camera position and target so it appears that the camera is still looking at the mesh from the same angle as before

This should give the illusion that the user has rotated the camera around a specific point on the mesh.

While steps 1 and 2 are clear, I'm facing confusion with step 3.

I have included some images to help illustrate the process:

1) User clicks on a point on the mesh

2) Camera focuses on the clicked point

3) User rotates the camera around the selected pivot point

4) On mouse up, the camera should be repositioned so the selected point appears unchanged and the camera angle remains consistent

The question is how to achieve the behavior shown in the 4th image.

The key is to make it seem like the camera is maintaining the same perspective while the mesh appears to be rotated around the clicked point.

Any assistance would be greatly appreciated. Thank you!

Answer №1

I trust that I have understood your query correctly. Calculate the variance between the starting target and the target point on an object, and upon releasing the mouse click - adjust your camera position by this calculated value.

var w = window.innerWidth,
  h = window.innerHeight;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, w / h, 1, 1000);
camera.position.setScalar(5);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(w, h);
document.body.appendChild(renderer.domElement);

var controls = new THREE.OrbitControls(camera, renderer.domElement);

scene.add(new THREE.GridHelper(10, 10));

var prismGeom = new THREE.ConeBufferGeometry(1, 2, 3);
prismGeom.translate(0, 1, 0);
var prismMat = new THREE.MeshBasicMaterial({
  color: "red",
  wireframe: true
});
var prism = new THREE.Mesh(prismGeom, prismMat);
prism.position.set(-1, 0, -2);
scene.add(prism);

var centralMarker = new THREE.Mesh(new THREE.SphereBufferGeometry(0.125, 4, 2), new THREE.MeshBasicMaterial({
  color: "aqua"
}));
scene.add(centralMarker);

var pointMarker = new THREE.Mesh(centralMarker.geometry, new THREE.MeshBasicMaterial({
  color: "magenta"
}));
pointMarker.visible = false;
scene.add(pointMarker);


var oldTarget = scene.position;
var targeted = false;
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects = [];

window.addEventListener("mousedown", onMouseDown, false);
window.addEventListener("mouseup", onMouseUp, false);

function onMouseDown(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);
  intersects = raycaster.intersectObject(prism);
  if (intersects.length > 0) {
    targeted = true;
    controls.target.copy(intersects[0].point);
    pointMarker.position.copy(intersects[0].point);
    pointMarker.visible = true;
    controls.update();
  }
}

function onMouseUp(event) {
  if (!targeted) return;
  let shift = new THREE.Vector3().copy(oldTarget).sub(controls.target);
  camera.position.add(shift);
  controls.target.copy(oldTarget);
  controls.update();
  targeted = false;
  pointMarker.visible = false;
}

renderer.setAnimationLoop(() => {
  renderer.render(scene, camera)
})
body {
  overflow: hidden;
  margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

Answer №2

const width = window.innerWidth,
  height = window.innerHeight;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
camera.position.setScalar(5);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);

const controls = new THREE.OrbitControls(camera, renderer.domElement);

scene.add(new THREE.GridHelper(10, 10));

const prismGeometry = new THREE.ConeBufferGeometry(1, 2, 3);
prismGeometry.translate(0, 1, 0);
const prismMaterial = new THREE.MeshBasicMaterial({
  color: "red",
  wireframe: true
});
const prismObj = new THREE.Mesh(prismGeometry, prismMaterial);
prismObj.position.set(-1, 0, -2);
scene.add(prismObj);

const centralMarker = new THREE.Mesh(new THREE.SphereBufferGeometry(0.125, 4, 2), new THREE.MeshBasicMaterial({
  color: "aqua"
}));
scene.add(centralMarker);

const pointMarker = new THREE.Mesh(centralMarker.geometry, new THREE.MeshBasicMaterial({
  color: "magenta"
}));
pointMarker.visible = false;
scene.add(pointMarker);

const initialTarget = scene.position;
let isTargeted = false;
const raycaster = new THREE.Raycaster();
const mousePosition = new THREE.Vector2();
let clickedIntersects = [];

window.addEventListener("mousedown", onMouseDown, false);
window.addEventListener("mouseup", onMouseUp, false);

function onMouseDown(event) {
  mousePosition.x = (event.clientX / window.innerWidth) * 2 - 1;
  mousePosition.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mousePosition, camera);
  clickedIntersects = raycaster.intersectObject(prismObj);
  if (clickedIntersects.length > 0) {
    isTargeted = true;
    controls.target.copy(clickedIntersects[0].point);
    pointMarker.position.copy(clickedIntersects[0].point);
    pointMarker.visible = true;
    controls.update();
  }
}

function onMouseUp(event) {
  if (!isTargeted) return;
  const shift = new THREE.Vector3().copy(initialTarget).sub(controls.target);
  camera.position.add(shift);
  controls.target.copy(initialTarget);
  controls.update();
  isTargeted = false;
  pointMarker.visible = false;
}

renderer.setAnimationLoop(() => {
  renderer.render(scene, camera)
})
body {
  overflow: hidden;
  margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

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

What is the best way to send the totalAmount data from one child component to its parent, and then pass it on to another

I am facing an issue with passing the totalAmount data from cart.js to the parent component (app.js) and then further to another child component (info.js). Despite my attempts, it seems like I am unable to pass it correctly as I encounter the following err ...

Is it necessary to implement the useCallback hook along with React.memo for rendering optimization in React components?

As I delved into understanding how useCallback and useMemo function in React to optimize app performance, I decided to create a simple app for experimentation. What I observed was that the callback function (OnBlurHandler) passed to my child component trig ...

Dispose the inputpicker filter after setting the value

I am currently utilizing the "Jquery inputpicker plugin" for creating dropdown menus. More information about this plugin can be found here. To initialize my dropdown, I use the following code: $('#test').inputpicker({ data:[ {value:"1 ...

"Troubleshooting callback errors and viewing statistics in multi-configuration setups

Is it possible to utilize multiple Webpack configs while in watch mode? I have noticed that the compilation callback behaves differently when using build versus watch. I couldn't find any references to this behavior and was curious if anyone else has ...

Tips for showcasing a dataset within a table using Angular.js ng-repeat

I am encountering an issue where I have to present an array of data within a table using Angular.js. Below is an explanation of my code. Table: <table class="table table-bordered table-striped table-hover" id="dataTable" > <tbody> ...

Encountering an issue with core.js:15723 showing ERROR TypeError: Unable to access property 'toLowerCase' of an undefined value while using Angular 7

Below, I have provided my code which utilizes the lazyLoading Module. Please review my code and identify any errors. Currently facing TypeError: Cannot read property 'toLowerCase' of undefined in Angular 7. Model Class: export class C_data { ...

Performing an AJAX POST request in Laravel

I'm struggling with getting this ajax call to work, and I can't seem to figure out what's going wrong. View (html) <div class="col-sm-6 col-xs-3 pl0" style="margin-left: -5px;"> <button class="btn btn-primary visible-xs" n ...

Tips for adjusting a pre-filled form?

When a form is rendered by onClick from a component, it loads with values. I want to be able to edit these current values and then perform an update operation. Here is the link to the sandbox: https://codesandbox.io/s/material-demo-forked-e9fju?file=/demo ...

Unknown and void

undefined === null => false undefined == null => true I pondered the logic behind undefined == null and realized only one scenario: if(document.getElementById() == null) .... Are there any other reasons why (undefined === null) ...

Sinon experiences delays during an AJAX call

I am working with Mocha Chai and Sinon to test a revealing module pattern and encountering a timeout failure. How can I effectively test a method that assigns variables from an AJAX request? Test.js (function () { 'use strict'; describe(&a ...

How to use $$[n] in Spectron/WebdriverIO to target the nth child element instead of the selector

Attempting to utilize Spectron for testing my Electron application has been challenging. According to the documentation, in order to locate the nth child element, you can either use an nth-child selector or retrieve all children that match a selector using ...

What is the best method for gracefully opening external links in a reusable new tab?

Here is the progress I have made so far: <script> var win; function OpenInNewTab(url ) { // var win; if (win) { win.close(); } win =window.open(url, 'myWin'); win.focus(); } </script> My links are structured like ...

Attempting to update the state by utilizing the values provided by useContext

Hey there! I'm currently working on a Gatsby React app and my main objective on this particular page is to remove some localStorage and reset context AFTER rendering. The timing of this reset is crucial because I need the page to initially render with ...

Ways to conceal various components while showcasing just a single element from every individual container

I am looking to only display specific span elements within their parent container (coin) while hiding the rest. The desired output should show only "1" and "first" while the other spans are hidden. <div class="types"> <div class=" ...

Executing JavaScript code within a Django application to load a file

Utilizing a JavaScript tool called jQuery FileTree within my Django application has presented a dilemma. This particular JavaScript requires access to a python script path, but incorporating Django template tags directly into JavaScript poses an issue. Wi ...

Troubleshooting multiple file upload errors in Asp.net Mvc using ajax

I am attempting to implement multiple file upload in MVC using jQuery ajax but I am encountering some issues. https://i.sstatic.net/34kXO.png Here is my HTML design and code: <div id="fileinputdiv"> <input type="file" name="mainfile" id="main ...

Could someone assist me in understanding why VScode is displaying an error stating it cannot locate a module?

node:internal/modules/cjs/loader:1051 throw err; ^ Error: The module '/Users/ben/Desktop/GA/unit2/week5/GA_Project_2/QuotaQuest/index.js' cannot be found. at Module._resolveFilename (node:internal/modules/cjs/loader:1048:15) at Modul ...

There is an issue with the sorting function: [orderBy:notarray]. The expected input was an array

Looking to incorporate pagination functionality from this source: http://jsfiddle.net/SAWsA/11/ [ { "name": "Micro biology", "id": "2747c7ecdbf85700bde15901cf961998", "category": "Other", "type": "Mandatory - No Certification", "cate ...

What steps should I take to insert a divider in a dropdown menu?

I am attempting to enhance my dropdown menu by incorporating a separator using the following code: <li class='divider'></li> Below is my HTML code with the separator included: <ul class="dropdown-menu dropdown-menu-right" id="ul ...

Tips on how to eliminate a dimension in a cube

I'm attempting to create a cube, but I'd like the ability to eliminate one dimension, transforming it into a two-dimensional shape like a sheet of paper. My approach: let cubeGeometry = new THREE.BoxGeometry(10, 0, 10); let cubeMaterial = new T ...