three.js 數學方法之Plane

Vadim發表於2020-07-21

今天郭先生就來繼續說一說three.js數學方法中的plane(平面)。在三維空間中無限延伸的二維平面,平面方程用單位長度的法向量和常數表示。構造器為Plane( normal : Vector3, constant : Float )。第一個引數為平面的法向量,既然是法向量也就預示著這個平面是有方向之分的,第二個引數是平面到法向量的距離,因為法向量相同到原點距離相同的平面也是有兩個,所以這個constant也是有正負號的之分的。接下來我先說下它的屬性和方法,最後給一個plane相關的小案例。

1. plane的屬性

1. normal

獲取plane的法向量

2. constant

獲取plane到原點的距離

2. plane的方法

1. set( normal: Vector3, constant: number ): Plane

設定平面 normal 的法線和常量 constant 屬性值。

new THREE.Plane().set(new THREE.Vector3(0,1,0), -10);

這裡可要注意plane的法向量為new THREE.Vector3(0,1,0)代表平面的正方向是沿Y軸向上的,距離-10代表平面到原點的距離,符號代表和法向量的方向相反,所以這個這個平面在Y軸正方向。如下圖,這是一個size為20的planeHelper所展示的plane。

 

2. setComponents( x: number, y: number, z: number, w: number ): Plane

設定定義平面的各個變數。
x - 單位長度法向量的x值。
y - 單位長度法向量的y值。
z - 單位長度法向量的z值。
w - 原點沿法向量到平面常量 constant 距離。

new THREE.Plane().setComponents(0,1,0,-10);//效果同上

3. setFromNormalAndCoplanarPoint( normal: Vector3, point: Vector3 ): Plane

通過平面上的一點以及法線確定平面。

new THREE.Plane().setFromNormalAndCoplanarPoint(new THREE.Vector3(0,1,0), new THREE.Vector3(10,10,10));//效果同上

4. setFromCoplanarPoints( a: Vector3, b: Vector3, c: Vector3 ): Plane

根據給定的三個點確定平面。通過右手螺旋規則確定(向量叉乘)法向量 normal。

new THREE.Plane().setFromCoplanarPoints(new THREE.Vector3(-10,10,9), new THREE.Vector3(2,10,3), new THREE.Vector3(10,10,10));//效果同上 
new THREE.Plane().setFromCoplanarPoints(new THREE.Vector3(1,2,3), new THREE.Vector3(2,2,3), new THREE.Vector3(3,2,3));//如果三個點共線的話將返回預設的平面

5. clone(): this

返回一個與當前平面有相同法線 normal,常量 constant 距離的平面。就不多說了

6. copy( plane: Plane ): this

拷貝給定平面,將其中的法線 normal,距離常量 constant屬性拷貝給該物件。克隆和拷貝的方法我們已經說的很多了

7. normalize(): Plane

歸一化法向量 normal ,並相應的調整常量 constant數值。這個比較有意思我們說一下

//首先我們通過建構函式,並且兩個法向量方向相同模長不同,因為返回的constant相同,實際上得到的是相同的平面,因為法向量預設是歸一化了的 
var plane1 = new THREE.Plane(new THREE.Vector3(2,2,0), -20);//constant: -20,normal: Vector3 {x: 2, y: 2, z: 0}
var plane2 = new THREE.Plane(new THREE.Vector3(1,1,0), -20);//constant: -20,normal: Vector3 {x: 1, y: 1, z: 0} //然後我們將其歸一化,這是兩個plane的constant,也會隨著歸一化的比例調整,所以實際效果的距離相差一倍。
var plane_n1 = plane1.normalize();//返回constant: -7.071067811865475,normal: Vector3 {x: 0.7071067811865475, y: 0.7071067811865475, z: 0}
var plane_n2 = plane2.normalize();//返回constant: -14.14213562373095,normal: Vector3 {x: 0.7071067811865475, y: 0.7071067811865475, z: 0}

8. negate(): Plane

將法向量與常量求反(乘以-1)。

var plane1 = new THREE.Plane(new THREE.Vector3(2,2,0), -10); 
plane1.negate();

注意這個方法直接修改原plane

9. distanceToPoint( point: Vector3 ): number

返回點point到平面的有符號距離。在法向量方向的正方向則返回正值。

var plane1 = new THREE.Plane(new THREE.Vector3(0,1,0), -10); 
plane1.distanceToPoint(new THREE.Vector3(0,1,0))//返回-9

10. distanceToSphere( sphere: Sphere ): number

返回球面 sphere 的邊緣到平面的最短距離。

var plane = new THREE.Plane(new THREE.Vector3(0,1,0), -10);
var sphere = new THREE.Sphere(new THREE.Vector3(0,0,0), 5);
plane.distanceToSphere(sphere)//返回-15
//three.js distanceToSphere原始碼
distanceToSphere: function ( sphere ) {
    return this.distanceToPoint( sphere.center ) - sphere.radius;
},

按照道理來說球面到平面的最近距離應該是5,而返回的是-15,看到原始碼發現,這個最短距離的求法是先算平面到球心的距離減去球的半徑,即-10-5=-15。

11. projectPoint( point: Vector3, target: Vector3 ): Vector3

將一個點point投射到該平面上。

var plane = new THREE.Plane(new THREE.Vector3(0,1,0), -10); 
plane.projectPoint(new THREE.Vector3(1,1,1));//返回(1,10,1)

12. intersectLine( line: Line3, target: Vector3 ): Vector3 | undefined

返回給定線段和平面的交點。如果不相交則返回undefined。如果線與平面共面,則返回該線段的起始點。

var line1 = new THREE.Line3(new THREE.Vector3(0,0,0), new THREE.Vector3(9,9,9)); 
var line2 = new THREE.Line3(new THREE.Vector3(0,0,0), new THREE.Vector3(11,11,11));
console.log(plane.intersectLine(line1))//返回undefined
console.log(plane.intersectLine(line2))//返回Vector3 {x: 10, y: 10, z: 10}

13. intersectsLine( line: Line3 ): boolean

測試線段是否與平面相交。

var line1 = new THREE.Line3(new THREE.Vector3(0,0,0), new THREE.Vector3(9,9,9)); 
var line2 = new THREE.Line3(new THREE.Vector3(0,0,0), new THREE.Vector3(11,11,11));
console.log(plane.intersectLine(line1))//返回false
console.log(plane.intersectLine(line2))//返回true

14. intersectsBox( box: Box3 ): boolean

確定該平面是否與給定3d包圍盒Box3相交。

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(-2,-2,-2), new THREE.Vector3(2,10,2));
console.log(plane.intersectsBox(box1));//不相交返回false
console.log(plane.intersectsBox(box2));//相交返回true

15. intersectsSphere( sphere: Sphere ): boolean

確定該平面是否與給定球體 Sphere 相交。

var sphere1 = new THREE.Sphere(new THREE.Vector3(0,0,0), 5); 
var sphere2 = new THREE.Sphere(new THREE.Vector3(0,0,0), 10);
console.log(plane.intersectsBox(sphere1));//不相交返回false
console.log(plane.intersectsBox(sphere2));//相交返回true
 

16. coplanarPoint( target: Vector3 ): Vector3

返回一個共麵點,通過原點的法向量在平面上投影算得。這個方法和projectPoint很相似。只是固定為原點的投影,

var plane = new THREE.Plane(new THREE.Vector3(1,1,1), -10);
plane.coplanarPoint(new THREE.Vector3())//返回Vector3 {x: 10, y: 10, z: 10}
//如果將法向量歸一化,這樣一來得到的這個點正好是在平面上
var plane = new THREE.Plane(new THREE.Vector3(1,1,1).normalize(), -10);
plane.coplanarPoint(new THREE.Vector3())//返回Vector3 {x: 5.7735, y: 5.7735, z: 5.7735}
//three.js coplanarPoint原始碼
// normal is assumed to be normalized
return target.copy( this.normal ).multiplyScalar( - this.constant );

 

17. applyMatrix4( matrix: Matrix4, optionalNormalMatrix?: Matrix3 ): Plane

matrix - 要應用的四位矩陣(Matrix4)。
optionalNormalMatrix - (可選引數) 預先計算好的上述Matrix4引數的法線矩陣 Matrix3。
在平面上應用矩陣。矩陣必須是仿射齊次變換。

var plane = new THREE.Plane(new THREE.Vector3(0,1,0), -10);//返回constant: -10,normal: Vector3 {x: 0.7071067811865475, y: 0.7071067811865476, z: 0} 
var matrix = new THREE.Matrix4().makeRotationZ(-Math.PI/4);
plane.applyMatrix4(matrix);//返回constant: -10,normal: Vector3 {x: 1, y: 2.220446049250313e-16, z: 0}

18. translate( offset: Vector3 ): Plane

將平面平移給定向量大小,注意:這隻會影響平面的常量不會影響平面的法向量。

var plane = new THREE.Plane(new THREE.Vector3(0,1,0), -10); 
plane.translate(new THREE.Vector3(0,-5,0));//將平面向下平移5個單位

19. equals( plane: Plane ): boolean

檢查兩個平面是否相等。(法線 normal 以及常量 constant 都相同)。很簡單我們不多說。

3.使用plane

應用平面的地方有很多,我來拿WebGLRenderer的clippingPlanes屬性做案例,clippingPlanes是包含Plane的陣列作為剪裁平面,主要程式碼如下,線上案例點選部落格原文

box = new THREE.Mesh(new THREE.BoxGeometry(20,20,20), new THREE.MeshNormalMaterial({side: THREE.DoubleSide}));
plane = new THREE.Plane(new THREE.Vector3(1,1,1), 0);
planeHelper = new THREE.PlaneHelper(plane, 30);
renderer.clippingPlanes = [plane]
scene.add(planeHelper);
scene.add(box);
gui = new GUI();
params = new function() {
    this.normal_x = 1;
    this.normal_y = 1;
    this.normal_z = 1;
    this.constant = 0;
    this.clipping = true;
};
gui.add(params, 'normal_x', -1, 1).onChange(() => setParams());
gui.add(params, 'normal_y', -1, 1).onChange(() => setParams());
gui.add(params, 'normal_z', -1, 1).onChange(() => setParams());
gui.add(params, 'constant', -10, 10).onChange(() => setParams());
gui.add(params, 'clipping').onChange(e => renderer.clippingPlanes = e ? [plane] : []);

this.render();
document.getElementById("loading").style.display = "none";

function setParams() {
    plane.set(new THREE.Vector3(params.normal_x, params.normal_y, params.normal_z), params.constant);
    planeHelper = new THREE.PlaneHelper(plane, 30)
}

 

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

 

相關文章