OrbitControls allows us to quickly implement a rotating camera around a scene. But many people actually use it for rotating an object. There are use cases where you don't always want your camera viewport to look directly at the controls rotation target.
Consider the SketchFab object viewer for example:
https://sketchfab.com/3d-models/the-whole-world-712c2de7426f4eb59aa101d21cd72492
When you rotate an object with the camera zoomed out, all functions well:

You can zoom and pan to focus the camera on the poles using Shift-Mouse-drag:

But now when you continue to rotate around the globe, you suddenly start seeing inside the object and strange angles (because the rotation target is not the center of the globe anymore):

1) Allow camera.lookAt to be offset from controls.target
Now I understand it's intended functionality for OrbitControls to keep the camera looking at the target as it's primarily for orbiting a scene not an object, but wondering if it's possible to support more customization of the controls target vs camera lookAt functionality? currently when using OrbitControls the camera.lookAt function is effectively disabled.
2) Create ObjectControls for exploring/panning individual objects
Or if you would consider a new ObjectControls template which rotates the object smoothly instead of the camera? allowing for movement of the camera independently from the object rotation? since many people are using OrbitControls to imitate rotating an object rather than a scene anyway? There is an example someone has created here:
http://benchung.com/smooth-mouse-rotation-three-js/
Your default jsfiddle actually shows this problem very well (Try panning using Shift-Mouse-drag):
https://jsfiddle.net/hyok6tvj/
Other people have asked for a solution to this use case here:
https://stackoverflow.com/questions/34526500/threejs-orbitcontrol-with-different-rotate-center-and-lookat-point
None
Allow camera.lookAt to be offset from controls.target
To keep OrbitControls simple, if would let the user implement this enhancement on app level. Right now, the implementation of OrbitControls is clear and comprehensible. TBH, performing this decoupling makes the controls only confusing.
Create ObjectControls for exploring/panning individual objects
Have you considered to use TransformControls for this?
@kmturley Do you mean something like this target/pivot OrbitControls Example ?
The main difference in this version is that I've decoupled pivot, which is the point the camera rotates around, and target, which is the point the camera looks at.
In this specific case, I've modified the pan command to move the target position, but the pivot stays centered at the object.
I agree with @Mugen87, that perhaps this doesn't fit in the default OrbitControls, but feel free to use this version if it meets your expectations. I just don't guarantee it is entirely functional, as I haven't fully tested it.
/js/controls/OrbitControls - non-module
/jsm/controls/OrbitControls.js - module
@Mugen87 I tried using Transform controls, but couldn't get a good working version. Although I found out I could fake the effect need by moving the sphere to the scene 0,0,0, it doesn't stop the Y axis rotation issue where the camera goes inside an object, I could lock the Y axis to prevent this.
https://jsfiddle.net/kmturley/ht1n3o4z/16/
@sciecode That's exactly the functionality needed. I created a demo here showing the camera pointing at the north pole of a globe (but the camera rotates around the globe normally):
https://jsfiddle.net/kmturley/6qxh3zc9/24/
I exposed the pan() function so pan could also be set programmatically:
this.pan = function (deltaX, deltaY) {
pan(deltaX, deltaY);
}
Then setting the pan after the controls init using:
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set(0, radius, radius * 2);
// controls
orbit = new OrbitControls( camera, renderer.domElement );
orbit.enableDamping = true;
orbit.pan(0, 200);
Ideally it would inherit the camera.lookAt setting like this:
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set(0, radius, radius * 2);
camera.lookAt(0, radius, 0);
// controls
orbit = new OrbitControls( camera, renderer.domElement );
orbit.enableDamping = true;
Is that possible?
I exposed the pan() function so pan could also be set programmatically
No need for it. You may set controls.target directly after initialization.
orbit = new OrbitControls( camera, renderer.domElement );
orbit.target.set( 0, 200, 0 );
Is that possible?
As we said previously, it's unlikely this feature will get implemented in the default OrbitControls, let's see how others feel about it.
But if we choose to do it, my implementation definitely won't make to the final PR, as I modified some core mechanics to account for your use-case. However, given that enough support is given for adding the feature, I'll gladly adapt it to work correctly with our current implementation.
@sciecode after testing side-by-side your version doesn't exactly function how I expected. The horizontal orbit is correct, but the vertical rotation is off:
If you rotate your version vertically the globe still pivots around the control target:

https://jsfiddle.net/kmturley/6qxh3zc9/29/
In my manual object spinning implementation the globe pivots around it's center:

