基于 Three.js 的图形操纵控件

一、背景

Three.js 是一个前端三维图形展示库。它自带了一个三维图形控件,OrbitControls.js,可以控制三维图形的平移、缩放、旋转。

OrbitControls 是通过控制”照相机“(也就是三维形体的观察者)的位置,以实现对三维形体的平移、缩放,和旋转的。

也就是说:

  • 通过平移照相机的位置,实现三维形体在显示屏幕上的平移效果
  • 通过调整照相机的远近,实现三维形体在显示屏幕上的缩放效果
  • 通过旋转照相机的镜头角度和朝向,实现三维形体在显示屏幕上的旋转

但在实际使用中,这样带来一个问题:

  • 在平移了屏幕上的形体之后,相当于把”照相机“平移到了形体侧方
  • 这样,照相机镜头的指向,即照相机的正视线,不再对准形体中心的,而是被平移到了形体旁边的位置
  • 这时,再进行旋转操作,就是把照相机对准形体旁边的位置旋转,造成形体在屏幕上旋转的轨迹非常混乱

为了解决这个问题,我们需要自己开发一套控件,来实现形体平移之后,仍能围绕形体中心进行旋转的效果。

二、方案

基本思路如下:

  1. 借鉴 OrbitControls 的事件处理框架,接收鼠标操作和触摸屏上的手势,判断用户是要进行旋转、平移,还是缩放。
  2. 然后通过操作形体,而不是操作照相机的方式,实现旋转、平移、缩放的效果。

2.1 事件处理框架

借用 OrbitControls 的事件处理框架:

  • 监听 pointerdown 事件,收到该事件后
    • 把该 pointer (也就是鼠标的某一个键,或者触摸屏上的某一个手指)加入活动 pointer 列表 
    • 进行 touchstart 处理(触摸屏)
      • 如果只有一个活动的 pointer (单指) - 旋转
        • handleTouchStartRotate() – 记录当前单指的坐标
      • 如果有两个活动的 pointer(双指)- 平移 + 缩放 - handleTouchStartDollyPan()
        • handleTouchStartDolly() - 记录当前两指之间的距离
        • handleTouchStartPan() - 记录当前两指中间的坐标
    • 进行 mousedown 处理(鼠标)
    • 开始监听 pointermove 事件,收到该事件后
      • 进行 touchmove 处理 (触摸屏)
        • 记录该 pointer 的位置
          • handleTouchMoveRotate
            • 根据当前 pointer 和初始 pointer 的坐标差值,控制旋转
              • rotateLeft
              • rotateUp
            • 用当前 pointer 的坐标值,更新初始坐标值的位置
          • handleTouchMoveDollyPan
            • handleTouchMoveDolly
              • 根据当前两指间距离和初始两指间距离的比例,计算缩放比例
              • dolly()
              • 用当前两指间距离,更新初始的两指间距离
            • handleTouchMovePan
              • 计算当前两指的平均坐标值
              • 根据当前坐标和初始坐标,计算移动位置
              • pan()
              • 用当前两指的平均坐标值,更新初始的两指坐标值
      • 进行 mousemove 处理(鼠标)
    • 开始监听 pointerup 事件,收到该事件后
      • 把该事件相关的 pointer 从 pointer 列表中删除 
      • 如果当前没有活动的 pointer (鼠标所有按键都松开了,触摸屏上没有手指活动)
        • 不再监听 pointermove
        • 不再监听 pointerup
  • 监听 pointercancel 事件
    • 移动浏览器会自动发送 pointercancel 事件,这时我们需要在三维图形的画布 (canvas) 的 CSS 中,把  touch-action 属性设为 none

2.2 功能指标

2.2.1 前提

支持的设备

支持 鼠标操作 触摸屏操作

  • 鼠标:左键拖动 = 旋转,右键拖动 = 平移,滚轮 = 缩放;按住 ctrl / shift / meta 三个键中的任意一个,可以切换左、右键的效果。
  • 触摸屏:单指拖动 = 旋转,双指拖动 = 平移,双指 Pinch = 缩放

目前暂时不支持键盘

支持的投影模式

支持 正交照相机 (orthographic camera) 和 透视照相机 (perspective camera) 两种模式

正交照相机没有 “近大远小” 的效果,不能靠移动照相机的位置进行图形的缩放。

2.2.2 缩放

操作:

  • 鼠标:滚轮
  • 触摸屏:双指 Pinch

要求:缩放效果均匀,不会随着放大或者缩小变化得特别快或者特别慢。

限制:最多放大 5 倍,最小缩到 1/5

2.2.3 平移

操作:

  • 鼠标:右键拖动  (或者按住 ctrl / shift / meta 三键之一,同时左键拖动)
  • 触摸屏:双指拖动

要求:形体跟随 pointer (鼠标或手指)移动,跟随性良好,

限制:形体不得移出 pointer 控制范围之外,不得移到屏幕之外完全不可见。

2.2.4 旋转

操作:

  • 鼠标:左键拖动(或者按住 ctrl / shift / meta 三键之一,同时右键拖动)
  • 触摸屏:单指拖动

要求:绕形体中心点旋转,形体跟随 pointer ( 鼠标或手指)转动,跟随性好。

限制:上下旋转不超过 2Pi, 左右旋转不超过 2Pi

注意:

  • 双指操作时,Pinch 和 拖动可能同时进行,因此,可能产生 “既平移,又缩放” 的效果
  • 一些笔记本电脑的触控板,比如 Dell 大多数型号的笔记本电脑,并不支持实际意义上的多点触控,上面的 pinch 实际上产生的是 mousewheel (鼠标滚轮)事件,而对双指拖动没有任何反应。

三、实现

3.1 缩放

正交相机 Orthographic 透视相机 Perspective
Mouse

每次事件缩放固定的比例

  • deltaY>0,滚轮向下:  缩小
  • deltaY<0,滚轮向上:  放大

比例 = 0.95^zoomSpeed

也可以直接考虑设置 zoom 参数
Touch

根据两次事件指尖距离的比例 (r),
以 r^zoomSpeed 进行缩放

  • 指尖距离变大,r>1,放大
  • 指尖距离变小,r<1,缩小
统一考虑调整 zoom 参数

3.2 平移

根据 camera.zoom 的比例,结合 pointer 在屏幕上的位置变化向量,计算物体移动的距离。

物体移动(x, -y) = Pointer(dx, dy) * 视场(x,y) / 画布 (x,y) / camera.zoom

正交相机  视场(x,y) = camera.right - camera.left , camera.top - camera.bottom

透视相机  视场(x,y) = tan(camera.fov/2)*camera.postion.z*2, tan(camera.fov/2)*camera.postion.z*2*camera.aspect

3.3 旋转

每次事件只做步长为 0.05 的旋转,根据 Pointer(x, y) 的移动方向,进行左右,或者上下旋转。

学习网站参考
Discover three.js!

Three.js – JavaScript 3D Library

你可能感兴趣的:(three.js,three.js)