Source: components/HelperComponent.js

import { Utils } from '../common/Utils';
import { ObjectBoxHelper } from '../helpers/ObjectBoxHelper';
import { ObjectOrientedBoxHelper } from '../helpers/ObjectOrientedBoxHelper';
import { BaseComponent } from './BaseComponent';
import { BoxHelperModeType } from '../const';

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

/**
 * @class HelperComponent
 * The object helper component.
 * @memberof THING
 * @extends THING.BaseComponent
 * @public
 */
class HelperComponent extends BaseComponent {

	static mustCopyWithInstance = true;

	/**
	 * The helper component of object what can show many useful information of it, like axis, bounding box, etc.
	 */
	constructor() {
		super();

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

		_private.helpers = {
			axes: null,
			boundingBox: null,
			orientedBox: null,
		};

		_private.axesLength = 0;

		_private.boundingBox = null;
		_private.orientedBox = null;
	}

	// #region Private

	_enableLineHelper(name, value, onGetPoints, onGetColors) {
		let _private = this[__.private];

		if (value) {
			if (_private.helpers[name]) {
				return;
			}

			let object = this.object;
			object.waitForComplete().then(() => {
				let lineSegments = Utils.createObject('LineSegments');
				lineSegments.setAttribute('Pickable', false);
				lineSegments.getUserData()['isDebugNode'] = true;
				object.body.createRootNode();
				object.node.add(lineSegments);

				let points = onGetPoints();
				let colors = onGetColors();

				lineSegments.setSize(points.length * 3);
				lineSegments.setPoints(points);
				lineSegments.setColors(colors);

				_private.helpers[name] = lineSegments;
			});
		}
		else {
			if (!_private.helpers[name]) {
				return;
			}

			_private.helpers[name].dispose();
			_private.helpers[name] = null;
		}
	}

	_enableBoxHelper(type, name, value, callback) {
		let _private = this[__.private];

		if (value) {
			if (!_private.helpers[name]) {
				_private.helpers[name] = new type({ object: this.object });
				this.app.scene.rootObjects['debug'].node.add(_private.helpers[name].lines);
			}

			callback(_private.helpers[name]);
		}
		else {
			if (!_private.helpers[name]) {
				return;
			}

			_private.helpers[name].dispose();
			_private.helpers[name] = null;
		}
	}

	_refreshBoxHelper(type, name, visible, mode, color) {
		this._enableBoxHelper(type, name, visible, (helper) => {
			helper.mode = mode;
			helper.color = color;
		});
	}

	_createBoxHelper(type, name) {
		let that = this;

		let visible = false;
		let mode = BoxHelperModeType.All;
		let color = [1, 1, 1];

		let object = {};
		Object.defineProperties(object, {
			'visible': {
				get: function () {
					return visible;
				},
				set: function (value) {
					visible = value;

					that._refreshBoxHelper(type, name, visible, mode, color);
				}
			},
			'mode': {
				get: function () {
					return mode;
				},
				set: function (value) {
					mode = value;

					that._refreshBoxHelper(type, name, visible, mode, color);
				}
			},
			'color': {
				get: function () {
					return color;
				},
				set: function (value) {
					color = Utils.parseColor(value, [1, 1, 1]);

					that._refreshBoxHelper(type, name, visible, mode, color);
				}
			}
		});

		return object;
	}

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

