Sam Curry
Sam Curry

Reputation: 475

How can I pick up text mesh with the RayCaster in Three.js?

So, I have run into an issue using RayCaster to pick up TextGeometry. I am using the function createText() below to render this text onto the canvas. This works fine, it shows up, and it even adds it to the array of meshes for the RayCaster it check for intersection, however, the function HandleSingleClick() is not firing on TextGeometries for some reason.

I have tested using cubes, and it does work. The intention is that if you click on the TextGeometry, it changes color to denote that you clicked on it. However, for some reason, the following script doesn't work when it comes to Text. I can confirm that the meshes are being added to the array the RayCaster checks in, but it doesn't pick them up for some reason.

import {
  Font,
  TextGeometry
} from '../js/libs/Three.es.js';
import FontJson from '../fonts/helvetiker_bold.typeface.json';

export default class TextExtension extends Autodesk.Viewing.Extension {

  constructor(viewer, options) {

      super()

      this.viewer = viewer
  }

  load() {
      console.log('Viewing.Extension.MeshSelection loaded')

      this.viewer.addEventListener(
          Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, () => {

              this.dbIds = this.getAllDbIds()
              console.log(this.dbIds);

          });
      this.viewer.toolController.registerTool(this)

      this.viewer.toolController.activateTool(
          'MeshSelection')

      this.intersectMeshes = [];
      return true
  }


  /////////////////////////////////////////////////////////
  // Tool Interface
  //
  /////////////////////////////////////////////////////////
  getNames() {

      return ['MeshSelection']
  }

  activate() {

  }

  deactivate() {

  }

  /////////////////////////////////////////////////////////
  // Unload callback
  //
  /////////////////////////////////////////////////////////
  unload() {

      console.log('MeshSelection unloaded')

      this.viewer.toolController.deactivateTool(
          '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() * 25
      mesh.position.y = -50 + Math.random() * 25
      mesh.position.z = 1 + Math.random() * 1

      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) {
      console.log(this.intersectMeshes);

      const pointer = event.pointers ?
          event.pointers[0] :
          event
      console.log(pointer);
      const rayCaster = this.pointerToRaycaster(
          this.viewer.impl.canvas,
          this.viewer.impl.camera,
          pointer)

      const intersectResults = rayCaster.intersectObjects(
          this.intersectMeshes, true)
      console.log(intersectResults);

      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)
          selections[0].object.material.color = this.createColorMaterial(Math.floor(Math.random() * 16777215));
          viewer.impl.sceneUpdated(true);
          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)
      })
  }

  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(),
          material,
          true)

      return material
  }


  /////////////////////////////////////////////////////////
  // Wraps TextGeometry object and adds a new mesh to  
  // the scene
  /////////////////////////////////////////////////////////
  createText(params, index) {
      const geometry = new TextGeometry(params.text,
          Object.assign({}, {
              font: new Font(FontJson),
              params
          }))
      geometry.computeBoundingBox();

      const material = this.createColorMaterial(
          params.color)

      const text = new THREE.Mesh(
          geometry, material)

      text.scale.set(params.scale, params.scale, params.scale);

      text.position.set(
          params.position.x,
          params.position.y,
          10)


      this.intersectMeshes[index] = text;
      this.viewer.impl.scene.add(text);

      this.viewer.impl.sceneUpdated(true)
  }
}

Autodesk.Viewing.theExtensionManager.registerExtension(
  'TextSpawner', TextExtension);

Upvotes: 2

Views: 557

Answers (1)

Felipe
Felipe

Reputation: 4375

You need to debug the ray casting logic to find out what is going on. In the Three.js version that the viewer is using there is a chech to see if the geometry object from the tested mesh is of instance THREE.BufferGeometry or THREE.Geometry, if none the intersection logic is not computed.

In order to make it compatible with it while adding as little code as possible, I used the following approach:

  /////////////////////////////////////////////////////////
  // Wraps TextGeometry object and adds a new mesh to
  // the scene
  /////////////////////////////////////////////////////////
  createText (params) {

    const textGeometry = new TextGeometry(params.text,
      Object.assign({}, {
        font: new Font(FontJson),
        params
      }))

    // use a geometry recognized by the viewer 
    // THREE.js version
    const geometry = new THREE.BufferGeometry

    geometry.fromGeometry(textGeometry)

    const material = this.createColorMaterial(
      params.color)

    const text = new THREE.Mesh(
      geometry , material)

    text.position.set(
      params.position.x,
      params.position.y,
      params.position.z)

    this.viewer.impl.scene.add(text)

    this.viewer.impl.sceneUpdated(true)

    return text
  }

Upvotes: 1

Related Questions