( function () {

	const a = {
		c: null,
		// center
		u: [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ],
		// basis vectors
		e: [] // half width

	};
	const b = {
		c: null,
		// center
		u: [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ],
		// basis vectors
		e: [] // half width

	};
	const R = [[], [], []];
	const AbsR = [[], [], []];
	const t = [];
	const xAxis = new THREE.Vector3();
	const yAxis = new THREE.Vector3();
	const zAxis = new THREE.Vector3();
	const v1 = new THREE.Vector3();
	const size = new THREE.Vector3();
	const closestPoint = new THREE.Vector3();
	const rotationMatrix = new THREE.Matrix3();
	const aabb = new THREE.Box3();
	const matrix = new THREE.Matrix4();
	const inverse = new THREE.Matrix4();
	const localRay = new THREE.Ray(); // OBB

	class OBB {

		constructor( center = new THREE.Vector3(), halfSize = new THREE.Vector3(), rotation = new THREE.Matrix3() ) {

			this.center = center;
			this.halfSize = halfSize;
			this.rotation = rotation;

		}

		set( center, halfSize, rotation ) {

			this.center = center;
			this.halfSize = halfSize;
			this.rotation = rotation;
			return this;

		}

		copy( obb ) {

			this.center.copy( obb.center );
			this.halfSize.copy( obb.halfSize );
			this.rotation.copy( obb.rotation );
			return this;

		}

		clone() {

			return new this.constructor().copy( this );

		}

		getSize( 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( point, result ) {

			const 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

			const x = THREE.MathUtils.clamp( v1.dot( xAxis ), - halfSize.x, halfSize.x );
			result.add( xAxis.multiplyScalar( x ) );
			const y = THREE.MathUtils.clamp( v1.dot( yAxis ), - halfSize.y, halfSize.y );
			result.add( yAxis.multiplyScalar( y ) );
			const z = THREE.MathUtils.clamp( v1.dot( zAxis ), - halfSize.z, halfSize.z );
			result.add( zAxis.multiplyScalar( z ) );
			return result;

		}

		containsPoint( 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( box3 ) {

			return this.intersectsOBB( obb.fromBox3( box3 ) );

		}

		intersectsSphere( 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( 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 ( let i = 0; i < 3; i ++ ) {

				for ( let 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 ( let i = 0; i < 3; i ++ ) {

				for ( let j = 0; j < 3; j ++ ) {

					AbsR[ i ][ j ] = Math.abs( R[ i ][ j ] ) + epsilon;

				}

			}

			let ra, rb; // test axes L = A0, L = A1, L = A2

			for ( let 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 ( let 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( 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;

		}
		/**
  * Performs a ray/OBB intersection test and stores the intersection point
  * to the given 3D vector. If no intersection is detected, *null* is returned.
  */


		intersectRay( ray, result ) {

			// the idea is to perform the intersection test in the local space
			// of the OBB.
			this.getSize( size );
			aabb.setFromCenterAndSize( v1.set( 0, 0, 0 ), size ); // create a 4x4 transformation matrix

			matrix.setFromMatrix3( this.rotation );
			matrix.setPosition( this.center ); // transform ray to the local space of the OBB

			inverse.copy( matrix ).invert();
			localRay.copy( ray ).applyMatrix4( inverse ); // perform ray <-> AABB intersection test

			if ( localRay.intersectBox( aabb, result ) ) {

				// transform the intersection point back to world space
				return result.applyMatrix4( matrix );

			} else {

				return null;

			}

		}
		/**
  * Performs a ray/OBB intersection test. Returns either true or false if
  * there is a intersection or not.
  */


		intersectsRay( ray ) {

			return this.intersectRay( ray, v1 ) !== null;

		}

		fromBox3( box3 ) {

			box3.getCenter( this.center );
			box3.getSize( this.halfSize ).multiplyScalar( 0.5 );
			this.rotation.identity();
			return this;

		}

		equals( obb ) {

			return obb.center.equals( this.center ) && obb.halfSize.equals( this.halfSize ) && obb.rotation.equals( this.rotation );

		}

		applyMatrix4( matrix ) {

			const e = matrix.elements;
			let sx = v1.set( e[ 0 ], e[ 1 ], e[ 2 ] ).length();
			const sy = v1.set( e[ 4 ], e[ 5 ], e[ 6 ] ).length();
			const sz = v1.set( e[ 8 ], e[ 9 ], e[ 10 ] ).length();
			const det = matrix.determinant();
			if ( det < 0 ) sx = - sx;
			rotationMatrix.setFromMatrix4( matrix );
			const invSX = 1 / sx;
			const invSY = 1 / sy;
			const 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;

		}

	}

	const obb = new OBB();

	THREE.OBB = OBB;

} )();
