Three.js中實現一個OBBHelper

當時明月在曾照彩雲歸發表於2023-09-22

1. 引言

Three.js中,Box3物件指的是AABB式的包圍盒,這種包圍盒會隨物體的旋轉而變換大小,精度較差

Three.js中還有OBB物件,這是一種能表現物體主要特徵的、不隨物體的旋轉而變換大小的包圍盒

兩者如下圖所示:

QQ截圖20230922001643

Three.js中雖然有OBB,卻沒有OBB Helper,即OBB包圍盒線框物件

本文參考Box3Helper原始碼,並寫出一個OBBHelper

2. Box3Helper

以下是Three.js原始碼中的Box3Helper:

import { LineSegments } from '../objects/LineSegments.js';
import { LineBasicMaterial } from '../materials/LineBasicMaterial.js';
import { BufferAttribute, Float32BufferAttribute } from '../core/BufferAttribute.js';
import { BufferGeometry } from '../core/BufferGeometry.js';

class Box3Helper extends LineSegments {

	constructor( box, color = 0xffff00 ) {

		const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );

		const positions = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 1, - 1, 1, - 1, - 1 ];

		const geometry = new BufferGeometry();

		geometry.setIndex( new BufferAttribute( indices, 1 ) );

		geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );

		super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) );

		this.box = box;

		this.type = 'Box3Helper';

		this.geometry.computeBoundingSphere();

	}

	updateMatrixWorld( force ) {

		const box = this.box;

		if ( box.isEmpty() ) return;

		box.getCenter( this.position );

		box.getSize( this.scale );

		this.scale.multiplyScalar( 0.5 );

		super.updateMatrixWorld( force );

	}

	dispose() {

		this.geometry.dispose();
		this.material.dispose();

	}

}

export { Box3Helper };

這段程式碼是一個名為Box3Helper的類的定義,它繼承自LineSegments類。Box3Helper類用於建立一個輔助框,用來視覺化Box3物件的邊界框。

程式碼中首先匯入了一些依賴的模組,包括LineSegmentsLineBasicMaterialBufferAttributeFloat32BufferAttributeBufferGeometry

Box3Helper類的建構函式中,首先建立了一個表示邊界框的索引陣列indices,然後建立了一個表示邊界框的頂點座標陣列positions

接下來,建立了一個BufferGeometry物件,並使用indices陣列建立了一個BufferAttribute物件來表示索引,使用positions陣列建立了一個Float32BufferAttribute物件來表示頂點座標。然後將這兩個屬性設定到geometry物件中。

然後呼叫父類LineSegments的建構函式,傳入geometry和一個LineBasicMaterial物件作為引數,來建立一個視覺化邊界框的線段物件。

接著,將傳入建構函式的box引數賦值給this.box屬性。

然後設定this.type屬性為'Box3Helper'

最後呼叫geometry物件的computeBoundingSphere方法來計算邊界球。

Box3Helper類還定義了一個updateMatrixWorld方法,用於更新輔助框的世界矩陣。在該方法中,首先獲取this.box的中心點和尺寸,然後根據尺寸縮放輔助框的比例,並呼叫父類的updateMatrixWorld方法來更新世界矩陣。

最後,定義了一個dispose方法,用於釋放資源,包括釋放geometrymaterial物件。

最後透過export語句將Box3Helper類匯出,以便在其他地方使用。

3. OBBHelper

參考上面的程式碼。給出OBBHelper的程式碼如下:

import {
	Vector3, LineSegments, LineBasicMaterial,
	BufferAttribute, Float32BufferAttribute, BufferGeometry
} from 'three';


class OBBHelper extends LineSegments {

	constructor(obb, object, color = 0xffff00) {

		const indices = new Uint16Array([0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7, 0, 2, 1, 3, 4, 6, 5, 7]);

		const positions = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 1, - 1, 1, - 1, - 1 ];
		
		const geometry = new BufferGeometry();

		geometry.setIndex(new BufferAttribute(indices, 1));

		geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));

		super(geometry, new LineBasicMaterial({ color: color, toneMapped: false }));

		this.obb = obb;

		this.object = object;

		this.type = 'OBBHelper';

		this.lastMatrix4 = object.matrixWorld.clone();

	}

	updateMatrixWorld(force) {

		this.obb.applyMatrix4(this.lastMatrix4.invert())
		this.obb.applyMatrix4(this.object.matrixWorld);
		this.lastMatrix4 = this.object.matrixWorld.clone();
		const positions = this.geometry.attributes.position.array;

		const halfSize = this.obb.halfSize;
		const center = this.obb.center;
		const rotation = this.obb.rotation;
		const corners = [];
		for (let i = 0; i < 8; i++) {
			const corner = new Vector3();
			corner.x = (i & 1) ? center.x + halfSize.x : center.x - halfSize.x;
			corner.y = (i & 2) ? center.y + halfSize.y : center.y - halfSize.y;
			corner.z = (i & 4) ? center.z + halfSize.z : center.z - halfSize.z;
			corner.applyMatrix3(rotation);
			corners.push(corner);
		}

		for (let i = 0; i < corners.length; i++) {
			const corner = corners[i];
			positions[i * 3] = corner.x;
			positions[i * 3 + 1] = corner.y;
			positions[i * 3 + 2] = corner.z;
		}

		this.geometry.attributes.position.needsUpdate = true;

		super.updateMatrixWorld(force);
	}

	dispose() {
		this.geometry.dispose();
		this.material.dispose();
	}
}

export { OBBHelper };

這段程式碼是一個自定義的 OBBHelper 類,用於建立一個輔助物件來顯示一個方向包圍盒(OBB)的邊界框。以下是程式碼的解釋:

  1. 匯入了所需的 Three.js 模組和類。這些模組和類包括 Vector3LineSegmentsLineBasicMaterialBufferAttributeFloat32BufferAttributeBufferGeometry

  2. OBBHelper 類繼承自 LineSegments 類,因此它是一個線段物件。

  3. OBBHelper 建構函式接收三個引數:obbobjectcolorobb 是一個方向包圍盒物件,object 是一個 Three.js 物件,color 是邊界框的顏色,預設為黃色(0xffff00)。

  4. 建立一個 indices 陣列,其中包含了邊界框的頂點索引。這些索引指定了邊界框的邊的連線關係。

  5. 建立一個 positions 陣列,其中包含了邊界框的頂點位置。這些位置定義了邊界框的形狀。

  6. 建立一個 BufferGeometry 物件,用於儲存幾何資料。

  7. 使用 geometry.setIndex 方法將索引資料分配給幾何體的索引屬性。

  8. 使用 geometry.setAttribute 方法將頂點位置資料分配給幾何體的位置屬性。

  9. 呼叫父類 LineSegments 的建構函式,傳遞幾何體和材質作為引數,建立一個線段物件。

  10. 設定 OBBHelper 物件的屬性,包括 obbobjecttype

  11. updateMatrixWorld 方法中,更新輔助物件的世界矩陣。首先,將上一次的世界矩陣的逆矩陣應用於 obb 物件,然後將當前的世界矩陣應用於 obb 物件。接著,根據 obb 物件的屬性計算出邊界框的頂點位置,並更新幾何體的位置屬性。

  12. 最後,呼叫父類的 updateMatrixWorld 方法,更新輔助物件的世界矩陣。

  13. dispose 方法用於釋放幾何體和材質的記憶體。

  14. 匯出 OBBHelper 類供其他模組使用。

透過使用這個 OBBHelper 類,可以建立一個輔助物件來顯示一個方向包圍盒的邊界框,並將其新增到場景中以進行渲染和顯示。

實現的效果如下(黃色為Box3Helper,紅色為OBBHelper):

動畫

4. 參考資料

[1] OBB – three.js docs (three3d.cn)

[2] three.js/src/helpers/Box3Helper.js at master · mrdoob/three.js (github.com)

[3] three.js examples (three3d.cn)

[4] three.js/examples/jsm/math/OBB.js at master · mrdoob/three.js (github.com)

[5] BufferGeometry.boundingBox的應用:BoxHelper的實現 - 掘金 (juejin.cn)

[6] 113 Three.js的obb (OrientedboundingBox)方向包圍盒的使用_暮志未晚Webgl的部落格-CSDN部落格

相關文章