自订义线

这篇文章的原著是 Autodesk AND 的 Philippe Leefsma,以下以我简称。

可能有许多原因你想在 Viewer 里加入自订义的线型,例如显示线框(Wireframe)几何、视觉化包围箱(Bounding Box)或者其他你想带给使用者的视觉回馈。基本上这有三种方式来做到这件事,让我们来看看到底要怎么做,首先我们要先产生一个线型几何,其代码入下所示:

const geometry = new THREE.Geometry ()

geometry.vertices.push (new THREE.Vector3 ( 0,  0,  0))
geometry.vertices.push (new THREE.Vector3 (10, 10, 10))

var linesMaterial = new THREE.LineBasicMaterial ({
  color: new THREE.Color (0xFF0000),
  transparent: true,
  depthWrite: false,
  depthTest: true,
  linewidth: 10,
  opacity: 1.0
})

var lines = new THREE.Line (geometry,
  linesMaterial,
  THREE.LinePieces)

下一步我们来看看要怎么将这个线型加入到 Viewer 的场景里(scene):

一、将线型加到 viewer.impl.scene

//1 - 第一种方法
viewer.impl.scene.add (lines)
viewer.impl.invalidate (true)

但这个方法并不是一个靠谱的方法,因为他只能在 FireFox 里正常运作,在我的 Chrome 上是没有作用的。。。所以让我们看看下一个方法。

二、将线型加到 viewer.impl.scene

//2 - 第二种方法
viewer.impl.sceneAfter.add (lines)
viewer.impl.invalidate (true)

这个方法比前一个好多了,他可以在多个浏览器上正常执行,但当你透过修改了构件的可视性后(执行了孤立显示或隐藏等动作),所有的线型都会跟著构件一起消失,我想这是应该是 Viewer 内部的著色器(Shader)和渲染(Rendering)设置造成的。

三、产生 Viewer Overlay 场景,并将线型加入 Overlay 场景:

//3 - 第三种方法
viewer.impl.createOverlayScene (
  'myOverlay', linesMaterial)

viewer.impl.addOverlay (
  'myOverlay', lines)

viewer.impl.invalidate (true)

经测试这个方法非常的靠谱,他可以在多个浏览器上正确执行,并且不受构件可视性的影响。但你不一定要使用第三个方法,你可以根据你的需求选择适合你应用场景的方法。

下面是我传写的一个例子 Viewing.Extension.BoundingBox,他可以在选重构件后在 Viewer 场景里用自定义线型描绘它的包围箱,在线示例可以参考这里

/////////////////////////////////////////////////////////////////
// BoundingBox Viewer Extension
// By Philippe Leefsma, Autodesk Inc, August 2017
//
/////////////////////////////////////////////////////////////////
import MultiModelExtensionBase from 'Viewer.MultiModelExtensionBase'
import Toolkit from 'Viewer.Toolkit'

class BoundingBoxExtension extends MultiModelExtensionBase {

