question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ArcballControls - Extra rotation after recreating the controls

See original GitHub issue

Describe 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:

  1. Create a basic Three.js setup
  2. Add ArcballControls to it
  3. Make some function / logic to recreate the controls on demand
  4. Launch the page and rotate camera around
  5. Trigger the controls’ re-creation function / logic
  6. Try to rotate camera around again
  7. 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:closed
  • Created a year ago
  • Comments:7

github_iconTop GitHub Comments

1reaction
Yincognytocommented, Aug 13, 2022

After some debugging it’s now clear what causes the issue…

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 variable cup = new THREE.Vector3().copy(camera.up); when instantiating the camera (see, I can reuse variables too), and added controls._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:

try {
  scene.getObjectByName("controls").traverse(function(o) {
    try {o.geometry.dispose();} catch {};
    try {o.material.dispose();} catch {};
    try {for (var m of o.material) {m.dispose();};} catch {};
  });} catch {};
try {scene.remove(scene.getObjectByName("controls"));} catch {};

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!

0reactions
Mugen87commented, Aug 13, 2022

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 call controls.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 calling dispose(). 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 your change event listener still triggers calls of render().

Read more comments on GitHub >

github_iconTop Results From Across the Web

ArcballControls – three.js docs
Arcball controls allow the camera to be controlled by a virtual trackball with full ... allow FOV manipulation (in a vertigo-style method) and...
Read more >
Basic Trackball Camera Controls with Threejs HTML5 3D ...
http://filmsbykris.comFull Playlist:http://www.youtube.com/playlist?list=PLcUid3OP_4OVX8zp-ZTcyOsp6C9CJCqd0ThreeJS ...
Read more >
arcball-gems.pdf
One special feature of Arcball is its ability to handle with equal ease both free rotation and constrained rotation about any axis. The...
Read more >
How to implement a simple Arcball Camera.
The main idea is to rotate the camera using a pivot point. Here's the algorithm: Calculate the amount of rotation in x and...
Read more >
ArcBall Rotation - NeHe Productions
ArcBall Rotation Control, Revisited By Terence J. Grant (tjgrant@tatewake.com). WouldnÂ't it be great to rotate your model at will, just by using the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found