Browse Source

Examples: Introduce basic OBB implementation.

Mugen87 5 years ago
parent
commit
6f47e8fbb8

+ 17 - 0
docs/api/en/math/Matrix3.html

@@ -80,6 +80,23 @@ m.elements = [ 11, 21, 31,
 		<h3>[method:Boolean equals]( [param:Matrix3 m] )</h3>
 		<p>Return true if this matrix and [page:Matrix3 m] are equal.</p>
 
+		<h3>[method:this extractBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [param:Vector3 zAxis] )</h3>
+		<p>
+		Extracts the [link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) basis] of this
+		matrix into the three axis vectors provided. If this matrix is:
+		<code>
+a, b, c,
+d, e, f,
+g, h, i
+		</code>
+		then the [page:Vector3 xAxis], [page:Vector3 yAxis], [page:Vector3 zAxis] will be set to:
+		<code>
+xAxis = (a, d, g)
+yAxis = (b, e, h)
+zAxis = (c, f, i)
+		</code>
+		</p>
+
 		<h3>[method:this fromArray]( [param:Array array], [param:Integer offset] )</h3>
 		<p>
 		[page:Array array] - the array to read the elements from.<br />

+ 17 - 0
docs/api/zh/math/Matrix3.html

@@ -76,6 +76,23 @@ m.elements = [ 11, 21, 31,
 		<h3>[method:Boolean equals]( [param:Matrix3 m] )</h3>
 		<p>如果矩阵[page:Matrix3 m] 与当前矩阵所有对应元素相同则返回true。</p>
 
+		<h3>[method:this extractBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [param:Vector3 zAxis] )</h3>
+		<p>
+		Extracts the [link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) basis] of this
+		matrix into the three axis vectors provided. If this matrix is:
+		<code>
+a, b, c,
+d, e, f,
+g, h, i
+		</code>
+		then the [page:Vector3 xAxis], [page:Vector3 yAxis], [page:Vector3 zAxis] will be set to:
+		<code>
+xAxis = (a, d, g)
+yAxis = (b, e, h)
+zAxis = (c, f, i)
+		</code>
+		</p>
+
 		<h3>[method:this fromArray]( [param:Array array], [param:Integer offset] )</h3>
 		<p>
 		[page:Array array] - 用来存储设置元素数据的数组<br />

+ 1 - 0
examples/files.js

@@ -174,6 +174,7 @@ var files = {
 		"webgl_materials_video",
 		"webgl_materials_video_webcam",
 		"webgl_materials_wireframe",
+		"webgl_math_obb",
 		"webgl_math_orientation_transform",
 		"webgl_mirror",
 		"webgl_modifier_simplifier",

+ 367 - 0
examples/js/math/OBB.js

@@ -0,0 +1,367 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ */
+
+THREE.OBB = ( function () {
+
+	var a = {
+		c: null, // center
+		u: [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ], // basis vectors
+		e: [] // half width
+	};
+
+	var b = {
+		c: null, // center
+		u: [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ], // basis vectors
+		e: [] // half width
+	};
+
+	var R = [[], [], []];
+	var AbsR = [[], [], []];
+	var t = [];
+
+	var xAxis = new THREE.Vector3();
+	var yAxis = new THREE.Vector3();
+	var zAxis = new THREE.Vector3();
+	var v1 = new THREE.Vector3();
+	var closestPoint = new THREE.Vector3();
+	var rotationMatrix = new THREE.Matrix3();
+
+	//
+
+	function OBB( center = new THREE.Vector3(), halfSize = new THREE.Vector3(), rotation = new THREE.Matrix3() ) {
+
+		this.center = center;
+		this.halfSize = halfSize;
+		this.rotation = rotation;
+
+	}
+
+	Object.assign( OBB.prototype, {
+
+		set: function ( center, halfSize, rotation ) {
+
+			this.center = center;
+			this.halfSize = halfSize;
+			this.rotation = rotation;
+
+			return this;
+
+		},
+
+		copy: function ( obb ) {
+
+			this.center.copy( obb.center );
+			this.halfSize.copy( obb.halfSize );
+			this.rotation.copy( obb.rotation );
+
+			return this;
+
+		},
+
+		clone: function () {
+
+			return new this.constructor().copy( this );
+
+		},
+
+		getSize: function ( result ) {
+
+			return result.copy( this.halfSize ).multiplyScalar( 2 );
+
+		},
+
+		/**
+		* Reference: Closest Point on OBB to Point in Real-Time Collision Detection
+		* by Christer Ericson (chapter 5.1.4)
+		*/
+		clampPoint: function ( point, result ) {
+
+			var halfSize = this.halfSize;
+
+			v1.subVectors( point, this.center );
+			this.rotation.extractBasis( xAxis, yAxis, zAxis );
+
+			// start at the center position of the OBB
+
+			result.copy( this.center );
+
+			// project the target onto the OBB axes and walk towards that point
+
+			var x = THREE.MathUtils.clamp( v1.dot( xAxis ), - halfSize.x, halfSize.x );
+			result.add( xAxis.multiplyScalar( x ) );
+
+			var y = THREE.MathUtils.clamp( v1.dot( yAxis ), - halfSize.y, halfSize.y );
+			result.add( yAxis.multiplyScalar( y ) );
+
+			var z = THREE.MathUtils.clamp( v1.dot( zAxis ), - halfSize.z, halfSize.z );
+			result.add( zAxis.multiplyScalar( z ) );
+
+			return result;
+
+		},
+
+		containsPoint: function ( point ) {
+
+			v1.subVectors( point, this.center );
+			this.rotation.extractBasis( xAxis, yAxis, zAxis );
+
+			// project v1 onto each axis and check if these points lie inside the OBB
+
+			return Math.abs( v1.dot( xAxis ) ) <= this.halfSize.x &&
+					Math.abs( v1.dot( yAxis ) ) <= this.halfSize.y &&
+					Math.abs( v1.dot( zAxis ) ) <= this.halfSize.z;
+
+		},
+
+		intersectsBox3: function ( box3 ) {
+
+			return this.intersectsOBB( obb.fromBox3( box3 ) );
+
+		},
+
+		intersectsSphere: function ( sphere ) {
+
+			// find the point on the OBB closest to the sphere center
+
+			this.clampPoint( sphere.center, closestPoint );
+
+			// if that point is inside the sphere, the OBB and sphere intersect
+
+			return closestPoint.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );
+
+		},
+
+		/**
+		* Reference: OBB-OBB Intersection in Real-Time Collision Detection
+		* by Christer Ericson (chapter 4.4.1)
+		*
+		*/
+		intersectsOBB: function ( obb, epsilon = Number.EPSILON ) {
+
+			// prepare data structures (the code uses the same nomenclature like the reference)
+
+			a.c = this.center;
+			a.e[ 0 ] = this.halfSize.x;
+			a.e[ 1 ] = this.halfSize.y;
+			a.e[ 2 ] = this.halfSize.z;
+			this.rotation.extractBasis( a.u[ 0 ], a.u[ 1 ], a.u[ 2 ] );
+
+			b.c = obb.center;
+			b.e[ 0 ] = obb.halfSize.x;
+			b.e[ 1 ] = obb.halfSize.y;
+			b.e[ 2 ] = obb.halfSize.z;
+			obb.rotation.extractBasis( b.u[ 0 ], b.u[ 1 ], b.u[ 2 ] );
+
+			// compute rotation matrix expressing b in a's coordinate frame
+
+			for ( var i = 0; i < 3; i ++ ) {
+
+				for ( var j = 0; j < 3; j ++ ) {
+
+					R[ i ][ j ] = a.u[ i ].dot( b.u[ j ] );
+
+				}
+
+			}
+
+			// compute translation vector
+
+			v1.subVectors( b.c, a.c );
+
+			// bring translation into a's coordinate frame
+
+			t[ 0 ] = v1.dot( a.u[ 0 ] );
+			t[ 1 ] = v1.dot( a.u[ 1 ] );
+			t[ 2 ] = v1.dot( a.u[ 2 ] );
+
+			// compute common subexpressions. Add in an epsilon term to
+			// counteract arithmetic errors when two edges are parallel and
+			// their cross product is (near) null
+
+			for ( var i = 0; i < 3; i ++ ) {
+
+				for ( var j = 0; j < 3; j ++ ) {
+
+					AbsR[ i ][ j ] = Math.abs( R[ i ][ j ] ) + epsilon;
+
+				}
+
+			}
+
+			var ra, rb;
+
+			// test axes L = A0, L = A1, L = A2
+
+			for ( var i = 0; i < 3; i ++ ) {
+
+				ra = a.e[ i ];
+				rb = b.e[ 0 ] * AbsR[ i ][ 0 ] + b.e[ 1 ] * AbsR[ i ][ 1 ] + b.e[ 2 ] * AbsR[ i ][ 2 ];
+				if ( Math.abs( t[ i ] ) > ra + rb ) return false;
+
+
+			}
+
+			// test axes L = B0, L = B1, L = B2
+
+			for ( var i = 0; i < 3; i ++ ) {
+
+				ra = a.e[ 0 ] * AbsR[ 0 ][ i ] + a.e[ 1 ] * AbsR[ 1 ][ i ] + a.e[ 2 ] * AbsR[ 2 ][ i ];
+				rb = b.e[ i ];
+				if ( Math.abs( t[ 0 ] * R[ 0 ][ i ] + t[ 1 ] * R[ 1 ][ i ] + t[ 2 ] * R[ 2 ][ i ] ) > ra + rb ) return false;
+
+			}
+
+			// test axis L = A0 x B0
+
+			ra = a.e[ 1 ] * AbsR[ 2 ][ 0 ] + a.e[ 2 ] * AbsR[ 1 ][ 0 ];
+			rb = b.e[ 1 ] * AbsR[ 0 ][ 2 ] + b.e[ 2 ] * AbsR[ 0 ][ 1 ];
+			if ( Math.abs( t[ 2 ] * R[ 1 ][ 0 ] - t[ 1 ] * R[ 2 ][ 0 ] ) > ra + rb ) return false;
+
+			// test axis L = A0 x B1
+
+			ra = a.e[ 1 ] * AbsR[ 2 ][ 1 ] + a.e[ 2 ] * AbsR[ 1 ][ 1 ];
+			rb = b.e[ 0 ] * AbsR[ 0 ][ 2 ] + b.e[ 2 ] * AbsR[ 0 ][ 0 ];
+			if ( Math.abs( t[ 2 ] * R[ 1 ][ 1 ] - t[ 1 ] * R[ 2 ][ 1 ] ) > ra + rb ) return false;
+
+			// test axis L = A0 x B2
+
+			ra = a.e[ 1 ] * AbsR[ 2 ][ 2 ] + a.e[ 2 ] * AbsR[ 1 ][ 2 ];
+			rb = b.e[ 0 ] * AbsR[ 0 ][ 1 ] + b.e[ 1 ] * AbsR[ 0 ][ 0 ];
+			if ( Math.abs( t[ 2 ] * R[ 1 ][ 2 ] - t[ 1 ] * R[ 2 ][ 2 ] ) > ra + rb ) return false;
+
+			// test axis L = A1 x B0
+
+			ra = a.e[ 0 ] * AbsR[ 2 ][ 0 ] + a.e[ 2 ] * AbsR[ 0 ][ 0 ];
+			rb = b.e[ 1 ] * AbsR[ 1 ][ 2 ] + b.e[ 2 ] * AbsR[ 1 ][ 1 ];
+			if ( Math.abs( t[ 0 ] * R[ 2 ][ 0 ] - t[ 2 ] * R[ 0 ][ 0 ] ) > ra + rb ) return false;
+
+			// test axis L = A1 x B1
+
+			ra = a.e[ 0 ] * AbsR[ 2 ][ 1 ] + a.e[ 2 ] * AbsR[ 0 ][ 1 ];
+			rb = b.e[ 0 ] * AbsR[ 1 ][ 2 ] + b.e[ 2 ] * AbsR[ 1 ][ 0 ];
+			if ( Math.abs( t[ 0 ] * R[ 2 ][ 1 ] - t[ 2 ] * R[ 0 ][ 1 ] ) > ra + rb ) return false;
+
+			// test axis L = A1 x B2
+
+			ra = a.e[ 0 ] * AbsR[ 2 ][ 2 ] + a.e[ 2 ] * AbsR[ 0 ][ 2 ];
+			rb = b.e[ 0 ] * AbsR[ 1 ][ 1 ] + b.e[ 1 ] * AbsR[ 1 ][ 0 ];
+			if ( Math.abs( t[ 0 ] * R[ 2 ][ 2 ] - t[ 2 ] * R[ 0 ][ 2 ] ) > ra + rb ) return false;
+
+			// test axis L = A2 x B0
+
+			ra = a.e[ 0 ] * AbsR[ 1 ][ 0 ] + a.e[ 1 ] * AbsR[ 0 ][ 0 ];
+			rb = b.e[ 1 ] * AbsR[ 2 ][ 2 ] + b.e[ 2 ] * AbsR[ 2 ][ 1 ];
+			if ( Math.abs( t[ 1 ] * R[ 0 ][ 0 ] - t[ 0 ] * R[ 1 ][ 0 ] ) > ra + rb ) return false;
+
+			// test axis L = A2 x B1
+
+			ra = a.e[ 0 ] * AbsR[ 1 ][ 1 ] + a.e[ 1 ] * AbsR[ 0 ][ 1 ];
+			rb = b.e[ 0 ] * AbsR[ 2 ][ 2 ] + b.e[ 2 ] * AbsR[ 2 ][ 0 ];
+			if ( Math.abs( t[ 1 ] * R[ 0 ][ 1 ] - t[ 0 ] * R[ 1 ][ 1 ] ) > ra + rb ) return false;
+
+			// test axis L = A2 x B2
+
+			ra = a.e[ 0 ] * AbsR[ 1 ][ 2 ] + a.e[ 1 ] * AbsR[ 0 ][ 2 ];
+			rb = b.e[ 0 ] * AbsR[ 2 ][ 1 ] + b.e[ 1 ] * AbsR[ 2 ][ 0 ];
+			if ( Math.abs( t[ 1 ] * R[ 0 ][ 2 ] - t[ 0 ] * R[ 1 ][ 2 ] ) > ra + rb ) return false;
+
+			// since no separating axis is found, the OBBs must be intersecting
+
+			return true;
+
+		},
+
+		/**
+		* Reference: Testing Box Against Plane in Real-Time Collision Detection
+		* by Christer Ericson (chapter 5.2.3)
+		*/
+		intersectsPlane: function ( plane ) {
+
+			this.rotation.extractBasis( xAxis, yAxis, zAxis );
+
+			// compute the projection interval radius of this OBB onto L(t) = this->center + t * p.normal;
+
+			const r = this.halfSize.x * Math.abs( plane.normal.dot( xAxis ) ) +
+					this.halfSize.y * Math.abs( plane.normal.dot( yAxis ) ) +
+					this.halfSize.z * Math.abs( plane.normal.dot( zAxis ) );
+
+			// compute distance of the OBB's center from the plane
+
+			const d = plane.normal.dot( this.center ) - plane.constant;
+
+			// Intersection occurs when distance d falls within [-r,+r] interval
+
+			return Math.abs( d ) <= r;
+
+		},
+
+		fromBox3: function ( box3 ) {
+
+			box3.getCenter( this.center );
+
+			box3.getSize( this.halfSize ).multiplyScalar( 0.5 );
+
+			this.rotation.identity();
+
+			return this;
+
+		},
+
+		equals: function ( obb ) {
+
+			return obb.center.equals( this.center ) &&
+				obb.halfSize.equals( this.halfSize ) &&
+				obb.rotation.equals( this.rotation );
+
+		},
+
+		applyMatrix4: function ( matrix ) {
+
+			var e = matrix.elements;
+
+			var sx = v1.set( e[ 0 ], e[ 1 ], e[ 2 ] ).length();
+			var sy = v1.set( e[ 4 ], e[ 5 ], e[ 6 ] ).length();
+			var sz = v1.set( e[ 8 ], e[ 9 ], e[ 10 ] ).length();
+
+			var det = matrix.determinant();
+			if ( det < 0 ) sx = - sx;
+
+			rotationMatrix.setFromMatrix4( matrix );
+
+			var invSX = 1 / sx;
+			var invSY = 1 / sy;
+			var invSZ = 1 / sz;
+
+			rotationMatrix.elements[ 0 ] *= invSX;
+			rotationMatrix.elements[ 1 ] *= invSX;
+			rotationMatrix.elements[ 2 ] *= invSX;
+
+			rotationMatrix.elements[ 3 ] *= invSY;
+			rotationMatrix.elements[ 4 ] *= invSY;
+			rotationMatrix.elements[ 5 ] *= invSY;
+
+			rotationMatrix.elements[ 6 ] *= invSZ;
+			rotationMatrix.elements[ 7 ] *= invSZ;
+			rotationMatrix.elements[ 8 ] *= invSZ;
+
+			this.rotation.multiply( rotationMatrix );
+
+			this.halfSize.x *= sx;
+			this.halfSize.y *= sy;
+			this.halfSize.z *= sz;
+
+			v1.setFromMatrixPosition( matrix );
+			this.center.add( v1 );
+
+			return this;
+
+		}
+
+	} );
+
+	var obb = new OBB();
+
+	return OBB;
+
+} )();