  /////////////////////////////////////////////////////////
  // Class constructor
  //
  /////////////////////////////////////////////////////////
  constructor (viewer, options) {

    super (viewer, options)

    this.onContextMenu = this.onContextMenu.bind(this)

    this.linesMaterial = this.createMaterial(0x0000FF)

    this.lineGroups = []
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  createMaterial (color = 0x000000, opacity = 1.0) {

    return new THREE.LineBasicMaterial({
      color: new THREE.Color(color),
      transparent: true,
      depthWrite: false,
      depthTest: true,
      linewidth: 10,
      opacity
    })
  }

  /////////////////////////////////////////////////////////
  // Load callback
  //
  /////////////////////////////////////////////////////////
  load () {

    this.viewer.loadDynamicExtension(
      'Viewing.Extension.ContextMenu').then(
      (ctxMenuExtension) => {
        ctxMenuExtension.addHandler(
          this.onContextMenu)
      })

    console.log('Viewing.Extension.BoundingBox loaded')

    return true
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  get className() {

    return 'bounding-box'
  }

  /////////////////////////////////////////////////////////
  // Extension Id
  //
  /////////////////////////////////////////////////////////
  static get ExtensionId () {

    return 'Viewing.Extension.BoundingBox'
  }

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

    console.log('Viewing.Extension.BoundingBox unloaded')

    super.unload ()

    return true
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  onModelRootLoaded () {

    this.options.loader.show (false)

    this.viewer.impl.createOverlayScene (
      'boundingBox',
      this.linesMaterial)
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  async onSelection (event) {

    if (event.selections.length) {

      const selection = event.selections[0]

      const model =
        this.viewer.activeModel ||
        this.viewer.model

      this.selectedDbId = selection.dbIdArray[0]

      const bbox =
        await Toolkit.getWorldBoundingBox(
          model, this.selectedDbId)

      this.drawBox(bbox)
    }
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  drawBox (bbox) {

    const geometry = new THREE.Geometry()

    const { min, max } = bbox

    geometry.vertices.push(new THREE.Vector3(min.x, min.y, min.z))
    geometry.vertices.push(new THREE.Vector3(max.x, min.y, min.z))

    geometry.vertices.push(new THREE.Vector3(max.x, min.y, min.z))
    geometry.vertices.push(new THREE.Vector3(max.x, min.y, max.z))

    geometry.vertices.push(new THREE.Vector3(max.x, min.y, max.z))
    geometry.vertices.push(new THREE.Vector3(min.x, min.y, max.z))

    geometry.vertices.push(new THREE.Vector3(min.x, min.y, max.z))
    geometry.vertices.push(new THREE.Vector3(min.x, min.y, min.z))

    geometry.vertices.push(new THREE.Vector3(min.x, max.y, max.z))
    geometry.vertices.push(new THREE.Vector3(max.x, max.y, max.z))

    geometry.vertices.push(new THREE.Vector3(max.x, max.y, max.z))
    geometry.vertices.push(new THREE.Vector3(max.x, max.y, min.z))

    geometry.vertices.push(new THREE.Vector3(max.x, max.y, min.z))
    geometry.vertices.push(new THREE.Vector3(min.x, max.y, min.z))

    geometry.vertices.push(new THREE.Vector3(min.x, max.y, min.z))
    geometry.vertices.push(new THREE.Vector3(min.x, max.y, max.z))

    geometry.vertices.push(new THREE.Vector3(min.x, min.y, min.z))
    geometry.vertices.push(new THREE.Vector3(min.x, max.y, min.z))

    geometry.vertices.push(new THREE.Vector3(max.x, min.y, min.z))
    geometry.vertices.push(new THREE.Vector3(max.x, max.y, min.z))

    geometry.vertices.push(new THREE.Vector3(max.x, min.y, max.z))
    geometry.vertices.push(new THREE.Vector3(max.x, max.y, max.z))

    geometry.vertices.push(new THREE.Vector3(min.x, min.y, max.z))
    geometry.vertices.push(new THREE.Vector3(min.x, max.y, max.z))

    const lines = new THREE.Line(geometry,
      this.linesMaterial,
      THREE.LinePieces)

    this.lineGroups.push(lines)

    this.viewer.impl.addOverlay('boundingBox', lines)

    this.viewer.impl.invalidate(
      true, true, true)
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  onContextMenu (event) {

    const model =
      this.viewer.activeModel ||
      this.viewer.model

    event.menu.forEach((entry) => {

      const title = entry.title.toLowerCase()

      switch (title) {

        case 'isolate':
          entry.target = () => {
            Toolkit.isolateFull(
              this.viewer, this.selectedDbId, model)
          }
          break

        case 'hide selected':
          entry.target = () => {
            Toolkit.hide(
              this.viewer, this.selectedDbId, model)
          }
          break

        case 'show all objects':
          entry.target = () => {
            Toolkit.isolateFull(
              this.viewer, [], model)
            this.viewer.fitToView()
          }
          break

        default: break
      }
    })

    const instanceTree = model.getData().instanceTree

    const dbId = event.dbId || (instanceTree
        ? instanceTree.getRootId()
        : -1)

    if (dbId > -1) {

      event.menu.push({
        title: 'Clear All BoundingBoxes',
        target: () => {
          this.lineGroups.forEach((lines) => {

            this.viewer.impl.removeOverlay('boundingBox', lines)
          })

          this.viewer.impl.invalidate(
            true, true, true)

          this.lineGroups = []
        }
      })
    }
  }
}

Autodesk.Viewing.theExtensionManager.registerExtension (
  BoundingBoxExtension.ExtensionId,
  BoundingBoxExtension)

export default 'Viewing.Extension.BoundingBox'

康益昇
748 声望103 粉丝