three.js 數學方法之Box3

Vadim發表於2020-07-20

從今天開始郭先生就會說一下three.js 的一些數學方法了,像Box3、Plane、Vector3、Matrix3、Matrix4當然還有尤拉角和四元數。今天說一說three.js的Box3方法(Box2是Box3的二維版本,可以參考Box3)。線上案例點選部落格原文

Box3在3D空間中表示一個包圍盒。其主要用於表示物體在世界座標中的邊界框。它方便我們判斷物體和物體、物體和平面、物體和點的關係等等。
構造器引數Box3( min : Vector3, max : Vector3 ),其引數為兩個三維向量,第一個向量為Box3在3D空間中各個維度的最小值,第二個引數為Box3在3D空間中各個維度的最大值,程式碼如下。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2));

這個box就表示3D空間中中心點在(0,0,0),長寬高為4的包圍盒。
下面我們十分詳細的說說他的屬性和方法。

1. Box3的屬性

Box3只有三個屬性。

  1. isBox3 – 用於檢測當前物件或者派生類物件是否是Box3。預設為 true。
  2. .min – Vector3 表示包圍盒的(x, y, z)下邊界。預設值是( + Infinity, + Infinity, + Infinity )。
  3. .max – Vector3 表示包圍盒的(x, y, z)上邊界。預設值是( - Infinity, - Infinity, - Infinity )。

2. Box3的方法

1. set( min: Vector3, max: Vector3 )

這個比較簡單,就是設定包圍盒的上下邊界

var box = new THREE.Box3().set(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2));//返回的包圍盒和上面的包圍盒相同

2. setFromArray( array: ArrayLike )

設定包圍盒的上下邊界使得陣列 array 中的所有點的點都被包含在內

var box = new THREE.Box3().setFromArray([-2,-2,-2,2,2,2]);//返回的包圍盒和上面的包圍盒相同

3. setFromBufferAttribute( bufferAttribute: BufferAttribute )

設定此包圍盒的上邊界和下邊界,以包含 attribute 中的所有位置資料,使用方法如下

var typedArray= new Float32Array(3*2); 
var array = [-2,-2,-2,2,2,2];
array.forEach((d,i)=>typedArray[i] = d);
var bufferAttribute = new THREE.BufferAttribute(typedArray,3);
var box = new THREE.Box3().setFromBufferAttribute(bufferAttribute);

這裡注意BufferAttribute的第一個引數是一個型別化陣列,這個放到以後再說。

4. setFromPoints( points: Vector3[] )

設定此包圍盒的上邊界和下邊界,以包含陣列 points 中的所有點。

var box = new THREE.Box3().setFromPoints([new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)]);//返回的包圍盒和上面的包圍盒相同

5. setFromCenterAndSize( center: Vector3, size: Vector3 )

將當前包圍盒的中心點設定為 center ,並將此包圍盒的寬度,高度和深度設定為大小指定 size 的值。

var box = new THREE.Box3().setFromCenterAndSize(new THREE.Vector3(0,0,0), new THREE.Vector3(4,4,4))//返回的包圍盒和上面的包圍盒相同

6. setFromObject( object: Object3D )

計算和世界軸對齊的一個物件 Object3D (含其子物件)的包圍盒,計算物件和子物件的世界座標變換。

var boxObject = new THREE.Mesh( new THREE.BoxGeometry(5, 5, 5), new THREE.MeshBasicMaterial({ color: 0xffaa00 }) );
var box = new THREE.Box3().setFromObject(boxObject);

把正方體網格作為引數,實際上是根據geometry.vertices的Vector3點集和computeBoundingBox()方法計算的。

7. clone()

返回一個與該包圍盒子有相同下邊界min 和上邊界 max的新包圍盒程式碼如下

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); var newBox = box.clone();

8. copy( box: Box3 )

將傳入的值 box 中的 min 和 max 拷貝到當前物件。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2));
var newBox = new THREE.Box3().copy(box);

9. makeEmpty()

清空包圍盒,下邊界為( + Infinity, + Infinity, + Infinity ),上邊界為( - Infinity, - Infinity, - Infinity )

10. isEmpty()

如果這個包圍盒包含0個頂點,則返回true。注意,下界和上界相等的方框仍然包含一個點,即兩個邊界共享的那個點。
這個方法比較有意思,可以判斷包圍盒是否為空,體會下面的程式碼

new THREE.Box3(new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,0)).isEmpty()//返回false 
new THREE.Box3(new THREE.Vector3(0,0,0), new THREE.Vector3(-1,0,0)).isEmpty()//返回true

正常情況下包圍盒的上邊界都是大於等於下邊界的,如果某一個維度的上邊界小於下邊界那麼這個包圍盒就是空盒子

11. getCenter( target: Vector3 )

返回包圍盒的中心點 Vector3。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
box.getCenter()/返回中心點Vector3 {x: 0, y: 0, z: 0}

12. getSize( target: Vector3 )

返回包圍盒的寬度,高度,和深度。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
box.getSize()/返回包圍盒的寬度,高度,和深度Vector3 {x: 4, y: 4, z: 4}

13. expandByPoint( point: Vector3 )

擴充套件這個包圍盒的邊界使得該點(point)在包圍盒內。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
box.expandByPoint(new THREE.Vector3(4,0,0)).getCenter()//中心點已不是Vector3(0,0,0),而是Vector3(1,0,0)

通過Vector3(3,0,0)這個點擴充套件了原本的包圍盒

14. expandByVector( vector: Vector3 )

按 vector 每個緯度的值展開這個箱子。 這個盒子的寬度將由 vector 的x分量在兩個方向上展開。 這個盒子的高度將由 vector 兩個方向上的y分量展開。 這個盒子的深度將由 vector z分量在兩個方向上展開。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
box.expandByVector(new THREE.Vector3(0,1,2)).getSize()//新的包圍盒size已變成Vector3 {x: 4, y: 6, z: 8}

15. expandByScalar( scalar: number )

按 scalar 的值展開盒子的每個維度。如果是負數,盒子的尺寸會縮小。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
box.expandByVector(1).getSize()//新的包圍盒size已變成Vector3 {x: 6, y: 6, z: 6}

16. expandByObject( object: Object3D )

擴充套件此包圍盒的邊界,使得物件及其子物件在包圍盒內,包括物件和子物件的世界座標的變換。

var boxObject = new THREE.Mesh( new THREE.BoxGeometry(2,2,2), new THREE.MeshBasicMaterial({ color: 0xffaa00 }) ); 
boxObject.position.set(2,0,0);//或者boxObject.geometry.translate(2,0,0);
var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); box.expandByObject(boxObject);

17. containsPoint( point: Vector3 )

當傳入的值 point 在包圍盒內部或者邊界都會返回true。這是個比較有用的方法

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
var point1 = new THREE.Vector3(1,2,2);
var point2 = new THREE.Vector3(2,2,2);
var point3 = new THREE.Vector3(3,2,2);
box.containsPoint(point1)//返回true
box.containsPoint(point2)//返回true
box.containsPoint(point3)//返回false

18. containsBox( box: Box3 )

傳入的 box 整體都被包含在該物件中則返回true。如果他們兩個包圍盒是一樣的也返回true。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
var box1 = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(1,2,2));
var box2 = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2));
var box3 = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(3,2,2));
console.log(box.containsBox(box1))//返回true
console.log(box.containsBox(box2))//返回true
console.log(box.containsBox(box3))//返回false

19. getParameter( point: Vector3 )

返回一個點為這個盒子的寬度和高度的比例。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
console.log(box.getParameter(new THREE.Vector3(0,0,0),new THREE.Vector3()))//返回Vector3 {x: 0.5, y: 0.5, z: 0.5};
console.log(box.getParameter(new THREE.Vector3(1,0,0),new THREE.Vector3()))//返回Vector3 {x: 0.75, y: 0.5, z: 0.5}
console.log(box.getParameter(new THREE.Vector3(2,0,0),new THREE.Vector3()))//返回Vector3 {x: 1, y: 0.5, z: 0.5}
console.log(box.getParameter(new THREE.Vector3(3,0,0),new THREE.Vector3()))//返回Vector3 {x: 1.25, y: 0.5, z: 0.5}

這裡我們只觀察x方向,第一個輸出x=0,剛好在包圍盒的中心點,所以返回了0.5,第三個輸出x=2剛好在包圍盒的上邊界,所以返回1,也就是100%,當然超過上邊界就大於1(100%),低於下邊界就小於0(0%)。

20. intersectsBox( box: Box3 )