+ 31 - 0
examples/jsm/math/OBB.d.ts

@@ -0,0 +1,31 @@
+import {
+	Box3,
+	Matrix3,
+	Matrix4,
+	Plane,
+	Sphere,
+	Vector3
+} from '../../../src/Three';
+
+export class OBB {
+
+	center: Vector3;
+	halfSize: Vector3;
+	rotation: Matrix3;
+
+	constructor( center: Vector3, halfSize: Vector3, rotation: Matrix3 );
+	set( center: Vector3, halfSize: Vector3, rotation: Matrix3 ): this;
+	copy( obb: OBB ): this;
+	clone(): OBB;
+	getSize( result: Vector3 ): Vector3;
+	clampPoint( point: Vector3, result: Vector3 ): Vector3;
+	containsPoint( point: Vector3 ): boolean;
+	intersectsBox3( box3: Box3 ): boolean;
+	intersectsSphere( sphere: Sphere ): boolean;
+	intersectsOBB( obb: OBB, epsilon: number ): boolean;
+	intersectsPlane( plane: Plane ): boolean;
+	fromBox3( box3: Box3 ): this;
+	equals( obb: OBB ): boolean;
+	applyMatrix4( matrix: Matrix4 ): this;
+
+}

+ 375 - 0
examples/jsm/math/OBB.js

@@ -0,0 +1,375 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ */
+
+import {
+	MathUtils,
+	Matrix3,
+	Vector3
+} from "../../../build/three.module.js";
+
+var OBB = ( function () {
+
+	var a = {
+		c: null, // center
+		u: [ new Vector3(), new Vector3(), new Vector3() ], // basis vectors
+		e: [] // half width
+	};
+
+	var b = {
+		c: null, // center
+		u: [ new Vector3(), new Vector3(), new Vector3() ], // basis vectors
+		e: [] // half width
+	};
+
+	var R = [[], [], []];
+	var AbsR = [[], [], []];
+	var t = [];
+
+	var xAxis = new Vector3();
+	var yAxis = new Vector3();
+	var zAxis = new Vector3();
+	var v1 = new Vector3();
+	var closestPoint = new Vector3();
+	var rotationMatrix = new Matrix3();
+
+	//
+
+	function OBB( center = new Vector3(), halfSize = new Vector3(), rotation = new Matrix3() ) {
+
+		this.center = center;
+		this.halfSize = halfSize;
+		this.rotation = rotation;
+
+	}
+
+	Object.assign( OBB.prototype, {
+
+		set: function ( center, halfSize, rotation ) {
+
+			this.center = center;
+			this.halfSize = halfSize;
+			this.rotation = rotation;
+
+			return this;
+
+		},
+
+		copy: function ( obb ) {
+
+			this.center.copy( obb.center );
+			this.halfSize.copy( obb.halfSize );
+			this.rotation.copy( obb.rotation );
+
+			return this;
+
+		},
+
+		clone: function () {
+
+			return new this.constructor().copy( this );
+
+		},
+
+		getSize: function ( result ) {
+
+			return result.copy( this.halfSize ).multiplyScalar( 2 );
+
+		},
+
+		/**
+		* Reference: Closest Point on OBB to Point in Real-Time Collision Detection
+		* by Christer Ericson (chapter 5.1.4)
+		*/
+		clampPoint: function ( point, result ) {
+
+			var halfSize = this.halfSize;
+
+			v1.subVectors( point, this.center );
+			this.rotation.extractBasis( xAxis, yAxis, zAxis );
+
+			// start at the center position of the OBB
+
+			result.copy( this.center );
+
+			// project the target onto the OBB axes and walk towards that point
+
+			var x = MathUtils.clamp( v1.dot( xAxis ), - halfSize.x, halfSize.x );
+			result.add( xAxis.multiplyScalar( x ) );
+
+			var y = MathUtils.clamp( v1.dot( yAxis ), - halfSize.y, halfSize.y );
+			result.add( yAxis.multiplyScalar( y ) );
+
+			var z = MathUtils.clamp( v1.dot( zAxis ), - halfSize.z, halfSize.z );
+			result.add( zAxis.multiplyScalar( z ) );
+
+			return result;
+
+		},
+
+		containsPoint: function ( point ) {
+
+			v1.subVectors( point, this.center );
+			this.rotation.extractBasis( xAxis, yAxis, zAxis );
+
+			// project v1 onto each axis and check if these points lie inside the OBB
+
+			return Math.abs( v1.dot( xAxis ) ) <= this.halfSize.x &&
+					Math.abs( v1.dot( yAxis ) ) <= this.halfSize.y &&
+					Math.abs( v1.dot( zAxis ) ) <= this.halfSize.z;
+
+		},
+
+		intersectsBox3: function ( box3 ) {
+
+			return this.intersectsOBB( obb.fromBox3( box3 ) );
+
+		},
+
+		intersectsSphere: function ( sphere ) {
+
+			// find the point on the OBB closest to the sphere center
+
+			this.clampPoint( sphere.center, closestPoint );
+
+			// if that point is inside the sphere, the OBB and sphere intersect
+
+			return closestPoint.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );
+
+		},
+
+		/**
+		* Reference: OBB-OBB Intersection in Real-Time Collision Detection
+		* by Christer Ericson (chapter 4.4.1)
+		*
+		*/
+		intersectsOBB: function ( obb, epsilon = Number.EPSILON ) {
+
+			// prepare data structures (the code uses the same nomenclature like the reference)
+
+			a.c = this.center;
+			a.e[ 0 ] = this.halfSize.x;
+			a.e[ 1 ] = this.halfSize.y;
+			a.e[ 2 ] = this.halfSize.z;
+			this.rotation.extractBasis( a.u[ 0 ], a.u[ 1 ], a.u[ 2 ] );
+
+			b.c = obb.center;
+			b.e[ 0 ] = obb.halfSize.x;
+			b.e[ 1 ] = obb.halfSize.y;
+			b.e[ 2 ] = obb.halfSize.z;
+			obb.rotation.extractBasis( b.u[ 0 ], b.u[ 1 ], b.u[ 2 ] );
+
+			// compute rotation matrix expressing b in a's coordinate frame
+
+			for ( var i = 0; i < 3; i ++ ) {
+
+				for ( var j = 0; j < 3; j ++ ) {
+
+					R[ i ][ j ] = a.u[ i ].dot( b.u[ j ] );
+
+				}
+
+			}
+
+			// compute translation vector
+
+			v1.subVectors( b.c, a.c );
+
+			// bring translation into a's coordinate frame
+
+			t[ 0 ] = v1.dot( a.u[ 0 ] );
+			t[ 1 ] = v1.dot( a.u[ 1 ] );
+			t[ 2 ] = v1.dot( a.u[ 2 ] );
+
+			// compute common subexpressions. Add in an epsilon term to
+			// counteract arithmetic errors when two edges are parallel and
+			// their cross product is (near) null
+
+			for ( var i = 0; i < 3; i ++ ) {
+
+				for ( var j = 0; j < 3; j ++ ) {
+
+					AbsR[ i ][ j ] = Math.abs( R[ i ][ j ] ) + epsilon;
+
+				}
+
+			}
+
+			var ra, rb;
+
+			// test axes L = A0, L = A1, L = A2
+
+			for ( var i = 0; i < 3; i ++ ) {
+
+				ra = a.e[ i ];
+				rb = b.e[ 0 ] * AbsR[ i ][ 0 ] + b.e[ 1 ] * AbsR[ i ][ 1 ] + b.e[ 2 ] * AbsR[ i ][ 2 ];
+				if ( Math.abs( t[ i ] ) > ra + rb ) return false;
+
+
+			}
+
+			// test axes L = B0, L = B1, L = B2
+
+			for ( var i = 0; i < 3; i ++ ) {
+
+				ra = a.e[ 0 ] * AbsR[ 0 ][ i ] + a.e[ 1 ] * AbsR[ 1 ][ i ] + a.e[ 2 ] * AbsR[ 2 ][ i ];
+				rb = b.e[ i ];
+				if ( Math.abs( t[ 0 ] * R[ 0 ][ i ] + t[ 1 ] * R[ 1 ][ i ] + t[ 2 ] * R[ 2 ][ i ] ) > ra + rb ) return false;
+
+			}
+
+			// test axis L = A0 x B0
+
+			ra = a.e[ 1 ] * AbsR[ 2 ][ 0 ] + a.e[ 2 ] * AbsR[ 1 ][ 0 ];
+			rb = b.e[ 1 ] * AbsR[ 0 ][ 2 ] + b.e[ 2 ] * AbsR[ 0 ][ 1 ];
+			if ( Math.abs( t[ 2 ] * R[ 1 ][ 0 ] - t[ 1 ] * R[ 2 ][ 0 ] ) > ra + rb ) return false;
+
+			// test axis L = A0 x B1
+
+			ra = a.e[ 1 ] * AbsR[ 2 ][ 1 ] + a.e[ 2 ] * AbsR[ 1 ][ 1 ];
+			rb = b.e[ 0 ] * AbsR[ 0 ][ 2 ] + b.e[ 2 ] * AbsR[ 0 ][ 0 ];
+			if ( Math.abs( t[ 2 ] * R[ 1 ][ 1 ] - t[ 1 ] * R[ 2 ][ 1 ] ) > ra + rb ) return false;
+
+			// test axis L = A0 x B2
+
+			ra = a.e[ 1 ] * AbsR[ 2 ][ 2 ] + a.e[ 2 ] * AbsR[ 1 ][ 2 ];
+			rb = b.e[ 0 ] * AbsR[ 0 ][ 1 ] + b.e[ 1 ] * AbsR[ 0 ][ 0 ];
+			if ( Math.abs( t[ 2 ] * R[ 1 ][ 2 ] - t[ 1 ] * R[ 2 ][ 2 ] ) > ra + rb ) return false;
+
+			// test axis L = A1 x B0
+
+			ra = a.e[ 0 ] * AbsR[ 2 ][ 0 ] + a.e[ 2 ] * AbsR[ 0 ][ 0 ];
+			rb = b.e[ 1 ] * AbsR[ 1 ][ 2 ] + b.e[ 2 ] * AbsR[ 1 ][ 1 ];
+			if ( Math.abs( t[ 0 ] * R[ 2 ][ 0 ] - t[ 2 ] * R[ 0 ][ 0 ] ) > ra + rb ) return false;
+
+			// test axis L = A1 x B1
+
+			ra = a.e[ 0 ] * AbsR[ 2 ][ 1 ] + a.e[ 2 ] * AbsR[ 0 ][ 1 ];
+			rb = b.e[ 0 ] * AbsR[ 1 ][ 2 ] + b.e[ 2 ] * AbsR[ 1 ][ 0 ];
+			if ( Math.abs( t[ 0 ] * R[ 2 ][ 1 ] - t[ 2 ] * R[ 0 ][ 1 ] ) > ra + rb ) return false;
+
+			// test axis L = A1 x B2
+
+			ra = a.e[ 0 ] * AbsR[ 2 ][ 2 ] + a.e[ 2 ] * AbsR[ 0 ][ 2 ];
+			rb = b.e[ 0 ] * AbsR[ 1 ][ 1 ] + b.e[ 1 ] * AbsR[ 1 ][ 0 ];
+			if ( Math.abs( t[ 0 ] * R[ 2 ][ 2 ] - t[ 2 ] * R[ 0 ][ 2 ] ) > ra + rb ) return false;
+
+			// test axis L = A2 x B0
+
+			ra = a.e[ 0 ] * AbsR[ 1 ][ 0 ] + a.e[ 1 ] * AbsR[ 0 ][ 0 ];
+			rb = b.e[ 1 ] * AbsR[ 2 ][ 2 ] + b.e[ 2 ] * AbsR[ 2 ][ 1 ];
+			if ( Math.abs( t[ 1 ] * R[ 0 ][ 0 ] - t[ 0 ] * R[ 1 ][ 0 ] ) > ra + rb ) return false;
+
+			// test axis L = A2 x B1
+
+			ra = a.e[ 0 ] * AbsR[ 1 ][ 1 ] + a.e[ 1 ] * AbsR[ 0 ][ 1 ];
+			rb = b.e[ 0 ] * AbsR[ 2 ][ 2 ] + b.e[ 2 ] * AbsR[ 2 ][ 0 ];
+			if ( Math.abs( t[ 1 ] * R[ 0 ][ 1 ] - t[ 0 ] * R[ 1 ][ 1 ] ) > ra + rb ) return false;
+
+			// test axis L = A2 x B2
+
+			ra = a.e[ 0 ] * AbsR[ 1 ][ 2 ] + a.e[ 1 ] * AbsR[ 0 ][ 2 ];
+			rb = b.e[ 0 ] * AbsR[ 2 ][ 1 ] + b.e[ 1 ] * AbsR[ 2 ][ 0 ];
+			if ( Math.abs( t[ 1 ] * R[ 0 ][ 2 ] - t[ 0 ] * R[ 1 ][ 2 ] ) > ra + rb ) return false;
+
+			// since no separating axis is found, the OBBs must be intersecting
+
+			return true;
+
+		},
+
+		/**
+		* Reference: Testing Box Against Plane in Real-Time Collision Detection
+		* by Christer Ericson (chapter 5.2.3)
+		*/
+		intersectsPlane: function ( plane ) {
+
+			this.rotation.extractBasis( xAxis, yAxis, zAxis );
+
+			// compute the projection interval radius of this OBB onto L(t) = this->center + t * p.normal;
+
+			const r = this.halfSize.x * Math.abs( plane.normal.dot( xAxis ) ) +
+					this.halfSize.y * Math.abs( plane.normal.dot( yAxis ) ) +
+					this.halfSize.z * Math.abs( plane.normal.dot( zAxis ) );
+
+			// compute distance of the OBB's center from the plane
+
+			const d = plane.normal.dot( this.center ) - plane.constant;
+
+			// Intersection occurs when distance d falls within [-r,+r] interval
+
+			return Math.abs( d ) <= r;
+
+		},
+
+		fromBox3: function ( box3 ) {
+
+			box3.getCenter( this.center );
+
+			box3.getSize( this.halfSize ).multiplyScalar( 0.5 );
+
+			this.rotation.identity();
+
+			return this;
+
+		},
+
+		equals: function ( obb ) {
+
+			return obb.center.equals( this.center ) &&
+				obb.halfSize.equals( this.halfSize ) &&
+				obb.rotation.equals( this.rotation );
+
+		},
+
+		applyMatrix4: function ( matrix ) {
+
+			var e = matrix.elements;
+
+			var sx = v1.set( e[ 0 ], e[ 1 ], e[ 2 ] ).length();
+			var sy = v1.set( e[ 4 ], e[ 5 ], e[ 6 ] ).length();
+			var sz = v1.set( e[ 8 ], e[ 9 ], e[ 10 ] ).length();
+
+			var det = matrix.determinant();
+			if ( det < 0 ) sx = - sx;
+
+			rotationMatrix.setFromMatrix4( matrix );
+
+			var invSX = 1 / sx;
+			var invSY = 1 / sy;
+			var invSZ = 1 / sz;
+
+			rotationMatrix.elements[ 0 ] *= invSX;
+			rotationMatrix.elements[ 1 ] *= invSX;
+			rotationMatrix.elements[ 2 ] *= invSX;
+
+			rotationMatrix.elements[ 3 ] *= invSY;
+			rotationMatrix.elements[ 4 ] *= invSY;
+			rotationMatrix.elements[ 5 ] *= invSY;
+
+			rotationMatrix.elements[ 6 ] *= invSZ;
+			rotationMatrix.elements[ 7 ] *= invSZ;
+			rotationMatrix.elements[ 8 ] *= invSZ;
+
+			this.rotation.multiply( rotationMatrix );
+
+			this.halfSize.x *= sx;
+			this.halfSize.y *= sy;
+			this.halfSize.z *= sz;
+
+			v1.setFromMatrixPosition( matrix );
+			this.center.add( v1 );
+
+			return this;
+
+		}
+
+	} );
+
+	var obb = new OBB();
+
+	return OBB;
+
+} )();
+
+export { OBB };

+ 187 - 0
examples/webgl_math_obb.html

@@ -0,0 +1,187 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - math - OBB</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+		<style>
+			body {
+				color: #444;
+			}
+			a {
+				color: #444;
+			}
+		</style>
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener noreferrer">three.js</a> - OBB (Oriented Bounding Box)
+		</div>
+
+		<script type="module">
+
+			import * as THREE from '../build/three.module.js';
+
+			import { OBB } from './jsm/math/OBB.js';
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+
+			import Stats from './jsm/libs/stats.module.js';
+
+			var camera, scene, renderer, clock, controls, stats;
+
+			var objects = [];
+
+			init();
+			animate();
+
+			function init() {
+
+				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.set( 0, 0, 75 );
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0xffffff );
+
+				clock = new THREE.Clock();
+
+				var hemiLight = new THREE.HemisphereLight( 0xffffff, 0x222222, 1.5 );
+				hemiLight.position.set( 1, 1, 1 );
+				scene.add( hemiLight );
+
+				var size = new THREE.Vector3( 10, 5, 6 );
+				var geometry = new THREE.BoxBufferGeometry( size.x, size.y, size.z );
+
+				// setup OBB on geometry level (doing this manually for now)
+
+				geometry.userData.obb = new OBB();
+				geometry.userData.obb.halfSize.copy( size ).multiplyScalar( 0.5 );
+
+				for ( var i = 0; i < 100; i ++ ) {
+
+					var object = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: 0x00ff00 } ) );
+					object.matrixAutoUpdate = false;
+
+					object.position.x = Math.random() * 80 - 40;
+					object.position.y = Math.random() * 80 - 40;
+					object.position.z = Math.random() * 80 - 40;
+
+					object.rotation.x = Math.random() * 2 * Math.PI;
+					object.rotation.y = Math.random() * 2 * Math.PI;
+					object.rotation.z = Math.random() * 2 * Math.PI;
+
+					object.scale.x = Math.random() + 0.5;
+					object.scale.y = Math.random() + 0.5;
+					object.scale.z = Math.random() + 0.5;
+
+					scene.add( object );
+
+					// bounding volume on object level (this will reflect the current world transform)
+
+					object.userData.obb = new OBB();
+
+					objects.push( object );
+
+				}
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				//
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
+
+				//
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				controls.update();
+
+				// transform cubes
+
+				var delta = clock.getDelta();
+
+				for ( var i = 0, il = objects.length; i < il; i ++ ) {
+
+					var object = objects[ i ];
+
+					object.rotation.x += delta * Math.PI * 0.20;
+					object.rotation.y += delta * Math.PI * 0.1;
+
+					object.updateMatrix();
+					object.updateMatrixWorld();
+
+					// update OBB
+
+					object.userData.obb.copy( object.geometry.userData.obb );
+					object.userData.obb.applyMatrix4( object.matrixWorld );
+
+					// reset
+
+					object.material.color.setHex( 0x00ff00 );
+
+				}
+
+				// collision detection
+
+				for ( var i = 0, il = objects.length; i < il; i ++ ) {
+
+					var object = objects[ i ];
+					var obb = object.userData.obb;
+
+					for ( var j = i + 1, jl = objects.length; j < jl; j ++ ) {
+
+						var objectToTest = objects[ j ];
+						var obbToTest = objectToTest.userData.obb;
+
+						// now perform intersection test
+
+						if ( obb.intersectsOBB( obbToTest ) === true ) {
+
+							object.material.color.setHex( 0xff0000 );
+							objectToTest.material.color.setHex( 0xff0000 );
+
+						}
+
+					}
+
+				}
+
+				renderer.render( scene, camera );
+
+				stats.update();
+
+			}
+
+	</script>
+
+</body>
+</html>

+ 2 - 0
src/math/Matrix3.d.ts

@@ -1,4 +1,5 @@
 import { Matrix4 } from './Matrix4';
+import { Quaternion } from './Quaternion';
 import { Vector3 } from './Vector3';
 
 /**
@@ -72,6 +73,7 @@ export class Matrix3 implements Matrix {
 	identity(): Matrix3;
 	clone(): this;
 	copy( m: Matrix3 ): this;
+	extractBasis( xAxis: Vector3, yAxis: Vector3, zAxis: Vector3 ): Matrix3;
 	setFromMatrix4( m: Matrix4 ): Matrix3;
 	multiplyScalar( s: number ): Matrix3;
 	determinant(): number;

+ 12 - 0
src/math/Matrix3.js

@@ -72,6 +72,18 @@ Object.assign( Matrix3.prototype, {
 
 	},
 
+	extractBasis: function ( xAxis, yAxis, zAxis ) {
+
+		var te = this.elements;
+
+		xAxis.fromArray( te, 0 );
+		yAxis.fromArray( te, 3 );
+		zAxis.fromArray( te, 6 );
+
+		return this;
+
+	},
+
 	setFromMatrix4: function ( m ) {
 
 		var me = m.elements;

+ 1 - 0
utils/modularize.js

@@ -111,6 +111,7 @@ var files = [
 	{ path: 'math/ConvexHull.js', dependencies: [], ignoreList: [] },
 	{ path: 'math/ImprovedNoise.js', dependencies: [], ignoreList: [] },
 	{ path: 'math/Lut.js', dependencies: [], ignoreList: [] },
+	{ path: 'math/OBB.js', dependencies: [], ignoreList: [] },
 	{ path: 'math/SimplexNoise.js', dependencies: [], ignoreList: [] },
 
 	{ path: 'misc/ConvexObjectBreaker.js', dependencies: [ { name: 'ConvexBufferGeometry', path: 'geometries/ConvexGeometry.js' } ], ignoreList: [ 'Matrix4' ] },