bcjohn's blog
理解 THREE.Plane 的平面方程式
發布於: 2023-12-19 更新於: 2024-02-18 分類於: Three.js 閱讀次數: 

THREE.Planethreejs 中用來表示數學上的平面

數學中的平面方程式

a, b, c 代表平面的法向量d 是一個常數值

1
ax + by + cz + d = 0;

創建平面

以下程式碼創建了一個 y - 1 = 0 的平面,法向量為 [0, 1, 0],常數值為 -1

1
2
3
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0).normalize(), -1);
console.log("normal", plane.normal); // {x: 0, y: 1, z: 0}
console.log("constant", plane.constant); // -1

這裡需要特別注意的是傳入法向量時需要將其轉換為單位向量[0, 10, 0][0, 1, 0] 在向量的定義上是一樣的,都是指向正 x 軸方向,但 threejs 要求在牽涉到法向量時都必須是單位向量,否則在某些運算上會錯誤,所以以上程式碼才刻意加了 .normalize()

Plane 的方法

- distanceToPoint(point: Vector3): Float

計算傳入的 point 與平面的距離

1
2
3
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), -1);
const distanceToPoint = plane.distanceToPoint(new THREE.Vector3(0, 3, 0));
console.log("distanceToPoint", distanceToPoint); // 2

這裡就可以看出為什麼一開始傳入法向量要是單位向量,以下面的例子來看:

1
2
3
const plane = new THREE.Plane(new THREE.Vector3(0, 10, 0), -1);
const distanceToPoint = plane.distanceToPoint(new THREE.Vector3(0, 3, 0));
console.log("distanceToPoint", distanceToPoint); // 29

[0, 1, 0][0, 10, 0]法向量的定義上是一樣的,所以計算出來的點與平面的距離應該要一樣,但這兩個寫法的結果卻完全不一樣,原因可以從 threejsdistanceToPoint 來查看

1
2
3
distanceToPoint(point) {
return this.normal.dot(point) + this.constant;
}

threejs 原始碼來看,假設 point 座標為 (x0, y0, z0),計算 distanceToPoint 的數學式為:

$$ ax_0 + by_0 + cz_0 + d $$

但實際用數學計算某個點與平面的距離公式是:

$$ \frac{\lvert ax_0 + by_0 + cz_0 + d \rvert}{\sqrt{a^2+b^2+c^2}} $$

可以看出來差異就在分母的 $ \sqrt{a^2+b^2+c^2} $,而這個東西就是法向量的長度,當法向量單位向量時分母會等於 1,如此一來使用 threejsdistanceToPoint 才會與數學計算的結果一致,這也就是為什麼 threejs 中的法向量都需要先自行轉換為單位向量

Refs.

點到平面的距離公式推導
Use unit norm when finding plane distanceToPoint

- projectPoint(point:Vector3, target:Vector3): Vector3

計算空間中的某個點 point 投影到平面上的座標

  • point - 空間中的某個點 $(x_0, y_0, z_0)$
  • target - 投影到平面的座標
  • 回傳值 - 投影到平面的座標
1
2
3
4
5
6
7
8
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), -1);
const projectPointTarget = new THREE.Vector3();
const projectPoint = plane.projectPoint(
new THREE.Vector3(1, 2, 0),
projectPointTarget,
);
console.log("projectPointTarget", projectPointTarget); // {x: 1, y: 1, z: 0}
console.log("projectPoint", projectPoint); // {x: 1, y: 1, z: 0}
原始碼
1
2
3
projectPoint( point, target ) {
return target.copy( point ).addScaledVector( this.normal, - this.distanceToPoint( point ) );
}
數學推導

$ (x, y, z) = (x_0 - at, y_0 - bt, z_0 - ct) $

$ ax + by + cz + d = 0 $
$ a(x_0 - at) + b(y_0 - bt) + c(z_0 - ct) + d = 0 $
$ ax_0 + by_0 + cz_0 - a^2t - b^2t - z^2t + d = 0 $

=>
$$ t = \frac{ax_0 + by_0 + cz_0 + d}{a^2+b^2+c^2} = ax_0 + by_0 + cz_0 + d = distanceToPoint(point) $$
$$ (x, y, z) = (x_0 - at, y_0 - bt, z_0 - ct) = (x_0, y_0, z_0) + (a, b, c) \cdot -distanceToPoint(point) $$

Refs.

點到平面的距離公式

- coplanarPoint(target: Vector3): Vector3

計算沿著原點方向投影到平面的共面點

  • target - 沿著原點方向投影到平面的共面點 $(x, y, z)$

coplanarPoint 的意思是共面點,代表這個點位於平面上,coplanarPoint 的概念跟 projectPoint 很像,差別在 coplanarPoint 投影到平面上的點是沿著原點的方向,而 projectPoint 是沿著平面法向量的方向

1
2
3
4
5
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0).normalize(), -1);
const target = new THREE.Vector3();
const coplanarPoint = plane.coplanarPoint(target);
console.log("target", target); // {x: 0, y: 1, z: 0}
console.log("coplanarPoint", coplanarPoint); // {x: 0, y: 1, z: 0}
原始碼
1
2
3
coplanarPoint(target) {
return target.copy(this.normal).multiplyScalar(-this.constant);
}
數學推導

$ (x, y, z) = (at, bt, ct) $

$ ax + by + cz + d = 0 $
$ t \times (a^2+b^2+c^2) + d = 0 $
$ t = -d $

$ (x, y, z) = (a, b, c) \times -d $

- setFromNormalAndCoplanarPoint(normal: Vector3, point: Vector3 )

這個方法藉由提供平面的法向量及平面上某點的座標創建出平面

  • normal - 平面的法向量 $(a, b, c)$
  • point - 平面上某個點的座標 $(x, y, z)$
1
2
3
4
5
6
7
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0).normalize(), -1);
plane.setFromNormalAndCoplanarPoint(
new THREE.Vector3(0, 1, 0).normalize(),
new THREE.Vector3(0, 3, 0),
);
console.log("plane.normal", plane.normal); // {x: 0, y: 1, z: 0}
console.log("plane.constant", plane.constant); // -3
原始碼
1
2
3
4
5
setFromNormalAndCoplanarPoint(normal, point) {
this.normal.copy( normal );
this.constant = - point.dot( this.normal );
return this;
}
數學推導

$ (a, b, c) \cdot (x, y, z) + d = 0 $
$ d = - (x, y, z) \cdot (a, b, c) $

DEMO

版本

  • threejs r159

參考資料