
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();