Feature request: ObjectControls
See original GitHub issueFeature request
OrbitControls were originally created to rotate a camera around a point in a scene: https://threejs.org/examples/#misc_controls_orbit
TransformControls were originally created for rotating individual objects within a scene: https://threejs.org/examples/#misc_controls_transform
However in most examples and in web apps, we seem to be using OrbitControls to rotate objects? e.g. https://threejs.org/examples/#webgl_materials_normalmap_object_space https://threejs.org/examples/#webgl_clipping_intersection https://threejs.org/examples/#webgl_clipping_stencil https://threejs.org/examples/#webgl_decals etc…
This usage creates confusion when you actually do want to rotate an object (and control the camera separately). In my case I needed the user to spin a globe, while the camera pans to a certain viewing angle.
Neither OrbitControls or TransformControls fully support the actual needed functionality needed for object spinning:
- OrbitControls locks your camera.lookAt to controls.target, and is spinning the camera not the object, which also spins the scenes.
- TransformControls correctly spins the object, but doesn’t support damping / free spinning / hiding the gizmo gui.
So with that in mind, could we add an example for ObjectControls?
The controls implementation of Autodesk Forge viewer is probably a good benchmark: https://forge-rcdb.autodesk.io/configurator?id=5904729b0007f5ead5b1196d
Some ideal features:
- Selecting an object
- Rotate object
- Lock axis
- Free spin in any direction
- Damping to slowly spin to a stop
Here is my attempt at a potential solution by using TransformControls as a reference:
index.html
control = new ObjectControls(camera, renderer.domElement);
control.enableDamping = true;
control.freeMode = true;
control.setMode('rotate');
control.showX = false;
control.showY = false;
control.showZ = false;
ObjectControls.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 && (Math.abs(rotateDelta.x) > 0.01 || Math.abs(rotateDelta.y) > 0.01)) {
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 = .1 / 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";
}
Live example:
Three.js version
- Dev
- r112
Browser
- All of them
- Chrome
- Firefox
- Internet Explorer
OS
- All of them
- Windows
- macOS
- Linux
- Android
- iOS
Hardware Requirements (graphics card, VR Device, …)
None
Issue Analytics
- State:
- Created 4 years ago
- Reactions:3
- Comments:8 (1 by maintainers)
Top GitHub Comments
For all users who are looking for a solution, there is now SpinControls.
Since this project is already maintained at GitHub, it’s not required to add the controls to the
three.js
repository. Please post feature requests or bugs at the above repo.This would be awesome to see as I agree this is a pretty common set of use cases imho…