Unfortunately, it is impossible to create SurfaceShadingNode
without passing dbIds
due to its implementation, but we can use Scene Builder to add custom geometries with the dbId
support.
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM 'AS IS' AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////
(function () {
/**
* Helper of converting THREE.Box3 to THREE.Mesh
* @class
*/
class BoxMeshHelper extends THREE.Mesh {
constructor(box) {
const geometry = new THREE.BufferGeometry();
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(36 * positionNumComponents), positionNumComponents));
geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(36 * positionNumComponents), normalNumComponents));
geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(36 * uvNumComponents), uvNumComponents));
super(geometry, new THREE.MeshPhongMaterial({ color: 0xffff00, side: THREE.DoubleSide, opacity: 0.4, transparent: true }));
this.type = 'BoxMeshHelper';
this.box = box;
this.positionNumComponents = 3;
this.normalNumComponents = 3;
this.uvNumComponents = 2;
this.update();
}
update() {
const box = this.box;
if (box.isEmpty()) return;
const min = box.min;
const max = box.max;
/*
5____4
1/___0/|
| 6__|_7
2/___3/
0: max.x, max.y, max.z
1: min.x, max.y, max.z
2: min.x, min.y, max.z
3: max.x, min.y, max.z
4: max.x, max.y, min.z
5: min.x, max.y, min.z
6: min.x, min.y, min.z
7: max.x, min.y, min.z
*/
const vertices = [
// front
{ pos: [min.x, min.y, max.z], norm: [0, 0, 1], uv: [0, 1], },
{ pos: [max.x, min.y, max.z], norm: [0, 0, 1], uv: [1, 1], },
{ pos: [min.x, max.y, max.z], norm: [0, 0, 1], uv: [0, 0], },
{ pos: [min.x, max.y, max.z], norm: [0, 0, 1], uv: [0, 0], },
{ pos: [max.x, min.y, max.z], norm: [0, 0, 1], uv: [1, 1], },
{ pos: [max.x, max.y, max.z], norm: [0, 0, 1], uv: [1, 0], },
// right
{ pos: [max.x, min.y, max.z], norm: [1, 0, 0], uv: [0, 1], },
{ pos: [max.x, min.y, min.z], norm: [1, 0, 0], uv: [1, 1], },
{ pos: [max.x, max.y, max.z], norm: [1, 0, 0], uv: [0, 0], },
{ pos: [max.x, max.y, max.z], norm: [1, 0, 0], uv: [0, 0], },
{ pos: [max.x, min.y, min.z], norm: [1, 0, 0], uv: [1, 1], },
{ pos: [max.x, max.y, min.z], norm: [1, 0, 0], uv: [1, 0], },
// back
{ pos: [max.x, min.y, min.z], norm: [0, 0, -1], uv: [0, 1], },
{ pos: [min.x, min.y, min.z], norm: [0, 0, -1], uv: [1, 1], },
{ pos: [max.x, max.y, min.z], norm: [0, 0, -1], uv: [0, 0], },
{ pos: [max.x, max.y, min.z], norm: [0, 0, -1], uv: [0, 0], },
{ pos: [min.x, min.y, min.z], norm: [0, 0, -1], uv: [1, 1], },
{ pos: [min.x, max.y, min.z], norm: [0, 0, -1], uv: [1, 0], },
// left
{ pos: [min.x, min.y, min.z], norm: [-1, 0, 0], uv: [0, 1], },
{ pos: [min.x, min.y, max.z], norm: [-1, 0, 0], uv: [1, 1], },
{ pos: [min.x, max.y, min.z], norm: [-1, 0, 0], uv: [0, 0], },
{ pos: [min.x, max.y, min.z], norm: [-1, 0, 0], uv: [0, 0], },
{ pos: [min.x, min.y, max.z], norm: [-1, 0, 0], uv: [1, 1], },
{ pos: [min.x, max.y, max.z], norm: [-1, 0, 0], uv: [1, 0], },
// top
{ pos: [max.x, max.y, min.z], norm: [0, 1, 0], uv: [0, 1], },
{ pos: [min.x, max.y, min.z], norm: [0, 1, 0], uv: [1, 1], },
{ pos: [max.x, max.y, max.z], norm: [0, 1, 0], uv: [0, 0], },
{ pos: [max.x, max.y, max.z], norm: [0, 1, 0], uv: [0, 0], },
{ pos: [min.x, max.y, min.z], norm: [0, 1, 0], uv: [1, 1], },
{ pos: [min.x, max.y, max.z], norm: [0, 1, 0], uv: [1, 0], },
// bottom
{ pos: [max.x, min.y, max.z], norm: [0, -1, 0], uv: [0, 1], },
{ pos: [min.x, min.y, max.z], norm: [0, -1, 0], uv: [1, 1], },
{ pos: [max.x, min.y, min.z], norm: [0, -1, 0], uv: [0, 0], },
{ pos: [max.x, min.y, min.z], norm: [0, -1, 0], uv: [0, 0], },
{ pos: [min.x, min.y, max.z], norm: [0, -1, 0], uv: [1, 1], },
{ pos: [min.x, min.y, min.z], norm: [0, -1, 0], uv: [1, 0], },
];
const positions = [];
const normals = [];
const uvs = [];
for (const vertex of vertices) {
positions.push(...vertex.pos);
normals.push(...vertex.norm);
uvs.push(...vertex.uv);
}
this.geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(positions), this.positionNumComponents);
this.geometry.attributes.normal = new THREE.BufferAttribute(new Float32Array(normals), this.normalNumComponents);
this.geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array(uvs), this.uvNumComponents);
this.geometry.attributes.position.needsUpdate = true;
this.geometry.attributes.normal.needsUpdate = true;
this.geometry.attributes.uv.needsUpdate = true;
this.geometry.computeBoundingSphere();
}
}
class DataVizCustomBoundsExt extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this.dbIdPrefix = 1000;
}
get dataVizExt() {
return this.viewer.getExtension('Autodesk.DataVisualization');
}
get sceneBuilderExt() {
return this.viewer.getExtension('Autodesk.Viewing.SceneBuilder');
}
async renderBounds(bounds) {
if (!(bounds instanceof THREE.Box3)) return;
let mesh = new BoxMeshHelper(bounds);
let boundMesh = new THREE.Mesh(
mesh.geometry.clone(),
this.boundsMaterial
);
boundMesh.dbId = ++this.dbIdPrefix;
this.modelBuilder.addMesh(
boundMesh
);
const shadingGroup = new Autodesk.DataVisualization.Core.SurfaceShadingGroup('Bounds');
const boundNode = new Autodesk.DataVisualization.Core.SurfaceShadingNode(`Bound ${boundMesh.dbId}`, boundMesh.dbId);
boundNode.addPoint(
new Autodesk.DataVisualization.Core.SurfaceShadingPoint('Bound sensor', bounds.center(), ['Temperature'])
);
shadingGroup.addChild(boundNode);
const heatmapData = new Autodesk.DataVisualization.Core.SurfaceShadingData();
heatmapData.addChild(shadingGroup);
// Initialize with model loaded from forge
let model = this.modelBuilder.model;
heatmapData.initialize(model);
await this.dataVizExt.setupSurfaceShading(model, heatmapData);
this.dataVizExt.renderSurfaceShading('Bounds', 'Temperature', this.getSensorValue);
}
getSensorValue() {
return Math.random();
}
async load() {
await Promise.all([
this.viewer.loadExtension('Autodesk.Viewing.SceneBuilder'),
this.viewer.loadExtension('Autodesk.DataVisualization'),
]);
await this.viewer.waitForLoadDone();
this.modelBuilder = await this.sceneBuilderExt.addNewModel({
conserveMemory: false,
modelNameOverride: 'Custom Bounds',
loadAsHidden: true
});
const matName = 'bounds-mat';
const boundsMat = new THREE.MeshPhongMaterial({ color: 0xffff00, side: THREE.DoubleSide, opacity: 0.4, transparent: true });
this.modelBuilder.addMaterial(matName, boundsMat);
this.boundsMaterial = this.modelBuilder.findMaterial(matName);
return true;
}
unload() {
this.dataVizExt.removeSurfaceShading(this.modelBuilder.model);
this.viewer.unloadModel(this.modelBuilder.model);
return true;
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('Autodesk.ADN.DataVizCustomBoundsExt', DataVizCustomBoundsExt);
})();
const dataVizCustomBoundsExt = await viewer.loadExtension('Autodesk.ADN.DataVizCustomBoundsExt');
// Custom bounds
let bounds = new THREE.Box3(new THREE.Vector3(-87.49999999999999, -123.7500076293945, -29.434711456298828), new THREE.Vector3(0, -0.0000018146972706745137, 0));
dataVizCustomBoundsExt.renderBounds(bounds);
Demo: https://youtu.be/O_itfTAvfrQ?t=11
