Markup Extension with multi-icons (see Blog post)
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.

3D Markup with Multi-icons and Info-Card

Try the DEMO: Click Here

(click the image to see video)


This is an extension to Forge Viewer, so you can attach 3D markers to your scene with a pop-out info-card. I originally used the same extension for the AR ConXTech demo (see and original blog post).
I needed to show 1000's of RFI's and Issues in a large Revit scene, so I needed a markup technique to render a large number of points.

Following on from Philippe's great post, I added a couple of things...

> Multi-Icons:

To use multi-icons, I used a spritesheet and added this to the pointcloud fragment shader:

gl_FragColor = gl_FragColor * texture2D(tex, vec2((gl_PointCoord.x+vColor.y*1.0)/4.0, 1.0-gl_PointCoord.y));
> Point Scaling:

I also needed to scale the points so the points looked like they stuck to the object as I zoom in and out. I added this line of code to the vertex shader:

gl_PointSize = size * ( size / (length( + 1.0) );
> Mobile Performance Test :

...and finally, to get great performance on iPad and Android, I needed to avoid using too many div elements. Now I just use one. You can see the performance in the video below

Here are 10,000 RFI's, etc running at 60 FPS...

(click the image to see video)

-- .

How to use:


  1. Add <script src="markupExt.js"></script> to your index.html
  2. Load extension after 'onSuccess' event, like so...
    function onSuccess() {
  1. Send your markup data via an event 'newData', like this...
// create 20 markup points at random x,y,z positions. 

var dummyData = [];
for (let i=0; i<20; i++) {
        icon:  Math.round(Math.random()*3),  
        x: Math.random()*300-150, 
        y: Math.random()*50-20, 
        z: Math.random()*150-130
	new CustomEvent('newData', {'detail': dummyData})

Note: icon: integer corresponds to an icon in the spritesheet (see options below). For example 0="Issue", 1="BIMIQ_Warning", 2="RFI", 3="BIMIQ_Hazard"

  1. Add a 'onMarkupClick' Listener
window.addEventListener("onMarkupClick", e=>{
    var elem = $("label"); = "block";
    elem.innerHTML = `<img src="img/${(}.jpg"><br>Markup ID:${}`;
}, false);
  1. Add a 'onMarkupMove' Listener
window.addEventListener("onMarkupMove", e=>{
}, false);

function moveLabel(p) { = ((p.x + 1)/2 * window.innerWidth) + 'px'; =  (-(p.y - 1)/2 * window.innerHeight) + 'px';            



2. Features and Options

> Icons / SpriteSheet

Here are the current icons I use:

Change the docs/img/icons.png file to your own icon set.

Note: The icon value corresponds to spritesheet position. So icon #0="Issue", #1="BIMIQ_Warning", #2="RFI", #3="BIMIQ_Hazard"

> Positioning your Info-Card

You can reposition the popup Info-Card offset using the settings at the top of the 'docs/markupExt.js' file

this.labelOffset = new THREE.Vector3(120,120,0);  // label offset 3D line offset position
this.xDivOffset = -0.2;  // x offset position of the div label wrt 3D line.
this.yDivOffset = 0.4;  // y offset position of the div label wrt 3D line.
> Adjusting the marker's 'Hit Radius' and 'Icon Size'
function markup3d(viewer, options) {
    this.raycaster.params.PointCloud.threshold = 5; // hit-test markup size.  Change this if markup 'hover' doesn't work
    this.size = 150.0; // markup size.  Change this if markup size is too big or small
> Make Points appear 'in Front' with Transparency

If you want the markup points to always appear on top of objects, change the depthWrite from true to false. Also change the impl.scene to impl.sceneAfter. Finally, to make the points transparent, add opacity: 0.4 to the material shader.

    this.scene = viewer.impl.scene; 
// change this to viewer.impl.sceneAfter with transparency

    this.initMesh_PointCloud = function() {
            var material = new THREE.ShaderMaterial({
                depthWrite: true,
                depthTest: true,

-- .

Info-Card details

> Line Color styling:

You can change the line color at the top of the docs/markupExt.js here:

function markup3d(viewer, options) {
   this.lineColor = 0xcccccc;
> Info-Card Styling

The Info-Card colors and CSS styling can be found in the 'docs/index.html' here:

        #label { 
            border: 2px solid #ccc; 
            background-color: #404040; 
            border-radius: 25px; 
            padding: 10px;
        #label img { width:200px; }

The info-card pictures can be found in the folder 'docs/img/0..5.jpg'. Click on an info card will pick one of the jpg's below (based on the MarkupID):

The HTML string/template is generated by the main 'docs/app.js'.

elem.innerHTML = `
 <img src="img/${( % 6)}.jpg"><br>Markup ID:${}`;

This is where you would add your own customized div with React.js or Vue.js, after you receive the 'onMarkupClick' event

> Creating your own Camera Views


  1. Set up your view state (set pivot, environment, FOV, etc)
  2. Go to Chrome Browser debug console
  3. Enter the following:
  1. Copy and paste the resulting JSON, into the 'viewStates' variable in app.js line65



Render Performance Tips:

> Reduce Pixel Density


window.devicePixelRatio = 1;

Use a reduced pixel density, to get better render performance for a trade-off in pixelation. Noticable on retina screens like mobile and OSX laptops. See the docs/app.js file for details.

> Use Mesh Consolidation
    var options = {
        env: "Local",
        useConsolidation: true,
        useADP: false,

This change makes a different on larger scenes, but your mileage may vary.