https://jsfiddle.net/kmturley/6317jd95/11/
I created a third version using TransformControls:
Which functions correctly, but requires the controls to be visible and does not support damping

https://jsfiddle.net/kmturley/ht1n3o4z/27/
This is the previous functionality:

When you moved the pan/target you get this:

Your version does this (which is better):

But when rotating the vertical rotation it looks like this:

What i'm looking for is this:

I guess this could be accomplished by moving the target as the object is rotated
I managed to replicate the functionality (another way) by modifying the TransformControls library to include:
TransformControls.js line 69
// Set to true to enable damping (inertia)
// If damping is enabled, you must call controls.update() in your animation loop
defineProperty( "enableDamping", false );
defineProperty( "dampingFactor", 0.05 );
defineProperty( "freeMode", false );
defineProperty( "rotateSpeed", 1.0 );
line 128:
var rotateStart = new Vector2();
var rotateEnd = new Vector2();
var rotateDelta = new Vector2();
line 255:
this.pointerHover = function ( pointer ) {
if ( this.object === undefined || this.dragging === true || ( pointer.button !== undefined && pointer.button !== 0 ) ) return;
if (this.freeMode === true) {
this.axis = 'XYZE';
return;
}
ray.setFromCamera( pointer, this.camera );
var intersect = ray.intersectObjects( _gizmo.picker[ this.mode ].children, true )[ 0 ] || false;
if ( intersect ) {
this.axis = intersect.object.name;
} else {
this.axis = null;
}
};
line 523:
var ROTATION_SPEED = 2 / worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) );
line 724:
this.update = function () {
if (this.dragging === false && scope.enableDamping ) {
quaternionStart.copy( this.object.quaternion );
offset = new Vector3(
rotateDelta.x * 5,
- rotateDelta.y * 5,
0
);
rotateDelta.x *= 1 - scope.dampingFactor;
rotateDelta.y *= 1 - scope.dampingFactor;
var ROTATION_SPEED = .5 / worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) );
rotationAxis.copy( offset ).cross( eye ).normalize();
rotationAngle = offset.dot( _tempVector.copy( rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED;
rotationAxis.applyQuaternion( parentQuaternionInv );
this.object.quaternion.copy( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) );
this.object.quaternion.multiply( quaternionStart ).normalize();
}
};
line 644:
function onPointerDown( event ) {
if ( ! scope.enabled ) return;
rotateStart.set( event.clientX, event.clientY );
document.addEventListener( "mousemove", onPointerMove, false );
scope.pointerHover( getPointer( event ) );
scope.pointerDown( getPointer( event ) );
}
function onPointerMove( event ) {
if ( ! scope.enabled ) return;
rotateEnd.set( event.clientX, event.clientY );
rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
scope.pointerMove( getPointer( event ) );
rotateStart.copy( rotateEnd );
}
line 1178:
if (this.freeMode === true) {
this.gizmo[ "translate" ].visible = false;
this.gizmo[ "rotate" ].visible = false;
this.gizmo[ "scale" ].visible = false;
this.helper[ "translate" ].visible = false;
this.helper[ "rotate" ].visible = false;
this.helper[ "scale" ].visible = false;
} else {
this.gizmo[ "translate" ].visible = this.mode === "translate";
this.gizmo[ "rotate" ].visible = this.mode === "rotate";
this.gizmo[ "scale" ].visible = this.mode === "scale";
this.helper[ "translate" ].visible = this.mode === "translate";
this.helper[ "rotate" ].visible = this.mode === "rotate";
this.helper[ "scale" ].visible = this.mode === "scale";
}
Demo here:
https://jsfiddle.net/kmturley/2p1v6bcw/18/
Your suggested workaround using TransformControls works, Thankyou!
I will close this feature request and open a new feature request for TransformControls containing:
Most helpful comment
@kmturley Do you mean something like this target/pivot OrbitControls Example ?
The main difference in this version is that I've decoupled
pivot, which is the point the camera rotates around, andtarget, which is the point the camera looks at.In this specific case, I've modified the pan command to move the
targetposition, but the pivot stays centered at the object.I agree with @Mugen87, that perhaps this doesn't fit in the default
OrbitControls, but feel free to use this version if it meets your expectations. I just don't guarantee it is entirely functional, as I haven't fully tested it./js/controls/OrbitControls - non-module
/jsm/controls/OrbitControls.js - module