確定當前包圍盒是否與傳入包圍盒box 相交。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
var box1 = new THREE.Box3(new THREE.Vector3(2,2,2), new THREE.Vector3(4,4,4));
var box2 = new THREE.Box3(new THREE.Vector3(3,2,2), new THREE.Vector3(4,4,4));
console.log(box.intersectsBox(box1))//box與box1相交,邊界相交也算相交
console.log(box.intersectsBox(box2))//box與box2不想交,

21. intersectsSphere( sphere: Sphere )

確定當前包圍盒是否與球體 sphere 相交。
這個球體和包圍和一樣,都是一個3D空間。由一箇中心點和半徑構成,和包圍盒十分類似,這裡就不多贅述。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
var sphere1 = new THREE.Sphere(new THREE.Vector3(4,2,2), 1);
var sphere2 = new THREE.Sphere(new THREE.Vector3(4,2,2), 2);
var sphere3 = new THREE.Sphere(new THREE.Vector3(4,2,2), 3);
console.log(box.intersectsSphere(sphere1))//返回false
console.log(box.intersectsSphere(sphere2))//返回true
console.log(box.intersectsSphere(sphere3))//返回true

這裡可以看出,他們的邊界相交也算相交。

22. intersectsPlane( plane: Plane )

檢測這個球與所傳入的plane是否有交集。這個plane是在三維空間中無限延伸的二維平面,平面方程用單位長度的法向量和常數表示為海塞法向量Hessian normal form形式。它的構造器有兩個引數,第一個是normal - (可選引數) 定義單位長度的平面法向量Vector3。預設值為 (1, 0, 0)。第二個是constant - (可選引數) 從原點到平面的有符號距離。 預設值為 0。這個plane我們日後還會講。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
var plane1 = new THREE.Plane(new THREE.Vector3(1,0,0), 1);
var plane2 = new THREE.Plane(new THREE.Vector3(1,0,0), 2);
var plane3 = new THREE.Plane(new THREE.Vector3(1,0,0), 3);
console.log(box.intersectsPlane(plane1))//返回true
console.log(box.intersectsPlane(plane2))//返回true
console.log(box.intersectsPlane(plane3))//返回false

這裡要注意平面的第二個引數是有符號的距離,所以程式碼中的三個平面都是在x軸的負半軸。

23. intersectsTriangle( triangle: Triangle )

確定當前包圍盒是否與三角形 triangle 相交。這個三角同樣是一個數學庫,這裡也不先說

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
var triangle1 = new THREE.Triangle(new THREE.Vector3(1,-1,1),new THREE.Vector3(1,-1,-1),new THREE.Vector3(1,0,1));
var triangle2 = new THREE.Triangle(new THREE.Vector3(2,-1,1),new THREE.Vector3(2,-1,-1),new THREE.Vector3(2,0,1));
var triangle3 = new THREE.Triangle(new THREE.Vector3(3,-1,1),new THREE.Vector3(3,-1,-1),new THREE.Vector3(3,0,1));
console.log(box.intersectsTriangle(triangle1))//返回true
console.log(box.intersectsTriangle(triangle2))//返回true
console.log(box.intersectsTriangle(triangle3))//返回false

24. clampPoint( point: Vector3, target: Vector3 )

是這個點point Clamps(處於範圍內) 處於包圍盒邊界範圍內,如果我們傳一個target,那麼新點就會複製到target上。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
console.log(box.clampPoint(new THREE.Vector3(3,0,0),new THREE.Vector3()))//這裡返回Vector3 {x: 2, y: 0, z: 0}
console.log(box.clampPoint(new THREE.Vector3(3,3,3),new THREE.Vector3()))//這裡返回Vector3 {x: 2, y: 2, z: 2}

這個結果可以知道,包圍盒的這個方法把傳入的任意點都轉化成包圍盒邊界上或者包圍盒內的點,如何點的某個維度不在包圍盒中,那麼這個維度就返回包圍盒這個維度的邊界的最大值或最小值。

25. distanceToPoint( point: Vector3 )

返回這個box的任何邊緣到指定點的距離。如果這個點位於這個盒子裡,距離將是0。這是個比較好的方法。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
console.log(box.distanceToPoint(new THREE.Vector3(2,2,2)))//返回0,因為在邊界上
console.log(box.distanceToPoint(new THREE.Vector3(3,3,3)))//返回1.732(根號3),因為離這個點最近的點是new THREE.Vector3(2,2,2。

26. getBoundingSphere( target: Sphere )

