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). Neither OrbitControls or TransformControls fully support the actual needed functionality needed for object spinning:
So with that in mind, could we add support to TransformControls for:
Here is my attempt at a potential solution by using OrbitControls as a reference:
index.html
control = new TransformControls(camera, renderer.domElement);
control.enableDamping = true;
control.freeMode = true;
control.setMode('rotate');
control.showX = false;
control.showY = false;
control.showZ = false;
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 && (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:
None
I have tried implementing damping and inertia to transform controls and it is possible but not trivial.
Additional complexity will be introduced if we want to maintain compatibility with threejs editor and other viewports that render scene only when something changed by listening to the 'changed' event. In other words we need an animation update loop that considers the inertia and continuously sends 'change' event but only until the movement gets completely damped out. I believe this feature is very important because without it we can drain user's batteries rapidly. This can also be implemented with some sort of dirty flag and external animation loop.
That being considered, I would actually advise against implementing inertia/damping in transform controls. It adds too much complexity and it is useful only in some very particular cases. It is better to create a completely new control class for spinning objects with inertia.
If inertia gets implemented anyway (together with change notification mechanism), I would suggest to implement it in a more abstract 'ObjectControl` class and have other controls inherit this behavior.
In this case, it makes probably more sense to keep TransformControls as it is and start experimenting with ObjectControls instead.
The controls implementation of Autodesk Forge viewer is probably a good benchmark:
https://forge-rcdb.autodesk.io/configurator?id=5904729b0007f5ead5b1196d
That being considered, I would actually advise against implementing inertia/damping in transform controls. It adds too much complexity and it is useful only in some very particular cases. It is better to create a completely new control class for spinning objects with inertia.
@kmturley Feel free to create a new feature request for ObjectControls.
Sure, have created a feature request for ObjectControls here:
https://github.com/mrdoob/three.js/issues/18815
Most helpful comment
I have tried implementing damping and inertia to transform controls and it is possible but not trivial.
Additional complexity will be introduced if we want to maintain compatibility with threejs editor and other viewports that render scene only when something changed by listening to the 'changed' event. In other words we need an animation update loop that considers the inertia and continuously sends 'change' event but only until the movement gets completely damped out. I believe this feature is very important because without it we can drain user's batteries rapidly. This can also be implemented with some sort of
dirtyflag and external animation loop.That being considered, I would actually advise against implementing inertia/damping in transform controls. It adds too much complexity and it is useful only in some very particular cases. It is better to create a completely new control class for spinning objects with inertia.
If inertia gets implemented anyway (together with change notification mechanism), I would suggest to implement it in a more abstract 'ObjectControl` class and have other controls inherit this behavior.