Skip to content
Advertisement

OrbitControls on a shared camera cannot handle panning

I am trying to create two renderers on two different div containers. Each renderer wish has its own OrbitControls for updating the associated camera. The camera is shared between the two renderers.

The goal is to sync the camera for OrbitControl (so that two containers can have the same camera view) and render between the two render loops.

I noticed that the general rotation could be shared automatically. However, when I do panning, it behaves a bit strange. Here is a video:

https://user-images.githubusercontent.com/5498964/170499367-44c2581d-8089-492e-bc90-a6675976bb44.mov

This is the minimum reproducible code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>

    <div id="rend1" style="height: 400px; width: 400px;"></div>
    <div id="rend2" style="height: 400px; width: 400px;"></div>

<script type="module">
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.121.1/examples/jsm/controls/OrbitControls.js';

const rend1 = document.getElementById("rend1")
const rend2 = document.getElementById("rend2")

const camera = new THREE.PerspectiveCamera(40, 1, 0.01, 1000);
camera.position.copy(new THREE.Vector3(0, 1, 2));
camera.lookAt(new THREE.Vector3(0, 0, 0));

const r1 = new THREE.WebGLRenderer({
    antialias: true,
    preserveDrawingBuffer: true,
    precision: "highp",
    alpha: true,
  });
r1.setPixelRatio(window.devicePixelRatio);
const s1 = new THREE.Scene();
rend1.append(r1.domElement);

const c1 =  new OrbitControls(camera, r1.domElement)
s1.add(new THREE.AxesHelper(1));
s1.add(camera);
const g1 = new THREE.BoxGeometry( 0.5, 0.5, 0.5 );
const m1 = new THREE.MeshBasicMaterial( {color: 0x0080ff} );
const cube1 = new THREE.Mesh(g1, m1);
s1.add(cube1)

const r2 = new THREE.WebGLRenderer({
    antialias: true,
    preserveDrawingBuffer: true,
    precision: "highp",
    alpha: true,
  });
r2.setPixelRatio(window.devicePixelRatio);
const s2 = new THREE.Scene();
rend2.append(r2.domElement);
const c2 =  new OrbitControls(camera, r2.domElement)
s2.add(new THREE.AxesHelper(1));
s2.add(camera);
const g2 = new THREE.BoxGeometry( 0.5, 0.5, 0.5 );
const m2 = new THREE.MeshBasicMaterial( {color: 0x0080ff} );
const cube2 = new THREE.Mesh(g2, m2);
s2.add(cube2)



const render1 = () => {
    r1.setSize(400, 400);
    c1.update();
    r1.render(s1, camera);
    window.requestAnimationFrame(() => render1());
};
const render2 = () => {
    r2.setSize(400, 400);
    c2.update();
    r2.render(s2, camera);
    window.requestAnimationFrame(() => render2());
};
render1 ();
render2 ();
</script>
</body>
</html>

What is the reason and how could we achieve desired behavior?

Thank you!

Advertisement

Answer

The actual solution for this problem is to sync cameras and orbit control’s target explicitly as they change. See c1.addEventListener("change", () => { ... }} and c2.addEventListener("change", () => { ... }}.

Here is a complete code for solving the issue:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>

    <div id="rend1" style="height: 400px; width: 400px;"></div>
    <div id="rend2" style="height: 400px; width: 400px;"></div>

<script type="module">
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.121.1/examples/jsm/controls/OrbitControls.js';

const rend1 = document.getElementById("rend1")
const rend2 = document.getElementById("rend2")

const camera1 = new THREE.PerspectiveCamera(40, 1, 0.01, 1000);
camera1.position.copy(new THREE.Vector3(0, 1, 2));
camera1.lookAt(new THREE.Vector3(0, 0, 0));
const camera2 = new THREE.PerspectiveCamera(40, 1, 0.01, 1000);
camera2.position.copy(new THREE.Vector3(0, 1, 2));
camera2.lookAt(new THREE.Vector3(0, 0, 0));
const r1 = new THREE.WebGLRenderer({
    antialias: true,
    preserveDrawingBuffer: true,
    precision: "highp",
    alpha: true,
  });
r1.setPixelRatio(window.devicePixelRatio);
const s1 = new THREE.Scene();
rend1.append(r1.domElement);

const c1 =  new OrbitControls(camera1, r1.domElement)
s1.add(new THREE.AxesHelper(1));
s1.add(camera1);
const g1 = new THREE.BoxGeometry( 0.5, 0.5, 0.5 );
const m1 = new THREE.MeshBasicMaterial( {color: 0x0080ff} );
const cube1 = new THREE.Mesh(g1, m1);
s1.add(cube1)
c1.addEventListener("change", () => {
    camera2.position.copy(camera1.position)
    camera2.rotation.copy(camera1.rotation)
    c2.target.copy(c1.target)
})

const r2 = new THREE.WebGLRenderer({
    antialias: true,
    preserveDrawingBuffer: true,
    precision: "highp",
    alpha: true,
  });
r2.setPixelRatio(window.devicePixelRatio);
const s2 = new THREE.Scene();
rend2.append(r2.domElement);
const c2 =  new OrbitControls(camera2, r2.domElement)
s2.add(new THREE.AxesHelper(1));
s2.add(camera2);
const g2 = new THREE.BoxGeometry( 0.5, 0.5, 0.5 );
const m2 = new THREE.MeshBasicMaterial( {color: 0x0080ff} );
const cube2 = new THREE.Mesh(g2, m2);
s2.add(cube2)
c2.addEventListener("change", () => {
    camera1.position.copy(camera2.position)
    camera1.rotation.copy(camera2.rotation)
    c1.target.copy(c2.target)
})




const render1 = () => {
    r1.setSize(400, 400);
    c1.update();
    r1.render(s1, camera1);
    window.requestAnimationFrame(() => render1());
};
const render2 = () => {
    r2.setSize(400, 400);
    c2.update();
    r2.render(s2, camera2);
    window.requestAnimationFrame(() => render2());
};
render1 ();
render2 ();
</script>
</body>
</html>
User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement