Three.js OrbitControls locking up

73 Views Asked by At

*Edit: I'm an idiot, I don't think my function has anything to do with it because it appears like double clicking without moving your mouse has this behavior somewhat consistently on its own, on the Three.js example as well.

I still don't know how to remove this functionality or even if this is some sort of feature.


When double-clicking the scene, I am calling a function that smoothly resets the camera position to its original position. However, after doing so, whenever trying to move with OrbitControls causes the not-allowed cursor to appear and the controls to rotate hardly at all before stopping. The only way to move the controls again is by single clicking the canvas and not dragging, this will reset the controls to a functional state.

When the resetPosition function, which moves the camera back to the origin slightly every frame, is removed, the controls work *mostly as intended. I've looked through it and see no obvious cause.

*The same thing happens very inconsistently when double-clicking (usually by dragging the controls quickly on the second click) even without resetPosition, resetPosition just makes it occur consistently. This behavior can even be observed in this official Three.js example.

An image of that, StackOverflow won't let me post images

How do I stop this from happening? I've found plenty of Three.js examples that don't have this issue, but I have no clue what they do differently.

The relevant code is below:

this.controls = new OrbitControls(this.camera, this.element)
this.controls.target.set(CONTROLS_TARGET.x, CONTROLS_TARGET.y, CONTROLS_TARGET.z)
this.controls.userRotateSpeed = 3
this.controls.maxDistance = 200
this.controls.minDistance = 10
resetPosition() {
    let azimuthalAngle = this.controls.getAzimuthalAngle(),
        polarAngle = this.controls.getPolarAngle() - Math.PI / 2,
        distance = this.controls.getDistance() - 30,
        movement = this.controls.target.clone()

    let lerpDelta = LERP_ALPHA ** (1 + this.deltaTime * 60)

    if (Math.abs(azimuthalAngle) < 0.001) azimuthalAngle = 0
    if (Math.abs(polarAngle) < 0.001) polarAngle = 0
    if (Math.abs(distance) < 0.001 && Math.abs(distance) > -0.001) distance = 0
    if (movement.distanceTo(CONTROLS_TARGET) < 0.05) movement = CONTROLS_TARGET

    this.controls.minAzimuthAngle = lerpDelta * azimuthalAngle
    this.controls.maxAzimuthAngle = this.controls.minAzimuthAngle

    this.controls.minPolarAngle = Math.PI/2 + lerpDelta * polarAngle
    this.controls.maxPolarAngle = this.controls.minPolarAngle

    this.controls.minDistance = lerpDelta * distance + 30
    this.controls.maxDistance = this.controls.minDistance

    movement.lerp(CONTROLS_TARGET, 1.0 - lerpDelta)
    this.controls.target.set(movement.x, movement.y, movement.z)

    if(azimuthalAngle === 0
        && polarAngle === 0
        && distance === 0
        && movement.equals(CONTROLS_TARGET)
    ) this.finishReset()
}
finishReset() {
    this.controls.minAzimuthAngle = -Infinity
    this.controls.maxAzimuthAngle = Infinity
    this.controls.minPolarAngle = 0
    this.controls.maxPolarAngle = Math.PI
    this.controls.minDistance = 10
    this.controls.maxDistance = 200
    this.doReset = false
}
animate() {
    if (this.doReset) this.resetPosition()
    this.controls.update()
    
    this.render()
}
1

There are 1 best solutions below

1
On

The issue you are encountering might be related to the way you are handling the resetPosition() function and the continuous updates to the OrbitControls during the animation loop. When you double-click the scene, you trigger the resetPosition() function, which smoothly resets the camera position. During this resetting process, you are modifying the min/max azimuthal and polar angles, as well as the min/max distance of the OrbitControls. This is intended to prevent the camera from moving away during the reset.

The problem arises when you modify these constraints continuously during the animation loop. The resetPosition() function is called on every frame when this.doReset is true, and this can interfere with the smooth operation of the OrbitControls, leading to the undesired behavior you described.

To fix this, you can try the following approach:

  1. Make sure that the resetPosition() function is only called once when you double-click to reset the camera. Set a flag to indicate that the reset process should start and then clear the flag once the reset is complete.

  2. Remove the continuous updating of min/max azimuthal and polar angles, as well as min/max distance, inside the resetPosition() function. Instead, set these constraints only once during the initialization of the OrbitControls.

Here's a modified version of your code:

// Initialization code
this.controls = new OrbitControls(this.camera, this.element);
this.controls.target.set(CONTROLS_TARGET.x, CONTROLS_TARGET.y, CONTROLS_TARGET.z);
this.controls.userRotateSpeed = 3;
this.controls.maxDistance = 200;
this.controls.minDistance = 10;

// Set a flag to trigger the reset process
this.doReset = false;

// Function to handle double-click event
function onDoubleClick() {
  this.doReset = true;
}

// Assuming you have an event listener for double-click on the canvas
this.element.addEventListener('dblclick', onDoubleClick.bind(this));

// Function to reset the camera position
function resetPosition() {
  let azimuthalAngle = this.controls.getAzimuthalAngle(),
    polarAngle = this.controls.getPolarAngle() - Math.PI / 2,
    distance = this.controls.getDistance() - 30,
    movement = this.controls.target.clone();

  let lerpDelta = LERP_ALPHA ** (1 + this.deltaTime * 60);

  if (Math.abs(azimuthalAngle) < 0.001) azimuthalAngle = 0;
  if (Math.abs(polarAngle) < 0.001) polarAngle = 0;
  if (Math.abs(distance) < 0.001 && Math.abs(distance) > -0.001) distance = 0;
  if (movement.distanceTo(CONTROLS_TARGET) < 0.05) movement = CONTROLS_TARGET;

  movement.lerp(CONTROLS_TARGET, 1.0 - lerpDelta);
  this.controls.target.set(movement.x, movement.y, movement.z);

  if (
    azimuthalAngle === 0 &&
    polarAngle === 0 &&
    distance === 0 &&
    movement.equals(CONTROLS_TARGET)
  )
    this.finishReset();
}

function finishReset() {
  this.controls.minAzimuthAngle = -Infinity;
  this.controls.maxAzimuthAngle = Infinity;
  this.controls.minPolarAngle = 0;
  this.controls.maxPolarAngle = Math.PI;
  this.controls.minDistance = 10;
  this.controls.maxDistance = 200;
  this.doReset = false;
}

function animate() {
  if (this.doReset) {
    resetPosition.call(this);
  }
  this.controls.update();
  this.render();
}

// Add the necessary call to the animate function in your main animation loop

By ensuring that the resetPosition() function is called only once during the double-click event and setting the constraints only once during initialization, the OrbitControls should work smoothly without getting stuck in the "not-allowed" cursor state.