8 Aug 2018

Create Pushpin Markup by SVG

Default blog image

pushpin gif

 

pushpin gif

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

    not

    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:

    1. Import the svg lib
    2. The SVG is contained by a svg element, and the svg is wrapped with a div DOM.
    3. 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.
    4. 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.
    5. 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.
    6. 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();

     

    Related Article