Source: components/ColliderComponent.js

import { EventDispatcher, MathUtils } from '@uino/base-thing';
import { ObjectColliderBoxHelper } from '../helpers/ObjectColliderBoxHelper';
import { ObjectColliderSphereHelper } from '../helpers/ObjectColliderSphereHelper';
import { BaseComponent } from './BaseComponent';
import { EventType, ColliderType } from '../const';

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

let _vec3_0 = MathUtils.createVec3();
let _vec3_1 = MathUtils.createVec3();
let _vec3_2 = MathUtils.createVec3();
let _vec3_3 = MathUtils.createVec3();
let _vec3_4 = MathUtils.createVec3();
let _vec3_5 = MathUtils.createVec3();
let _vec3_6 = MathUtils.createVec3();
let _vec3_7 = MathUtils.createVec3();

let _vec3Lib = MathUtils.vec3;

/**
 * @class ColliderComponent
 * The collider component.
 * @memberof THING
 * @extends THING.BaseComponent
 */
class ColliderComponent extends BaseComponent {

	static mustCopyWithInstance = true;

	/**
	 * The collider of object(s) what can listen on collider events to process hit logic code.
	 */
	constructor() {
		super();

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

		_private.enable = true;
		_private.visible = false;

		_private.helper = null;

		_private.eventDispatcher = new EventDispatcher();

		// For fast usage, we define here ...

		// The common info
		this._radius = 0.5;
		this._halfSize = null;
		this._offset = [0, 0, 0];
		this._mode = ColliderType.Box;

		// Get the min and max points
		let min = [-this._radius, -this._radius, -this._radius];
		let max = [this._radius, this._radius, this._radius];

		// The area info
		this._matrixWorld = null;
		this._areaInfo = {
			base: {
				min,
				max,
			},
			local: [
				// TOP
				[min[0], min[1], min[2]], // 0
				[max[0], min[1], min[2]], // 1
				[max[0], min[1], max[2]], // 2
				[min[0], min[1], max[2]], // 3
				// BOTTOM
				[min[0], max[1], min[2]], // 4
				[max[0], max[1], min[2]], // 5
				[max[0], max[1], max[2]], // 6
				[min[0], max[1], max[2]], // 7
			],
			transform: {
				min: min.slice(0),
				max: max.slice(0)
			},
		};
	}

	// #region Private Functions

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

