前言

  这个问题其实提早之前就解决了,一直懒得更新博客。
  趁着最近“山竹”台风天放假,我就来更新一波。

  其实区域限制在OrbitContrl 中是有的,但是OrbitControl的上下交互不及EditorControl 来得舒服。
  所以,客户要求使用EditorControl的效果,但是EditorControl又没有OrbitControl的区域限制效果。
  倘若不限制区域的话,用户将很有可能不知所踪,这样交互就很糟糕。

  于是为了解决这个问题,我就借鉴了OrbitControl的区域限制写法。

OrbitControl区域限制剖析

OrbitControl.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// How far you can dolly in and out ( PerspectiveCamera only )
this.minDistance = 0;
this.maxDistance = Infinity;

// How far you can zoom in and out ( OrthographicCamera only )
this.minZoom = 0;
this.maxZoom = Infinity;

// How far you can orbit vertically, upper and lower limits.
// Range is 0 to Math.PI radians.
this.minPolarAngle = 0; // radians
this.maxPolarAngle = Math.PI; // radians

// How far you can orbit horizontally, upper and lower limits.
// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
this.minAzimuthAngle = - Infinity; // radians
this.maxAzimuthAngle = Infinity; // radians

  上面的部分就是OrbitControl中关于限制区域的API
  那么这些API是怎么运作的呢?
  我们可以追踪minDistance的去向。
  在169行代码可以找到这一行。

1
2
// restrict radius to be between desired limits
spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );

  也就说让 spherical.radius 的最大最小范围只能是 maxDistance 和 minDistance
  那么什么是 spherical.radius 呢?
  继续追查spherical

1
2
var spherical = new THREE.Spherical();

  在260行可以看到这里声明了spherical
  什么是 THREE.Spherical ?
  又去官方文档查找资料

  从这里可以知道 spherical 和数学运算有关,通过spherical可以限制摄像机做圆周运动。
  radius是圆心半径,而 phi 和 theta 就分别是经纬度,刚好和官方说的 spherical coordinates 吻合。

  由此可以知道远近距离就是通过限制这个球坐标系的半径实现的。

  那么同理 minPolarAngle 和 minAzimuthAngle 就是分别限制 经度 和 纬度 了。

EditorContrl 区域限制修改

  EditorContrl 和 OrbitControl 不是用相同的方法开发的,直接复制代码就别想了。
  通过观察EditorControl的API可以发现, this.pan this.zoom this.rotate 这三个函数是控制移动的关键部分。

  而这其中 this.rotate 使用了spherical的方法。
  因此迁移代码也就不难了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
this.rotate = function ( delta ) {

vector.copy( object.position ).sub( center );

spherical.setFromVector3( vector );

spherical.theta += delta.x;
spherical.phi += delta.y;

/*加入的代码*/
// restrict theta to be between desired limits
spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
// restrict phi to be between desired limits
spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );

spherical.makeSafe();

vector.setFromSpherical( spherical );

object.position.copy( center ).add( vector );

object.lookAt( center );

scope.dispatchEvent( changeEvent );

};

  如此一来就实现了经纬度旋转限制的效果
  但是修改this.zoom的时候就遇到了麻烦
  这里的this.zoom并没有使用 spherical
  而是直接计算 摄像机 到 聚焦中心 的距离

  如此就无法直接使用 sphercial 的代码套用
  在这个过程中也遇到了很多坑,搞不好就是旋转好好地,一平移镜头就会跳。
  本来想着不用 spherical 来解决的,最后还是妥协了,把spherical加进去计算就OK了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
this.zoom = function ( delta ) {

var distance = object.position.distanceTo( center );

delta.multiplyScalar( distance * scope.zoomSpeed );

if ( delta.length() > distance ) return;

/*加入的代码*/
// delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
vector.copy( object.position ).sub( center );

spherical.setFromVector3( vector );

spherical.radius += delta.z;

// restrict radius to be between desired limits
spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );

vector.setFromSpherical( spherical );

object.position.copy( center ).add( vector );
// object.position.add( delta );

scope.dispatchEvent( changeEvent );

};

  最后的最后就是需要限制生相机的聚焦中心了。
  这个功能OrbitControl也没有,但是想到spherical的区域限制实现。
  自然也就有限制中心的想法。

  查了 Three.js 提供的数学运算,其中就有BOX3的方法
  这样就可以用类似的方法将聚焦中心限制在盒子中了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//设置初始值
this.panLimitMax = new THREE.Vector3(10,3,10);
this.panLimitMin = new THREE.Vector3(-10,-3,-10);

var box = new THREE.Box3();
box.min = this.panLimitMin;
box.max = this.panLimitMax;

/*其他代码*/

this.pan = function ( delta ) {

var distance = object.position.distanceTo( center );

delta.multiplyScalar( distance * scope.panSpeed );
delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );

object.position.add( delta );
center.add( delta );

/*加入的代码*/
vector.copy( object.position ).sub( center );

spherical.setFromVector3( vector );

box.min = this.panLimitMin;
box.max = this.panLimitMax;
box.clampPoint(center,center);

vector.setFromSpherical( spherical );

object.position.copy( center ).add( vector );
/*结束*/

scope.dispatchEvent( changeEvent );

};

  如此就实现了将摄像机聚焦中心限制在盒子中的效果,其实根据同样的方法可以迁移到球中。

总结

  突然发现其实很多复杂的东西Three.js都已经做好给你了,我并不清楚 sphercial 和 BOX3 背后的数学运算,但是既然提供了相关的API,只管用就是了。
  所以自己还是未能深入到WebGL的深层学习中,只是学习Three.js流于表面的东西。

  十月将近了,后面就是和研究了快一年的Three.js道别的时候了。
  时间真的过得很快,想来去年11月份的时候,聪哥让我研究WebGL。
  我就看了Lynda.com 和 pluralsight.com 的教程入坑了,在那里我学习到了Three.js
  后面的学习教程越来越少,只能靠看官方提供的文档和案例了,但是还是能做出一些东西,感觉还是挺欣慰的。因为这些的学习,自己的视力严重下降也真的让我感到悲哀…

  最近也将 THREE_View.js 的API文档写好了,后面继续Three.js的开发就相对简单了,通过自己封装的脚本可以写少很多很多的代码。