Point Drag Controls

Update December 2017

PointDragControls.js has now been updated to correct pointer drift in translation mode. The original controller was rotation only, and the translation controller was tacked on for completeness. Later some mouse pointer drift in the translation mode was since identified, which has now been fixed, and the controller has been improved generally. If you are already using PointDragControls.js, make sure you update to the latest version.

Demo Instructions

A simplified summary of the controls is given below – but read further for a more in depth understanding

There are 2 modes, translate and rotate. Double click on empty canvas to change mode.

In translate mode:

  • Left click and drag, or touch and drag, on an object to move it in the plane of the camera.
  • If using a mouse, right click on an object and drag mouse pointer in vertical direction to move it in direction camera is pointing (ie closer/further away)
  • If using a touch device, hold one finger on the empty canvas, then touch object (with a second finger!) and drag in vertical direction to move object in direction camera is pointing.

In rotate mode:

  • Left click and drag, or touch and drag, on an object in a vertical direction to rotate it about a horizontal axis.
  • Left click and drag, or touch and drag, on an object in a horizontal direction to rotate it about a vertical axis.
  • Right click on an object and drag mouse pointer in vertical direction to rotate it around an axis normal to the camera
  • If using a touch device, hold one finger on the empty canvas, then touch object and drag in vertical direction to rotate it around an axis normal to the camera

What’s this?

Point Drag Contols is a controller for Three.js which enables click and drag to translate or rotate individual objects. It was created for a specific application, but is now open source (under the MIT license).

Basically we couldn’t find a drag-to-rotate controller which did what we wanted under three.js, so we wrote one. DragControls.js is a nice controller, but it does not currently support rotation. Most snippets of code that can be found dealing with rotation only allow rotation about the objects’ origin, and without much control over the axis of rotation.

PointDragControls.js is mainly aimed at solving the rotation problem (but the simpler to implement translation controller is also included.) The goal is create something which (a) is intuitive to use (b) is coordinate system independent (ie works the same way whatever direction the camera points, and where-ever the objects are in the scene) (c) fully supports touch and (d) remains compatible with at least one of the camera controllers (Trackball.js, OrbitControls.js).

Presented here is a first version: it is felt that requirements (a), (b) and (c) have been met with a good level success. (d) has only been looked at in the case of OrbitControls.js – and it is clear some modification of OrbitControls.js is necessary to allow the 2 controllers to sit happily side by side. Basically, at a minimum OrbitControls.js needs to be told to ignore clicks when they are over an object, to avoid dragging both the camera and the object at the same time. The second demo (below) includes a first attempt at a modified OrbitControls.js: while this does work, please be advised it is in an early stage of development. Keep reading for more specifics.

More Specifically

The main advantage of this controller is that the initial click or touch event defines the origin about which to rotate the object. Click and drag from the top of the object and the object will rotate about… you guessed it… it’s top!

Rotations in 3D can be tricky, and difficult to visualise. In order to be as intuitive as possible it was decided that the directions of the rotation axes should be locked. (That is, locked by default – of course free rotation can also be specified, but is presumed to be not as desirable.) Furthermore, the direction of the rotation axes should be defined in a “polar” sense with respect to the position of the camera. Please refer to the accompanying diagram. The desirable rotation axes form an orthogonal set: the vector k lies directly along the line of sight from the camera to the object, j is the vector perpendicular to k parallel to “ground” (a plane extending horizontally from the camera) and i is also perpendicular to k but lies in the camera’s “vertical” plane.

Or looking at it another way, if a rope were to connect the origin to the object, k would lie along the rope, i and j would be the directions the object would begin to move if it were swung vertically and horizontally (respectfully).

It is interesting that this coordinate scheme does not seem to be the most intuitive for translations. Thus our controller reverts to a coordinate system with the horizontal, vertical and “look at” directions of the camera as the axes when the mode is switched to translate.

Isotropic Behaviour

To meet the requirement that the rotation axis set consistently appears in the aforementioned desirable orientation, irrespective of the angle of the camera, the position of the object etc. relative to “world” coordinates, a transformation matrix is computed by multiplying individual transformations for each of the necessary corrections. An incredible amount of joy and fun was derived from trying to figure all this out, juggling different coordinate systems, and then trying to determine which minus sign was missing from where so that 2 of the axes came out flip-flopped at the end. Or was there a mistake in the order of the matrix multiplications (since rotation transformations are in general non-commutative) ?

