Autodesk Forge is now Autodesk Platform Services

5 Dec 2017

Handling custom meshes selection along model components in the Forge Viewer

Default blog image

    Here is a question that came up from one of our customer when adding custom meshes to the Viewer scene: How to handle selection of those extra meshes and how to detect when one of those mesh is occluded by a component loaded as part of the Forge model?

    My suggestion was to use a double raytracing approach: the custom meshes need to be checked against a ray using directly Three.js API rayCaster.intersectObjects, this will return the custom meshes being potentially selected, then using the viewer.model.rayIntersect method, perform a second raytracing to detect the components of the Forge model being selected. Each method also returns the distance between the origin of the ray and the intersection. By comparing those distances, you can determine which of the custom mesh or the component is being intersected first.

    Below is a complete demo extension I created in order to illustrate that approach: it creates some random box meshes around the model (you may have to adapt the size range and position to fit your test model) and listens to click events in order to perform raytracing. The custom meshes being selected are dumped to the console.

/////////////////////////////////////////////////////////////////////
// MeshSelectionExtension, written by Philippe Leefsma - Dec 2017
//
// Illustrates how to perform double ray casting to detect selection
// of custom meshes added to the scene and handle occlusion with
// Viewer meshes from loaded model.
//
/////////////////////////////////////////////////////////////////////
class MeshSelectionExtension extends Autodesk.Viewing.Extension {
/////////////////////////////////////////////////////////
// Class constructor
//
/////////////////////////////////////////////////////////
constructor (viewer, options) {
super (viewer, options)
this.viewer = viewer
}
/////////////////////////////////////////////////////////
// Load callback
//
/////////////////////////////////////////////////////////
load () {
console.log('Viewing.Extension.MeshSelection loaded')
this.viewer.addEventListener(
Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, () => {
this.dbIds = this.getAllDbIds()
})
this.viewer.toolController.registerTool(this)
this.viewer.toolController.activateTool(
'Viewing.Extension.MeshSelection')
this.intersectMeshes = [
this.addMesh(),
this.addMesh(),
this.addMesh(),
this.addMesh(),
this.addMesh()
]
return true
}
/////////////////////////////////////////////////////////
// Tool Interface
//
/////////////////////////////////////////////////////////
getNames () {
return ['Viewing.Extension.MeshSelection']
}
activate () {
}
deactivate () {
}
/////////////////////////////////////////////////////////
// Unload callback
//
/////////////////////////////////////////////////////////
unload () {
console.log('Viewing.Extension.MeshSelection unloaded')
this.viewer.toolController.deactivateTool(
'Viewing.Extension.MeshSelection')
this.viewer.toolController.unregisterTool(this)
return true
}
/////////////////////////////////////////////////////////
// Adds a box mesh with random size and position
// to the scene
//
/////////////////////////////////////////////////////////
addMesh () {
const geometry = new THREE.BoxGeometry(
Math.random() * 10 + 5.0,
Math.random() * 10 + 5.0,
Math.random() * 10 + 5.0)
const color = Math.floor(Math.random() * 16777215)
const material = this.createColorMaterial(color)
const mesh = new THREE.Mesh(geometry, material)
mesh.position.x = -50 + Math.random() * 100
mesh.position.y = -50 + Math.random() * 100
mesh.position.z = -50 + Math.random() * 100
this.viewer.impl.scene.add(mesh)
this.viewer.impl.sceneUpdated(true)
return mesh
}
/////////////////////////////////////////////////////////
// Creates color material from int
//
/////////////////////////////////////////////////////////
createColorMaterial (color) {
const material = new THREE.MeshPhongMaterial({
specular: new THREE.Color(color),
side: THREE.DoubleSide,
reflectivity: 0.0,
color
})
const materials = this.viewer.impl.getMaterials()
materials.addMaterial(
color.toString(16),
material,
true)
return material
}
/////////////////////////////////////////////////////////
// Creates Raycaster object from the pointer
//
/////////////////////////////////////////////////////////
pointerToRaycaster (domElement, camera, pointer) {
const pointerVector = new THREE.Vector3()
const pointerDir = new THREE.Vector3()
const ray = new THREE.Raycaster()
const rect = domElement.getBoundingClientRect()
const x = ((pointer.clientX - rect.left) / rect.width) * 2 - 1
const y = -((pointer.clientY - rect.top) / rect.height) * 2 + 1
if (camera.isPerspective) {
pointerVector.set(x, y, 0.5)
pointerVector.unproject(camera)
ray.set(camera.position,
pointerVector.sub(
camera.position).normalize())
} else {
pointerVector.set(x, y, -1)
pointerVector.unproject(camera)
pointerDir.set(0, 0, -1)
ray.set(pointerVector,
pointerDir.transformDirection(
camera.matrixWorld))
}
return ray
}
/////////////////////////////////////////////////////////
// Click handler
//
/////////////////////////////////////////////////////////
handleSingleClick (event) {
const pointer = event.pointers
? event.pointers[0]
: event
const rayCaster = this.pointerToRaycaster(
this.viewer.impl.canvas,
this.viewer.impl.camera,
pointer)
const intersectResults = rayCaster.intersectObjects(
this.intersectMeshes, true)
const hitTest = this.viewer.model.rayIntersect(
rayCaster, true, this.dbIds)
const selections = intersectResults.filter((res) =>
(!hitTest || (hitTest.distance > res.distance))
)
if (selections.length) {
console.log('Custom meshes selected:')
console.log(selections)
return true
}
return false
}
/////////////////////////////////////////////////////////
// Get list of all dbIds in the model
//
/////////////////////////////////////////////////////////
getAllDbIds() {
const {instanceTree} = this.viewer.model.getData()
const {dbIdToIndex} = instanceTree.nodeAccess
return Object.keys(dbIdToIndex).map((dbId) => {
return parseInt(dbId)
})
}
}
Autodesk.Viewing.theExtensionManager.registerExtension(
'Viewing.Extension.MeshSelection',
MeshSelectionExtension)

Here is a recording of that code in action:

Related Article