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 http://vrock.it 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...
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));
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(mvPosition.xyz) + 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)
-- .
Steps:
- Add
<script src="markupExt.js"></script>
to yourindex.html
- Load extension after
'onSuccess'
event, like so...
function onSuccess() {
viewer.loadExtension("markup3d");
}
- 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++) {
dummyData.push({
icon: Math.round(Math.random()*3),
x: Math.random()*300-150,
y: Math.random()*50-20,
z: Math.random()*150-130
});
}
window.dispatchEvent(
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"
- Add a 'onMarkupClick' Listener
window.addEventListener("onMarkupClick", e=>{
var elem = $("label");
elem.style.display = "block";
moveLabel(e.detail);
elem.innerHTML = `<img src="img/${(e.detail.id%6)}.jpg"><br>Markup ID:${e.detail.id}`;
}, false);
- Add a 'onMarkupMove' Listener
window.addEventListener("onMarkupMove", e=>{
moveLabel(e.detail)
}, false);
function moveLabel(p) {
elem.style.left = ((p.x + 1)/2 * window.innerWidth) + 'px';
elem.style.top = (-(p.y - 1)/2 * window.innerHeight) + 'px';
}
--
.
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"
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.
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
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,
-- .
You can change the line color at the top of the docs/markupExt.js
here:
function markup3d(viewer, options) {
this.lineColor = 0xcccccc;
The Info-Card colors and CSS styling can be found in the 'docs/index.html'
here:
#label {
display:none;
position:fixed;
z-index:2;
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/${(e.detail.id % 6)}.jpg"><br>Markup ID:${e.detail.id}`;
This is where you would add your own customized div with React.js or Vue.js, after you receive the 'onMarkupClick' event
Steps:
- Set up your view state (set pivot, environment, FOV, etc)
- Go to Chrome Browser debug console
- Enter the following:
v=viewer.getState();delete(v.seedURN);delete(v.objectSet);delete(v.renderOptions);delete(v.cutplanes);JSON.stringify(v)
- Copy and paste the resulting JSON, into the
'viewStates'
variable inapp.js
line65
--
.
Use
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.
var options = {
env: "Local",
useConsolidation: true,
useADP: false,
}
This change makes a different on larger scenes, but your mileage may vary.
--
- Hit Test PointClouds: StackOverflow
- Phillip's PointCloud: BLOG
- ConXTech AR Demo: DEMO / BLOG