ArcballControls - Extra rotation after recreating the controls
See original GitHub issueDescribe the bug
If a camera rotation is performed using ArcballControls before recreating the controls, an extra rotation happens after releasing the mouse on subsequent rotations. If no camera rotation is performed before recreating the controls, everything works fine.
To Reproduce
Steps to reproduce the behavior:
- Create a basic Three.js setup
- Add ArcballControls to it
- Make some function / logic to recreate the controls on demand
- Launch the page and rotate camera around
- Trigger the controls’ re-creation function / logic
- Try to rotate camera around again
- Notice the extra rotation after releasing the mouse button
Code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {margin: 0;}
</style>
</head>
<body>
<script type="importmap">
{
"imports":
{
"three": "http://localhost:8080/three.module.js",
"ArcballControls": "http://localhost:8080/ArcballControls.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import {ArcballControls} from "ArcballControls";
var scene, camera, light, renderer, earth, controls, reqid, paused = false;
function createcontrols()
{
controls = new ArcballControls(camera, renderer.domElement, scene);
controls.addEventListener("change", function(e) {renderer.render(scene, camera); controls.update();});
};
function create()
{
renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 1.75);
light = new THREE.AmbientLight("rgb(255, 255, 255)");
light.position.copy(camera.position);
var geometry = new THREE.SphereGeometry(1, 360, 180);
var material = new THREE.MeshPhongMaterial();
material.map = new THREE.TextureLoader().load("http://localhost:8080/Earth.png");
material.displacementMap = new THREE.TextureLoader().load("http://localhost:8080/Earth - Height.png");
material.displacementScale = 0.002 * 99;
earth = new THREE.Mesh(geometry, material);
scene = new THREE.Scene();
scene.add(camera);
scene.add(light);
scene.add(earth);
createcontrols();
document.addEventListener("keyup", function(e)
{
switch (e.code)
{
case "KeyP": console.log("P"); paused = !paused; if (!paused) {animate();} else {stagnate();}; break;
case "KeyR": console.log("R"); controls.dispose(); controls = undefined; createcontrols(); renderer.render(scene, camera); controls.update(); break;
};
}, {passive: false});
};
function animate()
{
if (paused) {return;};
requestAnimationFrame(animate);
earth.rotation.y += 0.01;
renderer.render(scene, camera);
};
function stagnate()
{
if (!paused) {return;};
cancelAnimationFrame(reqid);
};
create();
animate();
</script>
</body>
</html>
Live example
None
Expected behavior
For the simpleton code above, drag to rotate the globe, press the R key to recreate ArcballControls, then rotate again to see how it “jumps” to a new rotation after releasing the mouse. I expect that the jump does not happen (like it doesn’t for the other controls). My actual usage scenario is switching from one control to another (all 8 included in my code) on some mouse scroll, by the way - I chose to illustrate recreating the controls in the interest of simplicity. Let me know if this can be replicated, or if by any chance there is something obvious missing from the code above (like updating some matrix and such) that fixes the issue.
Screenshots
None
Platform:
- Device: Desktop
- OS: Windows 10
- Browser: Chrome, Edge WebView2 via the WebView plugin for Rainmeter
- Three.js version: r.140, r.143
Issue Analytics
- State:
- Created a year ago
- Comments:7
Top GitHub Comments
Great to hear that you figured out where the issue came from and what needed to be done to fix it, in such a short time - many thanks! Indeed, it makes sense for the controls to behave that way if the values of some needed variables are lost in the recreating process.
I settled for modifying those internal variables instead of resetting because I just can’t have one of my controls messing the user’s current rotation (reset or no reset), and after all, it’s not the first time I had to do this with ArcballControls, having to toggle the gizmos visibility using a similar internal variable via
controls.setGizmosVisible(!controls._gizmos.visible);
. Right now, in my actual code, I have the global variablecup = new THREE.Vector3().copy(camera.up);
when instantiating the camera (see, I can reuse variables too), and addedcontrols._up0.copy(cup); controls._upState.copy(cup);
directly to the ArcballControls section of my controls building function. Everything works perfectly now - I appreciate that you included the alternative in the fiddle.As for the general advice, it’s a good one. Thing is, recreating the controls is part of a bigger function which recreates all kinds of objects in my scene based on user preference (e.g. don’t create earth at all, create it the standard Phong way, or create it using shaders for night lights, and similar systems for the rest of the objects) so it made sense to be consistent and approach controls the same way - some things are just not possible to refactor via toggling a
.enabled
or.visible
setting. I’ll consider taking the easy route where possible though.Yes, I’m aware the animation / on-demand mixing in the code can be done better, and will look to improve the process when the time comes - thanks for pointing that out. Currently, I’m already mixing the (automatic) simulation / animation with on demand actions in practice, since the extra rendering done on demand is not at the same frequency as the animation loop and hardly overkill, not to mention that generally the on demand actions are supposed to be done when the animation is stopped (hence the pause system in the above code).
As a side note, I saw that you corrected some things in the ArcballControls disposing system in r142. You might be interested to know that TransformControls does also leave some leftovers when disposing, mainly the gizmos (named “controls”). Currently I handle that by:
but you might consider implementing it properly by default via one or two lines added to that control. Not serious enough for a bug report here, I just thought that you should know. Cheers, and thanks to all the team for making Three.js available to us users!
After some debugging it’s now clear what causes the issue.
Unlike
OrbitControls
,ArcballControls
allows to move the camera over the poles by modifying the camera’s up vector. When you just dispose the controls, the camera’s up vector is left modified and not resetted to its original value (normally 0,1,0). When you now recreate the controls, the internal state variables are getting mixed up since you have not callcontrols.reset()
.The reset is mandatory since the controls have to know the original up vector of the camera when instantiating. So it’s actually best if you perform a manual call of
reset()
before callingdispose()
. Otherwise you have to modify some internal variables like so: https://jsfiddle.net/umsthaxr/3/In general, try to avoid to dispose/create controls over and over again and just enable/disable controls via the
enabled
property. Besides, never mix an animation loop with on-demand rendering like in your code example. When the animation loop is active, you render more frames than necessary since yourchange
event listener still triggers calls ofrender()
.