通過包圍盒獲取包圍球。得到的包圍球剛好包圍包圍盒

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
box.getBoundingSphere(new THREE.Sphere())//center: Vector3 {x: 0, y: 0, z: 0},radius: 3.4641016151377544

中心就是包圍盒的中心,半徑就是中心到一個頂點的距離。

27. intersect( box: Box3 )

返回此包圍盒和 box 的交集,將此框的上界設定為兩個框的max的較小部分, 將此包圍盒的下界設定為兩個包圍盒的min的較大部分。

var box1 = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
var box2 = new THREE.Box3(new THREE.Vector3(0,0,0), new THREE.Vector3(4,4,4));
console.log(box1.intersect(box2))//返回max: Vector3 {x: 2, y: 2, z: 2},min: Vector3 {x: 0, y: 0, z: 0}

28. union( box: Box3 )

在 box 引數的上邊界和已有box物件的上邊界之間取較大者,而對兩者的下邊界取較小者,這樣獲得一個新的較大的聯合盒子。

var box1 = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
var box2 = new THREE.Box3(new THREE.Vector3(0,0,0), new THREE.Vector3(4,4,4));
console.log(box1.union(box2))//返回max: Vector3 {x: 4, y: 4, z: 4},min: Vector3 {x: -2, y: -2, z: -2}

29. applyMatrix4( matrix: Matrix4 )

使用傳入的矩陣變換Box3(包圍盒8個頂點都會乘以這個變換矩陣)

var matrix4 = new THREE.Matrix4().makeScale(0,1,2);//得到一個縮放矩陣 
var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2));
box.applyMatrix4(matrix4);//包圍盒應用矩陣,返回max: Vector3 {x: 0, y: 2, z: 4} min: Vector3 {x: 0, y: -2, z: -4}

30. translate( offset: Vector3 )

給包圍盒的上下邊界新增偏移量 offset,這樣可以有效的在3D空間中移動包圍盒。 偏移量為 offset 大小。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
box.translate(new THREE.Vector3(1,0,0))//返回max: Vector3 {x: 3, y: 2, z: 2},min: Vector3 {x: -1, y: -2, z: -2}

31. equals( box: Box3 )

如果矩陣m 與當前矩陣所有對應元素相同則返回true。

var box = new THREE.Box3(new THREE.Vector3(-2,-2,-2), new THREE.Vector3(2,2,2)); 
var box1 = box.clone(); box.equals(box1)//box和它克隆的包圍盒相等。

這是Box3的全部方法了。

3. Box3的應用案例

這裡有兩個相對運動的網格,我們來判斷他們的相對位置,如下圖。

下面是主要程式碼

setBox3() {
    var boxGeometry = new THREE.BoxGeometry(30, 30, 30);
    var sphereGoemetry = new THREE.SphereGeometry(3, 30, 20);
    var sphereMaterial = new THREE.MeshBasicMaterial();
    box = this.setMaterial(boxGeometry, 0x0000ff);//先生成一個立方體網格
    box3 = new THREE.Box3().setFromObject(box);//根據幾何體生成包圍盒
    sphere = new THREE.Mesh(sphereGoemetry, sphereMaterial);//在生成一個球形網格
    scene.add(box);//新增到場景
    scene.add(sphere);//新增到場景

    this.render();
},
render() {
        //讓球動起來
    sphere.position.y = Math.sin(time) * 16 + 8;
    sphere.position.x = Math.cos(time) * 16 + 8;
    time = time + 0.02;
    sphereBox3 = new THREE.Box3().setFromObject(sphere);//動態生成球的包圍盒(這裡用了包圍盒,沒有用包圍球,邊邊角角有些出入,不影響大體效果)
    if(box3.containsBox(sphereBox3)) {
                //如果box3包含sphereBox3
        sphere.material.color = new THREE.Color(0x00ff00);
    } else if(box3.intersectsBox(sphereBox3)) {
                //如果box3交於sphereBox3
        sphere.material.color = new THREE.Color(0xff00ff);
    } else {
                //如果sphereBox3在box3之外
        sphere.material.color = new THREE.Color(0xffaa00);
    }
    renderer.render(scene, camera);
    requestAnimationFrame(this.render);
}

學好three.js 的一些數學方法並不能起飛,但是遇到問題可以得心應手使用它,做到事半功倍。

轉載請註明地址:郭先生的部落格

 

相關文章