Visual LabINTERACTIVE LEARNING
OpenGL 与 3D第 4 / 4 章

输入事件状态机

统一鼠标、触摸、滚轮和键盘事件,并管理控制器状态切换。

OrbitControls相机交互
专题预计 52 分钟查看源文 ↗

input-state

    function onPointerDown(event) {
      if (scope.enabled === false) return

      if (pointers.length === 0) {
        scope.domElement.setPointerCapture(event.pointerId)

        scope.domElement.addEventListener('pointermove', onPointerMove)
        scope.domElement.addEventListener('pointerup', onPointerUp)
      }

      //

      if (isTrackingPointer(event)) return

      //

      addPointer(event)

      if (event.pointerType === 'touch') {
      } else {
        onMouseDown(event)
      }
    }

    function onPointerMove(event) {
      dollyState.changePos = true
      if (scope.enabled === false) return

      if (event.pointerType === 'touch') {
      } else {
        onMouseMove(event)
      }
    }

    function onPointerUp(event) {
      removePointer(event)

      switch (pointers.length) {
        case 0:
          scope.domElement.releasePointerCapture(event.pointerId)

          scope.domElement.removeEventListener('pointermove', onPointerMove)
          scope.domElement.removeEventListener('pointerup', onPointerUp)

          scope.dispatchEvent(_endEvent)

          state = STATE.NONE

          break

        case 1:

          break
      }
    }

    function onMouseDown(event) {
      let mouseAction
      dollyState.changePos = true

      switch (event.button) {
        case 0:
          mouseAction = scope.mouseButtons.LEFT
          break

        case 1:
          mouseAction = scope.mouseButtons.MIDDLE
          break

        case 2:
          mouseAction = scope.mouseButtons.RIGHT
          break

        default:
          mouseAction = -1
      }

      switch (mouseAction) {
        case MOUSE.DOLLY:
          if (scope.enableZoom === false) return

          handleMouseDownDolly(event)

          state = STATE.DOLLY

          break

        case MOUSE.ROTATE:
          if (event.ctrlKey || event.metaKey || event.shiftKey) {
            if (scope.enablePan === false) return

            handleMouseDownPan(event)

            state = STATE.PAN
          } else {
            if (scope.enableRotate === false) return

            handleMouseDownRotate(event)

            state = STATE.ROTATE
          }

          break

        case MOUSE.PAN:
          if (event.ctrlKey || event.metaKey || event.shiftKey) {
            if (scope.enableRotate === false) return

            handleMouseDownRotate(event)

            state = STATE.ROTATE
          } else {
            if (scope.enablePan === false) return

            handleMouseDownPan(event)

            state = STATE.PAN
          }

          break

        default:
          state = STATE.NONE
      }

      if (state !== STATE.NONE) {
        scope.dispatchEvent(_startEvent)
      }
    }

    function onMouseMove(event) {
      switch (state) {
        case STATE.ROTATE:
          if (scope.enableRotate === false) return

          handleMouseMoveRotate(event)

          break

        case STATE.DOLLY:
          if (scope.enableZoom === false) return

          handleMouseMoveDolly(event)

          break

        case STATE.PAN:
          if (scope.enablePan === false) return

          handleMouseMovePan(event)

          break
      }
    }

    function onMouseWheel(event) {
      if (scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE) return

      event.preventDefault()

      scope.dispatchEvent(_startEvent)

      handleMouseWheel(customWheelEvent(event))
      // handleMouseWheel(event)

      scope.dispatchEvent(_endEvent)
    }

    function handleMouseMove(event){
      dollyState.changePos = true
    }

    function customWheelEvent(event) {
      const mode = event.deltaMode

      // minimal wheel event altered to meet delta-zoom demand
      const newEvent = {
        clientX: event.clientX,
        clientY: event.clientY,
        deltaY: event.deltaY,
      }

      // switch (mode) {
      //   case 1: // LINE_MODE
      //     newEvent.deltaY *= 16
      //     break

      //   case 2: // PAGE_MODE
      //     newEvent.deltaY *= 100
      //     break
      // }

      // detect if event was triggered by pinching
      if (event.ctrlKey && !controlActive) {
        newEvent.deltaY *= 10
      }

      return newEvent
    }

    function interceptControlDown(event) {
      if (event.key === 'Control') {
        controlActive = true

        const document = scope.domElement.getRootNode() // offscreen canvas compatibility

        document.addEventListener('keyup', interceptControlUp, { passive: true, capture: true })
      }
    }

    function interceptControlUp(event) {
      if (event.key === 'Control') {
        controlActive = false

        const document = scope.domElement.getRootNode() // offscreen canvas compatibility

        document.removeEventListener('keyup', interceptControlUp, { passive: true, capture: true })
      }
    }

    function onKeyDown(event) {
      if (scope.enabled === false || scope.enablePan === false || event.target.nodeName == 'INPUT') return

      handleKeyDown(event)
    }

    function onContextMenu(event) {
      if (scope.enabled === false) return
      event.preventDefault()
    }

    function addPointer(event) {
      pointers.push(event.pointerId)
    }

    function removePointer(event) {
      delete pointerPositions[event.pointerId]

      for (let i = 0; i < pointers.length; i++) {
        if (pointers[i] == event.pointerId) {
          pointers.splice(i, 1)
          return
        }
      }
    }

    function isTrackingPointer(event) {
      for (let i = 0; i < pointers.length; i++) {
        if (pointers[i] == event.pointerId) return true
      }

      return false
    }

    //

    scope.domElement.addEventListener('contextmenu', onContextMenu)
    scope.domElement.addEventListener('mousemove', handleMouseMove)

    scope.domElement.addEventListener('pointerdown', onPointerDown)
    scope.domElement.addEventListener('pointercancel', onPointerUp)
    scope.domElement.addEventListener('wheel', onMouseWheel, { passive: false })

    const document = scope.domElement.getRootNode() // offscreen canvas compatibility

    document.addEventListener('keydown', interceptControlDown, { passive: true, capture: true })

    // force an update at start
    this.update()
  }
}

export default OrbitControls