In fact this is so much fun, and not at all confusing, that we are sure you won’t want to download our solution, and instead will prefer to sit down with a hot cup of coffee and a piece of paper (several may be needed) and thrash it out from scratch. No, but really.

Alternatively, feel free to get the source.

Usage Instructions

You need script tags to load three.js and PointDragControls.js:

At minimum you need a camera, a scene and a renderer:

Then pass the camera, scene and renderer to init():

A more complete example is given below:

In the example you may notice the absence of an animate() function. In fact PointDragControls can be used with or without animate(). If your scene is generally static apart from when you are dragging objects around, then why animate? In this case it is simpler to ask the controller to render the scene only when an object is moved/rotated. To do this, set the auto_render option to true. Alternatively, if you are calling animate(), then it’s probably better to omit this option (the default is false).

Options

You can pass additional options to the controller on initialisation:

Available options with default values:

Runtime Functions

Some functions are provided to enable changing of the controller’s behaviour after initialisation. These are listed below:

Access to Globals

It is intended that the state of the controller be changed during runtime via one of the previously mentioned accessor functions. However, we recognise the code is at an early stage of development, and that you may want to do things which we didn’t think of. If this is the case, please do get in touch and tell us your frustrations. Meanwhile, you can access globals via controls.globals. Be careful. Many variables keep track of internal state, and may do odd things if you change them. Here is the list:

Use with OrbitControls

Near the beginning of this page it was mentioned that it would be desirable to have this controller work alongside existing camera position controllers (OrbitControls.js and TrackballControls.js, basically). So far we’ve tried this with OrbitControls.js, and the solution is presented below.

Be advised that at this stage the code changes to OrbitControls.js feel like a bit of a hack, because they may override or fail to take into account the broader flexibility which the original code was written for. They also haven’t had much in the way of testing.

We attempted to make the camera and object functionality similar – the camera now shares the ‘rotate’ and ‘translate’ mode scheme. However with the two sets of controls combined, there ends up being a substantial number of independent ways of interacting with the scene, which is potentially quite confusing. In fact, there may well be too many controls now for it to be intuitive. But see what you think.

To use both controllers, load both scripts as well as three.js. Make sure you use our version of OrbitControls.js.

Don’t forget to give the controllers different names!

Finally, you should pass the array of objects you are using PointDragControls with to a newly introduced property in OrbitControls ignoreObjects:

This tells OrbitControls not to move the camera when you are dragging an object.

Demo with OrbitControls

Encouragingly, this demo does illustrate the coordinate system independence of PointDragControls. Try rotating objects with the camera in different positions – you should find the rotation axes are always aligned with the camera, according to the “polar” system outlined earlier.

An updated version of the control scheme is given below:

There are 2 modes, translate and rotate. Double click on empty canvas to change mode.

In translate mode:

  • Left click and drag, or touch and drag, on an object to move it in the plane of the camera. (from PointDragControls.js)
  • Left click and drag, or touch and drag, on empty canvas to pan the camera (from OrbitControls.js)
  • Right click on an object and drag mouse pointer in vertical direction to move it in direction camera is pointing (ie closer/further away) (from PointDragControls.js)
  • Right click on empty canvas and drag mouse pointer in vertial direction to move whole scene closer/further away (from OrbitControls.js)

In rotate mode:

  • Left click and drag, or touch and drag, on an object in a vertical direction to rotate it about a horizontal axis. (from PointDragControls.js)
  • Left click and drag, or touch and drag, on an object in a horizontal direction to rotate it about a vertical axis. (from PointDragControls.js)
  • Left click and drag, or touch and drag, on empty canvas rotate the camera (from OrbitControls.js)
  • Right click on an object and drag mouse pointer in vertical direction to move it in direction camera is pointing (ie closer/further away) (from PointDragControls.js)
  • Right click on empty canvas and drag mouse pointer in vertial direction to move whole scene closer/further away (from OrbitControls.js)

In either mode:

  • “dolly” (move the camera forward/backward) using the mouse wheel

Feedback

If you found this controller useful or interesting, we’d like to hear from you. If you thought it wasn’t useful at all, we’d still like to hear from you. And if you have unresolved questions… guess what? Get in touch and let us know your thoughts!

We wish you many happy rotations!