		let helpers = _private.helpers;
		for (let key in helpers) {
			if (helpers[key]) {
				helpers[key].setVisible(visible);
			}
		}
	}

	// #endregion

	// #region BaseComponent Interface

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

		let helpers = _private.helpers;
		for (let key in helpers) {
			if (helpers[key]) {
				helpers[key].dispose();
				helpers[key] = null;
			}
		}

		super.onRemove();
	}

	onVisibleChange(value) {
		this._show(value);
	}

	// #endregion

	// #region Accessors

	/**
	 * @typedef {Object} BoxHelperResult
	 * @property {Boolean} visible The visible state.
	 * @property {Array<Number>} color The color.
	 * @property {BoxHelperModeType} mode The mode.
	 */

	/**
	 * Get bounding box.
	 * @type {BoxHelperResult}
	 * @example
	 * let component = new THING.HelperComponent();
	 * let bounding = component.boundingBox;
	 * // @expect(bounding.mode == 'All')
	 * @public
	 */
	get boundingBox() {
		let _private = this[__.private];

		if (!_private.boundingBox) {
			_private.boundingBox = this._createBoxHelper(ObjectBoxHelper, 'boundingBox');
		}

		return _private.boundingBox;
	}

	/**
	 * Get oriented box.
	 * @type {BoxHelperResult}
	 * @example
	 * let component = new THING.HelperComponent();
	 * let bounding = component.orientedBox;
	 * // @expect(bounding.mode == 'All')
	 * @public
	 */
	get orientedBox() {
		let _private = this[__.private];

		if (!_private.orientedBox) {
			_private.orientedBox = this._createBoxHelper(ObjectOrientedBoxHelper, 'orientedBox');
		}

		return _private.orientedBox;
	}

	/**
	 * Show/Hide axes helper.
	 * @type {Boolean}
	 * @example
	 * 	let box = new THING.Box();
	 * 	box.helper.axes = true;
	 *  box.waitForComplete().then(()=>{
	 *     let ret = box.helper.axes;
	 *    // @expect(ret == true);
	 *  })
	 * @public
	 */
	get axes() {
		let _private = this[__.private];

		return !!_private.helpers['axes'];
	}
	set axes(value) {
		let _private = this[__.private];

		this._enableLineHelper('axes', value,
			() => {
				let size = _private.axesLength;
				if (!size) {
					size = this.object.boundingBox.halfSize.reduce((total, num) => {
						return total + num;
					}, 0);
				}

				// Make sure we can see it
				size = Math.max(size, 10);

				_private.axesLength = size;

				return [
					[0, 0, 0], [size, 0, 0],
					[0, 0, 0], [0, size, 0],
					[0, 0, 0], [0, 0, size]
				];
			},
			() => {
				return [
					[1, 0, 0], [1, 0.6, 0],
					[0, 1, 0], [0.6, 1, 0],
					[0, 0, 1], [0, 0.6, 1]
				];
			}
		);
	}

	/**
	 * Get/Set axes helper length.
	 * @type {Number}
	 *  @example
	 * 	let box = new THING.Box();
	 * 	box.helper.axesLength = 10;
	 * 	// @expect(box.helper.axesLength == 10);
	 * @public
	 */
	get axesLength() {
		let _private = this[__.private];

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

		_private.axesLength = value;

		this.object.waitForComplete().then(() => {
			// Refresh axes
			let axes = _private.helpers.axes;
			if (axes) {
				axes.setPoints([
					[0, 0, 0], [value, 0, 0],
					[0, 0, 0], [0, value, 0],
					[0, 0, 0], [0, 0, value]
				]);
			}
		});
	}

	/**
	 * Get the light(s)'s config.
	 * @type {Array<Object>}
	 *  @example
	 * 	let box = new THING.Box();
	 * 	let count = box.helper.lights.length;
	 * 	// @expect(count == 0);
	 * @public
	 */
	get lights() {
		return this.object.query('.Light').map(light => {
			let object = {
				type: light.type,
				id: light.id,
				name: light.name,
				uuid: light.uuid,
			};

			[
				'color',
				'intensity',
				'angles',
				'components',
			].forEach(name => {
				Utils.bindObjectProperty(object, light, name);
			});

			if (light.isBaseShadowLight) {
				[
					'castShadow',
					'shadowQuality',
					'shadowBias',
					'shadowRadius',
					'shadowRange',
				].forEach(name => {
					Utils.bindObjectProperty(object, light, name);
				});
			}

			if (light.isSpotLight) {
				[
					'distance',
					'angle',
					'penumbra',
					'decay',
				].forEach(name => {
					Utils.bindObjectProperty(object, light, name);
				});
			}
			else if (light.isHemisphereLight) {
				[
					'groundColor',
				].forEach(name => {
					Utils.bindObjectProperty(object, light, name);
				});
			}

			return object;
		}).toArray();
	}

	// #endregion

}

export { HelperComponent }