import { MathUtils } from '../math/MathUtils'
import { Box3 } from './Box3';
import { Frustum } from './Frustum'

var a = {
	c: null, // center
	u: [[], [], []], // basis vectors
	e: [] // half width
};

var b = {
	c: null, // center
	u: [[], [], []], // basis vectors
	e: [] // half width
};

var R = [[], [], []];
var AbsR = [[], [], []];
var t = [];

var v1 = [];

/**
 * @class OBB
 * 有向包围盒。
 * @memberof THING
 * @public
 */
class OBB extends Box3 {
	/**
	 * 有向包围盒类,用于创建有向包围盒实例。
	 * @param {Array<number>} center 盒子的中心位置。
	 * @param {Array<number>} halfSize 盒子的半尺寸。
	 * @param {Array<number>} rotation 盒子的旋转四元数。
	 * @example
	 * // 创建一个有向包围盒
	 * let box = new BASE.THING.OBB([0, 0, 0], [1, 1, 1], [0, 0, 0, 1]);
	 * @public
	 */
	constructor(center, halfSize, rotation) {
		super(center, halfSize);

		this._rotation = rotation ? rotation.slice(0) : [0, 0, 0, 1];
	}

	_getWorldPosition(point, target) {
		let position = target || [];

		MathUtils.vec3.transformQuat(position, point, this._rotation);
		return MathUtils.vec3.add(position, position, this.center);
	}

	/**
	 * 获取角度。
	 * @type {Array<number>}
	 * @example
	 * // 打印盒子的角度。
	 * console.log(box.angles);
	 * @public
	 */
	get angles() {
		return MathUtils.getAnglesFromQuat(this._rotation);
	}

	/**
	 * 获取最小位置。
	 * @type {Array<number>}
	 * @example
	 * // 打印盒子的最小位置。
	 * console.log(box.min);
	 * @public
	 */
	get min() {
		return this._getWorldPosition(MathUtils.negVector(this.halfSize));
	}

	/**
	 * 获取最大位置。
	 * @type {Array<number>}
	 * @example
	 * // 打印盒子的最大位置。
	 * console.log(box.max);
	 * @public
	 */
	get max() {
		return this._getWorldPosition(this.halfSize);
	}

	get originalMin() {
		return super.min;
	}

	get originalMax() {
		return super.max;
	}

	getPoints() {
		let points = this._getPoints(MathUtils.negVector(this.halfSize), this.halfSize);
		return points.map(point => {
			return this._getWorldPosition(point);
		});
	}

	getLayoutPosition(types) {
		let halfSize = this.halfSize;
		let selfPosition = this._getLayoutPosition(types, MathUtils.negVector(halfSize), halfSize);

		return this._getWorldPosition(selfPosition);
	}

	containsPoint(point) {
		const v1 = MathUtils.subVector(point, this.center);

		const xAxis = MathUtils.vec3ApplyQuat([1, 0, 0], this._rotation);
		const yAxis = MathUtils.vec3ApplyQuat([0, 1, 0], this._rotation);
		const zAxis = MathUtils.vec3ApplyQuat([0, 0, 1], this._rotation);

		// project v1 onto each axis and check if these points lie inside the OBB

		return MathUtils.abs(MathUtils.dotVector(v1, xAxis)) <= this.halfSize[0] &&
			MathUtils.abs(MathUtils.dotVector(v1, yAxis)) <= this.halfSize[1] &&
			MathUtils.abs(MathUtils.dotVector(v1, zAxis)) <= this.halfSize[2];
	}


	/**
	 * Reference: OBB-OBB Intersection in Real-Time Collision Detection
	 * @param obb
	 * @param epsilon
	 * @example
	 * // 创建两个有向包围盒
	 * let obb1 = new BASE.THING.OBB([0, 0, 0], [1, 1, 1], [0, 0, 0, 1]);
	 * let obb2 = new BASE.THING.OBB([2, 0, 0], [1, 1, 1], [0, 0, 0, 1]);
	 * 
	 * // 检查两个包围盒是否相交
	 * let intersects = obb1.intersectsOBB(obb2);
	 * console.log(intersects); // false,因为两个包围盒之间有距离
	 * 
	 * // 移动第二个包围盒使其与第一个相交
	 * let obb3 = new BASE.THING.OBB([1, 0, 0], [1, 1, 1], [0, 0, 0, 1]);
	 * intersects = obb1.intersectsOBB(obb3);
	 * console.log(intersects); // true,因为两个包围盒相交
	 */
	intersectsOBB(obb, epsilon = Number.EPSILON) {
		// prepare data structures (the code uses the same nomenclature like the reference)

		a.c = this.center;
		a.e = this.halfSize;
		const cMatrix = MathUtils.mat3.fromQuat([], this._rotation);
		a.u[0] = [cMatrix[0], cMatrix[1], cMatrix[2]];
		a.u[1] = [cMatrix[3], cMatrix[4], cMatrix[5]];
		a.u[2] = [cMatrix[6], cMatrix[7], cMatrix[8]];

		b.c = obb.center;
		b.e = obb.halfSize;
		const tMatrix = MathUtils.mat3.fromQuat([], obb._rotation);
		b.u[0] = [tMatrix[0], tMatrix[1], tMatrix[2]];
		b.u[1] = [tMatrix[3], tMatrix[4], tMatrix[5]];
		b.u[2] = [tMatrix[6], tMatrix[7], tMatrix[8]];

		// 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] = MathUtils.dotVector(a.u[i], b.u[j]);
			}
		}

		// compute translation vector

		v1 = MathUtils.subVector(b.c, a.c);

		// bring translation into a's coordinate frame

		t[0] = MathUtils.dotVector(v1, a.u[0]);
		t[1] = MathUtils.dotVector(v1, a.u[1]);
		t[2] = MathUtils.dotVector(v1, 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] = MathUtils.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 (MathUtils.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 (MathUtils.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 (MathUtils.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 (MathUtils.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 (MathUtils.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 (MathUtils.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 (MathUtils.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 (MathUtils.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 (MathUtils.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 (MathUtils.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 (MathUtils.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;
	}
	toBoundingBoxAndTransform(box3, matrix) {
		MathUtils.mat4.fromQuat(matrix, this._rotation);
		box3.set([matrix[12], matrix[13], matrix[14]], this.halfSize)
		return this;
	}

	intersectsFrustum(frustum) {
		this.toBoundingBoxAndTransform(aabb, matrix);
		MathUtils.mat4.invert(inverse, matrix);
		localFrustum.copy(frustum).setFromProjectionMatrix(inverse);

		return localFrustum.intersectsBoxV1(aabb);
	}

}
const aabb = new Box3()
const localFrustum = new Frustum()
const matrix =  MathUtils.createMat4();
const inverse =  MathUtils.createMat4();
export { OBB }