three.js 數學方法之Box3


從今天開始郭先生就會說一下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 的一些數學方法並不能起飛,但是遇到問題可以得心應手使用它,做到事半功倍。

轉載請注明地址:郭先生的博客

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM