I am building a panoramic viewer that combines a 3D model and panorama images. The viewer features hotspots within the 3D model, and when a user clicks a hotspot, the view switches from the 3D model to a 360-degree panorama view at the hotspot’s position. For reference, imagine standing on a tower and rotating in place to look around in 360 degrees.
Here’s the workflow: The user clicks a hotspot. The camera moves to the hotspot's position in the 3D model. The view switches to a spherical panorama view where the user can freely rotate the camera in 360 degrees.
I currently have a working solution for a Y-UP model, but it breaks when the model is Z-UP. I want to make the solution generic so it works with any model orientation (Y-UP, Z-UP, or others
Problem: To achieve spherical movement, I use the following logic in my code (simplified):
Get the current camera direction.
Calculate the point the camera is looking at.
Set that point as the controls' target for the spherical view.
Here’s the code for switching to the spherical view:
function switchToSphericalView() {
const direction = new THREE.Vector3();
camera.getWorldDirection(direction);
const lookAtPoint = camera.position.clone().add(direction);
controls.enableZoom = false;
controls.pan = false;
controls.target = lookAtPoint;
controls.update();
return;
}
This works perfectly for a Y-UP model, as seen in this live demo: Live Demo: Y-UP Model
However, when I load a Z-UP model, the spherical view does not work correctly. To address this, I calculate an outward vector from the center of the model to orient the camera correctly. Despite this attempt, the spherical view still doesn’t behave the same as with a Y-UP model.
Here’s the live demo for the Z-UP model: Live Demo: Z-UP Model
Key Steps to Reproduce: Load the model and hotspot (preloaded in both demos). Press M to move the camera to the hotspot. Press S to switch to spherical view. Rotate the camera to observe the issue (Z-UP model behaves incorrectly).
Question:
How can I modify my camera orientation logic to make the spherical view work generically for models with any orientation (Y-UP, Z-UP, or others)?
I suspect the issue is related to how the lookAt or target is being calculated relative to the model’s local axis. If you need further clarification or explanation, feel free to ask. Any suggestions, best practices, or alternative approaches would be highly appreciated!
I read source code of OrbitControls
(source v0.170.0) to find how to do it and found an answer:
OrbitControls
already suport any camera.up
vector and will work correctly with it, but only if camera.up
vector was changed before you call OrbitControls
constructor like:
const camera = new THREE.PerspectiveCamera(...);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer(...);
camera.up.set(0,0,1)
const controls = new OrbitControls(camera, renderer.domElement);
this will change behaviour for OrbitControls
to rotate arround vector (0, 0, 1)
but it don't support (just don't have method to do this) changing camera.up
after OrbitControls
was created so you need to update controls._quat
manually and here I add method to this class to do it:
OrbitControls.prototype.upVectorChanged = function() {
this._quat = new THREE.Quaternion().setFromUnitVectors( this.object.up, new THREE.Vector3(0,1,0) );
this._quatInverse = this._quat.clone().invert();
}
now you can call .upVectorChanged()
for any instance of OrbitControls like:
camera.up.set(0,0,1)
controls.upVectorChanged()
controls.update()
and adding this code in the end of your live demo for the Z-UP model works