		switch (this._mode) {
			case ColliderType.Box:
				_private.helper = new ObjectColliderBoxHelper({ object: this.object });
				this.app.scene.rootObjects['debug'].node.add(_private.helper.lines);
				break;

			case ColliderType.Sphere:
				_private.helper = new ObjectColliderSphereHelper({ object: this.object });
				this.app.scene.rootObjects['debug'].node.add(_private.helper.lines);
				break;
		}
	}

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

		if (_private.helper) {
			_private.helper.dispose();
			_private.helper = null;
		}
	}

	_refreshHelper() {
		this._disposeHelper();
		this._createHelper();
	}

	_refreshAreaInfo() {
		let areaInfo = this._areaInfo;

		let min = [], max = [];
		MathUtils.vec3.add(min, areaInfo.base.min, this._offset);
		MathUtils.vec3.add(max, areaInfo.base.max, this._offset);

		areaInfo.local = [
			// TOP
			[min[0], min[1], min[2]], // 0
			[max[0], min[1], min[2]], // 1
			[max[0], min[1], max[2]], // 2
			[min[0], min[1], max[2]], // 3
			// BOTTOM
			[min[0], max[1], min[2]], // 4
			[max[0], max[1], min[2]], // 5
			[max[0], max[1], max[2]], // 6
			[min[0], max[1], max[2]], // 7
		];
	}

	// #endregion

	// #region Overrides

	onBeforeRemove() {
		this.enable = false;
		this.visible = false;
	}

	// #endregion

	// #region Callbacks

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

		_private.eventDispatcher.dispatchEvent({ type: EventType.ColliderEnter.toLowerCase(), object: collider.object });
	}

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

		_private.eventDispatcher.dispatchEvent({ type: EventType.ColliderLeave.toLowerCase(), object: collider.object });
	}

	// #endregion

	// #region Events

	/**
	 * Add event listener.
	 * @param {String} type The event type.
	 * @param {Function} listener The event callback function.
	 */
	addEventListener(type, listener) {
		let _private = this[__.private];

		_private.eventDispatcher.addEventListener(type.toLowerCase(), listener);
	}

	/**
	 * Remove event listener.
	 * @param {String} type The event type.
	 * @param {Function} listener The event callback function.
	 */
	removeEventListener(type, listener) {
		let _private = this[__.private];

		_private.eventDispatcher.removeEventListener(type.toLowerCase(), listener);
	}

	// #endregion

	test(collider) {
		switch (this._mode) {
			case ColliderType.Box:
				let transform1 = this._areaInfo.transform;
				let transform2 = collider._areaInfo.transform;

				return MathUtils.intersectsBox(transform1.min, transform1.max, transform2.min, transform2.max);

			case ColliderType.Sphere:
				let matrixWorld1 = this._matrixWorld;
				let matrixWorld2 = collider._matrixWorld;

				let dx = matrixWorld1[12] - matrixWorld2[12];
				let dy = matrixWorld1[13] - matrixWorld2[13];
				let dz = matrixWorld1[14] - matrixWorld2[14];
				let distance = Math.sqrt(dx * dx + dy * dy + dz * dz) * 0.5;

				if (distance < this._radius || distance < collider._radius) {
					return true;
				}
				break;

			default:
				break;
		}

		return false;
	}

	setMatrixWorld(value) {
		this._matrixWorld = value;

		let areaInfo = this._areaInfo;
		let min = areaInfo.transform.min;
		let max = areaInfo.transform.max;

		min[0] = min[1] = min[2] = +Infinity;
		max[0] = max[1] = max[2] = -Infinity;

		let local = this._areaInfo.local;

		_vec3Lib.transformMat4(_vec3_0, local[0], this._matrixWorld);
		_vec3Lib.min(min, min, _vec3_0);
		_vec3Lib.max(max, max, _vec3_0);

		_vec3Lib.transformMat4(_vec3_1, local[1], this._matrixWorld);
		_vec3Lib.min(min, min, _vec3_1);
		_vec3Lib.max(max, max, _vec3_1);

		_vec3Lib.transformMat4(_vec3_2, local[2], this._matrixWorld);
		_vec3Lib.min(min, min, _vec3_2);
		_vec3Lib.max(max, max, _vec3_2);

		_vec3Lib.transformMat4(_vec3_3, local[3], this._matrixWorld);
		_vec3Lib.min(min, min, _vec3_3);
		_vec3Lib.max(max, max, _vec3_3);

		_vec3Lib.transformMat4(_vec3_4, local[4], this._matrixWorld);
		_vec3Lib.min(min, min, _vec3_4);
		_vec3Lib.max(max, max, _vec3_4);

		_vec3Lib.transformMat4(_vec3_5, local[5], this._matrixWorld);
		_vec3Lib.min(min, min, _vec3_5);
		_vec3Lib.max(max, max, _vec3_5);

		_vec3Lib.transformMat4(_vec3_6, local[6], this._matrixWorld);
		_vec3Lib.min(min, min, _vec3_6);
		_vec3Lib.max(max, max, _vec3_6);

		_vec3Lib.transformMat4(_vec3_7, local[7], this._matrixWorld);
		_vec3Lib.min(min, min, _vec3_7);
		_vec3Lib.max(max, max, _vec3_7);
	}

	// #region Accessors

	/**
	 * Enable/Disable.
	 * @type {Boolean}
	 */
	get enable() {
		let _private = this[__.private];

		return _private.enable;
	}
	set enable(value) {
		let _private = this[__.private];

		_private.enable = value;

		if (value) {
			this.object.app.colliderManager.add(this);
		}
		else {
			this.object.app.colliderManager.remove(this);
		}
	}

	/**
	 * Get/Set the mode.
	 * @type {ColliderType}
	 */
	get mode() {
		return this._mode;
	}
	set mode(value) {
		let _private = this[__.private];

		if (this._mode == value) {
			return;
		}

		this._mode = value;

		// If helper is showing then refresh it
		if (_private.visible) {
			this._refreshHelper();
		}
	}

	/**
	 * Get/Set the radius.
	 * @type {Number}
	 */
	get radius() {
		return this._radius;
	}
	set radius(value) {
		this._radius = value;
	}

	/**
	 * Get/Set the offset.
	 * @type {Array<Number>}
	 */
	get offset() {
		return this._offset;
	}
	set offset(value) {
		if (value) {
			this._offset = value.slice(0);
		}
		else {
			this._offset = [0, 0, 0];
		}

		this._refreshAreaInfo();
	}

	/**
	 * Get/Set the half size.
	 * @type {Array<Number>}
	 */
	get halfSize() {
		return this._halfSize;
	}
	set halfSize(value) {
		this._halfSize = value.slice(0);

		let base = this._areaInfo.base;
		base.min[0] = -value[0];
		base.min[1] = -value[1];
		base.min[2] = -value[2];
		base.max[0] = value[0];
		base.max[1] = value[1];
		base.max[2] = value[2];

		this._refreshAreaInfo();
	}

	/**
	 * Get/Set the visible.
	 * @type {Boolean}
	 */
	get visible() {
		let _private = this[__.private];

		return _private.visible;
	}
	set visible(value) {
		let _private = this[__.private];

		if (_private.visible == value) {
			return;
		}

		_private.visible = value;

		if (value) {
			this._createHelper();
		}
		else {
			this._disposeHelper();
		}
	}

	/**
	 * Get the area info.
	 * @type {Object}
	 * @private
	 */
	get areaInfo() {
		return this._areaInfo;
	}

	// #endregion

}

export { ColliderComponent }