TransformControls enable Damping / hide gui / free spin

Feature request

OrbitControls were originally created to rotate a camera around a point in a scene:
Screen Shot 2020-01-28 at 3 32 16 PM
https://threejs.org/examples/#misc_controls_orbit

TransformControls were originally created for rotating individual objects within a scene:
Screen Shot 2020-01-28 at 3 32 26 PM
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:

  • 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 support to TransformControls for:

  • Damping – slowly spin to a stop
  • Free spin – spin in any direction without needing to hover on the gizmo
  • Hide gizmo gui – hide all gizmo ui elements (currently setting showX, showY, showZ does not hide the white helper lines)

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:

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

Author: Fantashit

1 thought on “TransformControls enable Damping / hide gui / free spin

  1. 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.

Comments are closed.