8 Aug 2018
Create Pushpin Markup by SVG

The IoT sample my colleague Philippe produced is very impressive. One of the features is pushpin markup which represents the sensor in the model. The markup is built by SVG. 2 years ago, Philippe has also created a blog on working with SVG markup in Forge Viewer. While the workflow is a bit complicated for some customers because it includes comprehensive functions which result in a cool demo.
Note: if your application will need to have huge number of pushpin markup, SVG is not a good choice. Point Cloud will be much better. We have some blogs on this:
https://forge.autodesk.com/blog/high-performance-3d-markups-pointcloud-forge-viewer
https://forge.autodesk.com/blog/using-pointcloud-forge-viewer
https://forge.autodesk.com/blog/3d-markup-icons-and-info-card
So, I tried to isolate the core section to make a small sample: when the user clicks on location of the object, a pushpin markup will be created. Although Forge Viewer 6.0 provides Pushpin extension, I think the basic logic of SVG would be also helpful in some scenarios.
Some tricks for sharing:
- Import the svg lib
- The SVG is contained by a svg element, and the svg is wrapped with a div DOM.
- The graphics of SVG is rendered by a JavaScript library svg-snap, by which we can manipulate the graphics dynamically. In my sample, the circle radius is fix.
- Since SVG is a drawn in the screen space in 2D coordination, when the camera of Forge Viewer changes, we need to update the location of the SVG accordingly in the event CAMERA_CHANGE_EVENT. Forge Viewer provides worldToClient method that can convert the 3D model position to 2D screen coordination.
- In CAMERA_CHANGE_EVENT, the original 3D model position will be not available. In my sample, I attach the 3D data to the SVG DOM by DOM.data dictionary.
- To get the selected position of the object, the sample uses viewer.impl.hitTest, which can provides the intersection point and dbid of the object.
//assume _viewer3D is available
var _viewer3D;
function init(){
//delegate the mouse click event
$(_viewer3D.container).bind("click", onMouseClick);
//delegate the event of CAMERA_CHANGE_EVENT
_viewer3D.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, function(rt){
//find out all pushpin markups
var $eles = $("div[id^='mymk']");
var DOMeles = $eles.get();
for(var index in DOMeles){
//get each DOM element
var DOMEle = DOMeles[index];
var divEle = $('#' + DOMEle.id);
//get out the 3D coordination
var val = divEle.data('3DData');
var pushpinModelPt = JSON.parse(val);
//get the updated screen point
var screenpoint = _viewer3D.worldToClient(new THREE.Vector3(
pushpinModelPt.x,
pushpinModelPt.y,
pushpinModelPt.z,));
//update the SVG position.
divEle.css({
'left': screenpoint.x - pushpinModelPt.radius*2,
'top': screenpoint.y - pushpinModelPt.radius
});
}
});
}
function onMouseClick (event) {
var screenPoint = {
x: event.clientX,
y: event.clientY
};
//get the selected 3D position of the object
//viewer canvas might have offset from the webpage.
let viewer_pos = _viewer3D.container.getBoundingClientRect();
var hitTest = _viewer3D.impl.hitTest(screenPoint.x - viewer_pos.x,
screenPoint.y - viewer_pos.y,true);
if(hitTest)
{
drawPushpin({x:hitTest.intersectPoint.x,
y:hitTest.intersectPoint.y,
z:hitTest.intersectPoint.z});
}
}
//generate a random id for each pushpin markup
function makeid() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ )
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
function drawPushpin(pushpinModelPt){
//convert 3D position to 2D screen coordination
var screenpoint = _viewer3D.worldToClient(
new THREE.Vector3(pushpinModelPt.x,
pushpinModelPt.y,
pushpinModelPt.z,));
//build the div container
var randomId = makeid();
var htmlMarker = '<div id="mymk' + randomId + '"></div>';
var parent = _viewer3D.container
$(parent).append(htmlMarker);
$('#mymk'+randomId ).css({
'pointer-events': 'none',
'width': '20px',
'height': '20px',
'position': 'absolute',
'overflow': 'visible'
});
//build the svg element and draw a circle
$('#mymk'+randomId).append('<svg id="mysvg'+randomId+ '"></svg>')
var snap = Snap($('#mysvg'+randomId)[0]);
var rad = 12;
var circle = snap.paper.circle(14, 14, rad);
circle.attr({
fill: "#FF8888",
fillOpacity: 0.6,
stroke: "#FF0000",
strokeWidth: 3
});
//set the position of the SVG
//adjust to make the circle center is the position of the click point
var $container = $('#mymk'+randomId);
$container.css({
'left': screenpoint.x - rad*2,
'top': screenpoint.y - rad
});
//store 3D point data to the DOM
var div = $('#mymk'+randomId);
//add radius info with the 3D data
pushpinModelPt.radius = rad;
var storeData = JSON.stringify(pushpinModelPt);
div.data('3DData', storeData);
}
init();