Home

radial3

After conducting some usability tests with Gzweb Mobile, we found a lot of bugs, desired features and possibilities for enhancement. Among these, one thing we noticed was that users tried to double tap or long press a model in the hope to reach some feature, such as translating, rotating or deleting the object. Thus the idea of an object menu came up. On Gazebo, the object’s drop-down menu can be reached by a right click of the mouse. For the mobile version however, a radial (or pie) menu might be more suitable for touch screens in small devices.

Doing a quick search for radial menus, I found some interesting implementations in pure CSS, like this or this. However, trying to mix that with jQueryMobile (what we use for the interface) and Three.js (what we use for the 3D graphics) proved to be more complicated than expected. Or maybe I was doing something wrong, who knows…

Then I had the idea of implementing one myself in Three.js. I couldn’t find one already implemented, I wonder if anyone has tried this before… But yeah, after looking at the CSS examples for a while, I felt like doing it within Three.js using sprites should be relatively easy.

Remember that I’m not a very experienced coder yet, so things might not be very elegant, efficient or scalable, but it seems to be enough for our purposes right now 😉 If you have any ideas on how to improve it I’d be glad to hear them 🙂

The radial menu

The menu is a class called GZ3D.RadialMenu contained in the file gzradialmenu.js. There can be only one menu open at a time.

I’m implementing it for touch, but it can certainly be adapted for mouse. Long-pressing a model shows the menu with an animation, which is just the items moving from the clicked position until the final radius around this position. While holding the press, the user selects the desired item and then ends the touch.

radial4

To select an item, the user does not need to keep the finger exactly on top of the item, it works if it’s in the direction of that item (the item’s region), at any distance from the starting point. This way, the user’s finger does not cover the item.

While the user is still holding the touch, the selected item is highlighted, which means its color and size change. Check out the complete flow and then I’ll explain more details.

radial2

The long press is implemented with the GUI as follows. Container is the canvas where the Three.js scene is. Checks for multi-touch and whether touchend is ending a long or short touch are done where the events are caught.

var press_time = 700;
$('#container')
  .on('touchstart', function (event) {
    var $this = $(this);
    $(this).data('checkdown', setTimeout(function () {
      guiEvents.emit('longpress_start',event);
    }, press_time));
  })
  .on('touchend', function (event) {
    clearTimeout($(this).data('checkdown'));
    guiEvents.emit('longpress_end',event);
  })
  .on('touchmove', function (event) {
    clearTimeout($(this).data('checkdown'));
    guiEvents.emit('longpress_move',event);
  });

Initialize

The menu is initialized at startup as a member of our GZ3D.Scene and added to the Three.js scene. The Three.js object holding the menu itself is radialMenu‘s member menu.

this.radialMenu = new GZ3D.RadialMenu(this.getDomElement());
this.scene.add( this.radialMenu.menu );

Currently the menu initializes with the following variables. All of these can be changed from the scene by this.radialMenu.<variable>.

Variable Description Value
radius distance from center of items to clicked point 70px
speed how much the items move per frame 10px
iconSize height and width of item icons when not selected 40px
iconSizeSelected height and width of item icons when selected 70px
plainColor color blended to icon when not selected FFFFFF (white)
selectedColor color blended to icon when selected F78E1E (Gazebo orange)

I’m also adding menu items at initialization, using addItem(type,itemTexture) as follows. For now, there are 3 items: delete, translate and rotate.

this.addItem('delete','style/images/trash_icon.png');
this.addItem('translate','style/images/translate_icon.png');
this.addItem('rotate','style/images/rotate_icon.png');

Items are added in order from left to right and there can be up to 8 items on the menu, spaced 45° as follows:

radial1

Hide

Hiding the menu consists of making all the items invisible (but they’re still in the scene). When an item is selected, hiding also executes a callback function, which looks like this:

this.scene.radialMenu.hide(event,function(type,entity)
   {
     if (type === 'delete')
     {
       /* delete entity */
     }
     else if (type === 'translate')
     {
       /* translate entity */
     }
     else if (type === 'rotate')
     {
       /* rotate entity */
     }
   });

Show

Showing the menu makes all its items visible and starts the animation.

Show takes 2 arguments: the touchstart event which triggered it, so the start point can be extracted, and the model to which the menu will be attached:

this.scene.radialMenu.show(event,model);

Update

Update continues the animation until the items reach the final position. It updates the position of all items according to the chosen speed. It should be called right before render so the items move at each frame.

Drag the long press

When dragging the long press, the onLongPressMove method is called. It finds out in which of the 8 regions the touch is, and if there’s an item there, the item is highlighted and selected. The selected item’s callback function is only executed if the touch ends while it is selected.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s