Skip to content

Instantly share code, notes, and snippets.

@leefsmp
Created November 13, 2015 13:43
Show Gist options
  • Save leefsmp/8138a2df3c7a774cfdbf to your computer and use it in GitHub Desktop.
Save leefsmp/8138a2df3c7a774cfdbf to your computer and use it in GitHub Desktop.
Radial menu viewer extension
/////////////////////////////////////////////////////////////////////
// Autodesk.ADN.Viewing.Extension.RadialMenu
// by Philippe Leefsma, November 2015
//
/////////////////////////////////////////////////////////////////////
AutodeskNamespace("Autodesk.ADN.Viewing.Extension");
Autodesk.ADN.Viewing.Extension.RadialMenu = function (viewer, options) {
Autodesk.Viewing.Extension.call(this, viewer, options);
var _$menu = null;
var _dbIdArray = null;
var _worldPoint = null;
var _container = viewer.container;
/////////////////////////////////////////////////////////////////
// Extension load callback
//
/////////////////////////////////////////////////////////////////
this.load = function () {
_$menu = createRadialMenu(
[
{
text: 'Item 1',
class: 'fa fa-play',
onClick: function(label) {
alert('Item 1!');
}
},
{
text: 'Item 2',
class: 'fa fa-stop',
onClick: function(label) {
alert('Item 2!');
}
},
{
text: 'Item 3',
class: 'fa fa-pause',
onClick: function(label) {
alert('Item 3!');
}
},
{
text: 'Item 4',
class: 'fa fa-random',
onClick: function(label) {
alert('Item 4!');
}
},
{
text: 'Item 5',
class: 'fa fa-backward',
onClick: function(label) {
alert('Item 5!');
}
},
{
text: 'Item 6',
class: 'fa fa-forward',
onClick: function(label) {
alert('Item 6!');
}
},
],
_container,
'img/favicon.png');
viewer.addEventListener(
Autodesk.Viewing.SELECTION_CHANGED_EVENT,
onItemSelected);
console.log('Autodesk.ADN.Viewing.Extension.RadialMenu loaded');
return true;
}
/////////////////////////////////////////////////////////////////
// Extension unload callback
//
/////////////////////////////////////////////////////////////////
this.unload = function () {
_$menu.remove();
viewer.removeEventListener(
Autodesk.Viewing.SELECTION_CHANGED_EVENT,
onItemSelected);
console.log('Autodesk.ADN.Viewing.Extension.RadialMenu unloaded');
return true;
}
/////////////////////////////////////////////////////////////////
// mouse click handler: stores screenpoint coordinates
//
/////////////////////////////////////////////////////////////////
function onMouseClick(event) {
$(_container).unbind(
"click",
onMouseClick);
if(_dbIdArray.length) {
var screenPoint = {
x: event.clientX,
y: event.clientY
};
var n = normalize(screenPoint);
_worldPoint = viewer.utilities.getHitPoint(
n.x, n.y);
var offset = getClientOffset(
_container);
_$menu.css({
'left': screenPoint.x - offset.x - 32 * 0.5,
'top': screenPoint.y - offset.y - 32 * 0.5,
'display':'block'
});
viewer.addEventListener(
Autodesk.Viewing.CAMERA_CHANGE_EVENT,
onCameraChanged);
}
else {
_$menu.hideMenu();
viewer.removeEventListener(
Autodesk.Viewing.CAMERA_CHANGE_EVENT,
onCameraChanged);
}
}
/////////////////////////////////////////////////////////////////
// item selected callback
//
/////////////////////////////////////////////////////////////////
function onItemSelected(event) {
_dbIdArray = event.dbIdArray;
$(_container).bind(
"click",
onMouseClick);
}
/////////////////////////////////////////////////////////////////
// camera change callback
//
/////////////////////////////////////////////////////////////////
function onCameraChanged(event) {
var screenPoint = worldToScreen(
_worldPoint,
viewer.getCamera());
_$menu.css({
'left': screenPoint.x - 32 * 0.5,
'top': screenPoint.y - 32 * 0.5
});
}
///////////////////////////////////////////////////////////////////////////
// normalize screen coordinates
//
///////////////////////////////////////////////////////////////////////////
function normalize(screenPoint) {
var viewport =
viewer.navigation.getScreenViewport();
var n = {
x: (screenPoint.x - viewport.left) / viewport.width,
y: (screenPoint.y - viewport.top) / viewport.height
};
return n;
}
///////////////////////////////////////////////////////////////////////////
// world -> screen coords conversion
//
///////////////////////////////////////////////////////////////////////////
function worldToScreen(worldPoint, camera) {
var p = new THREE.Vector4();
p.x = worldPoint.x;
p.y = worldPoint.y;
p.z = worldPoint.z;
p.w = 1;
p.applyMatrix4(camera.matrixWorldInverse);
p.applyMatrix4(camera.projectionMatrix);
// Don't want to mirror values with negative z (behind camera)
// if camera is inside the bounding box,
// better to throw markers to the screen sides.
if (p.w > 0)
{
p.x /= p.w;
p.y /= p.w;
p.z /= p.w;
}
// This one is multiplying by width/2 and –height/2,
// and offsetting by canvas location
var point = viewer.impl.viewportToClient(p.x, p.y);
// snap to the center of the pixel
point.x = Math.floor(point.x) + 0.5;
point.y = Math.floor(point.y) + 0.5;
return point;
}
/////////////////////////////////////////////////////////////////
// client offset
//
/////////////////////////////////////////////////////////////////
function getClientOffset(element) {
var x = 0;
var y = 0;
while (element) {
x += element.offsetLeft -
element.scrollLeft +
element.clientLeft;
y += element.offsetTop -
element.scrollTop +
element.clientTop;
element = element.offsetParent;
}
return { x: x, y: y };
}
/////////////////////////////////////////////////////////////////
// Generates random guid to use as DOM id
//
/////////////////////////////////////////////////////////////////
function guid() {
var d = new Date().getTime();
var guid = 'xxxx-xxxx-xxxx-xxxx'.replace(
/[xy]/g,
function (c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
});
return guid;
}
/////////////////////////////////////////////////////////////////
// Creates dom elements and animation logic for radial menu
//
/////////////////////////////////////////////////////////////////
function createRadialMenu(menuItems, parent, imgSrc) {
var $parent = $(parent);
var containerId = guid();
var selectorId = guid();
var triggerId = guid();
var itemsId = guid();
var html = [
'<div class="menu-container" id="' + containerId + '">',
'<div class="menu-selector" id="' + selectorId + '" style="display:none;">',
'<ul id="' + itemsId + '">',
'</ul>',
'<button id="' + triggerId + '">' +
'<img width="32" height="32" src="' + imgSrc + '"/>',
'</button>',
'</div>',
'</div>'
];
$parent.append(html.join('\n'));
var $selector = $('#' + selectorId);
function rotate (li, d) {
$({ d: -360 }).animate (
{
d: d
},
{
step: function (now) {
$(li)
.css ({ transform: 'rotate(' + now + 'deg)' })
.find ('label')
.css ({ transform: 'rotate(' + (-now) + 'deg)' }) ;
},
duration: 10
}) ;
}
function animateItems() {
var li = $selector.find('li');
var deg = $selector.hasClass ('half') ? 180 / (li.length - 1) : 360 / li.length;
for ( var i =0 ; i < li.length ; i++ ) {
var d = $selector.hasClass('half') ? (i * deg) - 90 : i * deg;
$selector.hasClass('open') ? rotate (li[i], d) : rotate (li[i], -360) ;
}
}
var _isOpen = false;
function hideMenu() {
$selector.removeClass('open');
animateItems();
setTimeout(function() {
$selector.css({'display': 'none'});
}, (_isOpen ? 800 : 0));
_isOpen = false;
}
$('#' + triggerId).click(function(event) {
$selector.toggleClass('open');
animateItems();
_isOpen = !_isOpen;
});
var $container= $('#' + containerId);
$container.css({
'background-color':'transparent',
'height': $parent.outerHeight(),
'width': $parent.outerWidth(),
'pointer-events':'none',
'position':'absolute',
'left': '0px',
'top': '0px'
});
var $items = $('#' + itemsId);
menuItems.forEach(function(menuItem) {
var itemId = guid();
var itemHtml = [
'<li id="' + itemId + '">',
'<input type="checkbox">',
'<label class="' + menuItem.class + '" id="' + itemId + '"> ' +
menuItem.text +
'</label>',
'</li>'
];
$items.append(itemHtml.join('\n'));
$('#' + itemId).click(function(){
menuItem.onClick(this);
});
});
$selector.hideMenu = hideMenu;
return $selector;
}
/////////////////////////////////////////////////////////////
// Add needed CSS
//
/////////////////////////////////////////////////////////////
var css = `
.menu-selector {
position: absolute;
width: 140px;
height: 140px;
}
.menu-selector,
.menu-selector button {
font-family: 'Oswald', sans-serif;
font-weight: 300;
}
.menu-selector button {
position: relative;
width: 100%;
height: 100%;
padding: 10px;
background: #428bca;
border-radius: 50%;
border: 0;
color: white;
font-size: 20px;
cursor: pointer;
transition: all .1s;
pointer-events: auto;
}
.menu-selector button:hover {
background: #3071a9;
}
.menu-selector button:focus {
outline: none;
}
.menu-selector ul {
position: absolute;
list-style: none;
padding: 0;
margin: 0;
top: -20px;
right: -20px;
bottom: -20px;
left: -20px;
pointer-events: none;
}
.menu-selector li {
position: absolute;
width: 100%;
height: 100%;
margin: 0 50%;
-webkit-transform: rotate(-360deg);
transition: all 0.8s ease-in-out;
}
.menu-selector li input {
display: none;
}
.menu-selector li input + label {
position: absolute;
left: 50%;
bottom: 100%;
width: 0;
height: 0;
line-height: 1px;
margin-left: 0;
background: #fff;
border-radius: 50%;
text-align: center;
font-size: 1px;
overflow: hidden;
cursor: pointer;
box-shadow: none;
transition: all 0.8s ease-in-out, color 0.1s, background 0.1s;
pointer-events: auto;
}
.menu-selector li input + label {
background: #86d2ff;
}
.menu-selector li input + label:hover {
background: #A6DA7F;
}
.menu-selector li input:checked + label {
background: #5cb85c;
color: white;
}
.menu-selector li input:checked + label:hover {
background: #449d44;
}
.menu-selector.open li input + label {
width: 80px;
height: 80px;
line-height: 80px;
margin-left: -40px;
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.1);
font-size: 14px;
}
/* For Viewer */
.menu-container .menu-selector {
width: 32px;
height: 32px;
z-index: 5;
}
.menu-container .menu-selector ul {
top: -80px;
right: -80px;
bottom: -80px;
left: -80px;
pointer-events: none ;
}
.menu-container .menu-selector li {
margin: 0 auto;
}
.menu-container .menu-selector button {
width: 32px;
height: 32px;
background: transparent;
padding: 0px;
}`;
$('<style type="text/css">' + css + '</style>').appendTo('head');
};
Autodesk.ADN.Viewing.Extension.RadialMenu.prototype =
Object.create(Autodesk.Viewing.Extension.prototype);
Autodesk.ADN.Viewing.Extension.RadialMenu.prototype.constructor =
Autodesk.ADN.Viewing.Extension.RadialMenu;
Autodesk.Viewing.theExtensionManager.registerExtension(
'Autodesk.ADN.Viewing.Extension.RadialMenu',
Autodesk.ADN.Viewing.Extension.RadialMenu);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment