Source: objects/Frustum.js

import { Plane } from "@uino/base-thing";
import { Object3D } from './Object3D';
import { MathUtils } from '../math/MathUtils';
import { FrustumHelperComponent } from '../components/FrustumHelperComponent';

const __ = {
	private: Symbol('private'),
}

const registerComponentParam = { isResident: true };

/**
 * @class Frustum
 * The frustum entity object.
 * @memberof THING
 * @extends THING.Object3D
 * @public
 */
class Frustum extends Object3D {

	/**
	 * The frustum object in scene.
	 * @param {Object} param The initial parameters.
	 */
	constructor(param = {}) {
		super(param);

		this[__.private] = {};
		let _private = this[__.private];

		_private.fov = param.fov || 50;
		_private.aspect = param.aspect || 1;
		_private.near = param.near || 0.1;
		_private.far = param.far || 1;

		_private.projectionMatrix = MathUtils.createMat4();

		this.planes = [
			new Plane(),
			new Plane(),
			new Plane(),
			new Plane(),
			new Plane(),
			new Plane()
		];

		this._updateProjectionMatrix();
	}

	// #region Overrides

	onSetupComponent(param) {
		super.onSetupComponent(param);

		this.addComponent(FrustumHelperComponent, 'helper', registerComponentParam);
	}

	// #endregion

	// #region Private

	_updateProjectionMatrix() {
		let _private = this[__.private];

		MathUtils.mat4.perspective(_private.projectionMatrix, _private.fov / 180 * Math.PI, _private.aspect, _private.near, _private.far);

		this._setFromProjectionMatrix(_private.projectionMatrix);
	}

	_setFromProjectionMatrix(m) {
		const planes = this.planes;
		const me = m;
		const me0 = me[0], me1 = me[1], me2 = me[2], me3 = me[3];
		const me4 = me[4], me5 = me[5], me6 = me[6], me7 = me[7];
		const me8 = me[8], me9 = me[9], me10 = me[10], me11 = me[11];
		const me12 = me[12], me13 = me[13], me14 = me[14], me15 = me[15];

		planes[0].setComponents(me3 - me0, me7 - me4, me11 - me8, me15 - me12).normalize();
		planes[1].setComponents(me3 + me0, me7 + me4, me11 + me8, me15 + me12).normalize();
		planes[2].setComponents(me3 + me1, me7 + me5, me11 + me9, me15 + me13).normalize();
		planes[3].setComponents(me3 - me1, me7 - me5, me11 - me9, me15 - me13).normalize();
		planes[4].setComponents(me3 - me2, me7 - me6, me11 - me10, me15 - me14).normalize();
		planes[5].setComponents(me3 + me2, me7 + me6, me11 + me10, me15 + me14).normalize();

		return this;
	}

	// #endregion

	/**
	 * set projection matrix.
	 * @param {Number} fov The fov.
	 * @param {Number} aspect The aspect.
	 * @param {Number} near The near.
	 * @param {Number} far The far.
	 */
	setProjectionMatrix(fov, aspect, near, far) {
		let _private = this[__.private];

		_private.fov = fov;
		_private.aspect = aspect;
		_private.near = near;
		_private.far = far;

		this._updateProjectionMatrix();
	}

	/**
	 * intersects object
	 * @param {Object} object
	 * @returns {Boolean}
	 */
	intersectsObject(object) {
		const sphere = object.getAABB().getBoundingSphere();

		return this.intersectsSphere(sphere);
	}

	/**
	 * intersects sphere
	 * @param {Object} sphere
	 * @param {Array<Number>} sphere.center
	 * @param {Number} sphere.radius
	 * @returns {Boolean}
	 */
	intersectsSphere(sphere) {
		const planes = this.planes;
		const center = sphere.center;
		const negRadius = -sphere.radius;

		for (let i = 0; i < 6; i++) {
			const distance = planes[i].distanceToPoint(center);

			if (distance < negRadius) {
				return false;
			}
		}

		return true;
	}

	/**
	 * intersects sphere
	 * @param {Array<Number>} point
	 * @returns {Boolean}
	 */
	containsPoint(point) {
		const planes = this.planes;

		for (let i = 0; i < 6; i++) {
			if (planes[i].distanceToPoint(point) < 0) {
				return false;
			}
		}

		return true;
	}

	// #region Common

	/**
	 * @private
	 */
	get projectionMatrix() {
		let _private = this[__.private];

		return _private.projectionMatrix;
	}

	/**
	 * Get/Set fov.
	 * @type {Number}
	 * @example
	 * 	object.name = 45;
	 */
	get fov() {
		let _private = this[__.private];

		return _private.fov;
	}

	set fov(value) {
		let _private = this[__.private];

		_private.fov = value;
		this._updateProjectionMatrix();
	}

	/**
	 * Get/Set aspect.
	 * @type {Number}
	 * @example
	 * 	object.aspect = 1;
	 */
	get aspect() {
		let _private = this[__.private];

		return _private.aspect;
	}

	set aspect(value) {
		let _private = this[__.private];

		_private.aspect = value;
		this._updateProjectionMatrix();
	}

	/**
	 * Get/Set near.
	 * @type {Number}
	 * @example
	 * 	object.near = 0.1;
	 */
	get near() {
		let _private = this[__.private];

		return _private.near;
	}

	set near(value) {
		let _private = this[__.private];

		_private.near = value;
		this._updateProjectionMatrix();
	}

	/**
	 * Get/Set far.
	 * @type {Number}
	 * @example
	 * 	object.far = 10;
	 */
	get far() {
		let _private = this[__.private];

		return _private.far;
	}

	set far(value) {
		let _private = this[__.private];

		_private.far = value;
		this._updateProjectionMatrix();
	}

	/**
	 * Check whether it's frustum object.
	 * @type {Boolean}
	 */
	get isFrustum() {
		return true;
	}

	// #endregion

}

export { Frustum }