Is there a way to conditionally lock Z-axis rotation for ArcballControls while limiting Y-axis one to make it behave as OrbitControls?

I have been using OrbitControls to rotate the camera around the object, and it has worked well. However, I now need to dynamically enable and disable rotation around the Z-axis using mouse events or programmatically. After spending some time playing with camera.up adjustments, I realized that achieving this with OrbitControls is quite challenging. As a result, I started looking into ArcballControls since it provides Z-axis rotation out of the box. However, I encountered the opposite issue: I am now struggling to disable the mentioned rotation. I attempted to conditionally disable the camera.up update as shown below:

      //update camera up vector
      if (
        (this._state == STATE.ROTATE ||
        this._state == STATE.ZROTATE ||
        this._state == STATE.ANIMATION_ROTATE) && 
       !this._disableZAxis
      ) {
        this.camera.up
          .copy(this._upState)
          .applyQuaternion(this.camera.quaternion);
      }

However, this approach resulted in a weird behavior when using diagonal mouse-move rotation. OrbitControls handle this situation better by limiting vertical mouse rotation to 180 degrees. I would like to replicate this behavior in ArcballControls when Z-axis rotation is disabled, however, I am still quite new in this area and I am not sure how to do it. Should I adjust rotationAxis calculation or it is purely rotationMatrix manipulation? How can it be done?
Thank you in advance!

If this helps, the LookAt command automatically cancels out any Z-rotation. So your camera is always rotated level with the horizon (like OrbitControls). So, if there is a way to add that command to your code, you should be set.

Thanks. But the problem is not with Z-axis locking itself since disabling camera.up updates solves this problem, but it is more with Y-axis limiting. It seems that the ArcballControls use Math.tan to calculate camera position and when we cross Y-axis during rotation it starts rotating the camera as well and this is what I am trying to prevent.

Did, you ever find a solution to the issue? Iā€™d be interested to know.

I am seeking something similar also to be able to add keyboard modifiers to conditionally lock the rotation axis. ie press z now only rotations around the Z axis of the camera.

I think I know how to solve it now, but I deprioritized this task, so never got to solving it.

ArcballControls has function called: calculateRotationAxis to calculate the vector to rotate around, then it uses function rotate to calculate transformation matrix and then applyTransformMatrix to actually perform the transformation. If I understand it correctly, to achieve the desired effect, we need to change the logic of calculateRotationAxis. In my case I decided to use quaternions instead of rotationAxis and ended up with some kind of a mixed approach. Also, I decided to keep the full 3D rotation for now.

Hello, I found a solution that I am using with a slider currently but you might be able to modify it. Hope it helps.

function rollCamera(axis, radians, controls) {
	//check the axis and set the vector
	let vector = new Vector3(0, 0, 1);
	if (axis === "Z") {
		vector = new Vector3(0, 0, 1);
	} else if (axis === "Y") {
		vector = new Vector3(0, 1, 0);
	} else if (axis === "X") {
		vector = new Vector3(1, 0, 0);
	} else {
		vector = new Vector3(0, 0, 1);
	}
	if (controls instanceof ArcballControls) {
		// Get the vector from the camera to the target (controls.target)
		const direction = new THREE.Vector3().subVectors(camera.position, controls.target).normalize();

		// Create a quaternion representing the rotation around the Z axis
		const quaternion = new THREE.Quaternion();
		quaternion.setFromAxisAngle(vector, radians);

		// Rotate the direction vector
		direction.applyQuaternion(quaternion);

		// Calculate the new position of the camera
		const distance = camera.position.distanceTo(controls.target);
		const newPosition = new THREE.Vector3().addVectors(controls.target, direction.multiplyScalar(distance));

		// Apply the new position and up vector to the camera
		camera.position.copy(newPosition);
		camera.up.applyQuaternion(quaternion);

		// Look at the target
		camera.lookAt(controls.target);

		// Update the camera's matrix and the controls
		//camera.updateMatrixWorld();
		controls.update();
	} else if (controls instanceof TrackballControls) {
		//store the current camera position
		const position = controls.object.position.clone();
		// Store the current look at target
		const target = controls.target.clone();

		// Get the camera's local Z-axis (up direction)
		const cameraMatrix = new THREE.Matrix4();
		cameraMatrix.lookAt(position, target, controls.object.up);
		const cameraLocalZAxis = vector.applyMatrix4(cameraMatrix);

		// Rotate the camera around its local Z-axis (up direction)
		controls.object.up.applyAxisAngle(cameraLocalZAxis, radians);

		// Update the controls
		controls.update();
	}
}
1 Like