Source: myPoints.js

/**
 * @module MyPoints
 * @description Array of my points.
 * @author [Andrej Hristoliubov]{@link https://github.com/anhr}
 *
 * @copyright 2011 Data Arts Team, Google Creative Lab
 *
 * @license under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
*/

import Player from '../player/player.js';
import getShaderMaterialPoints from '../getShaderMaterialPoints/getShaderMaterialPoints.js';
import three from '../three.js'
import Options from '../Options.js'
import MyObject from '../myObject.js'

/**
 * Creating the new [THREE.Points]{@link https://threejs.org/docs/index.html?q=poi#api/en/objects/Points} and adding it into group. Extends <a href="../../jsdoc/MyObject/module-myObject-MyObject.html" target="_blank">MyObject</a>.
 * @class
 * @extends MyObject
 */
class MyPoints extends MyObject {

	/**
	 * Creating the new [THREE.Points]{@link https://threejs.org/docs/index.html?q=poi#api/en/objects/Points} and adding it into group.
	 * @param {array} arrayFuncs <b>points.geometry.attributes.position</b> array.
	 * See <b>arrayFuncs</b> parametr of the <a href="../../player/jsdoc/module-Player-Player.getPoints.html" target="_blank">Player.getPoints(...)</a> for details.
	 * @param {THREE.Group} [group] [Group]{@link https://threejs.org/docs/index.html?q=grou#api/en/objects/Group} for new points.
	 * Default is <b><a href="../../jsdoc/three/Three.html" target="_blank">three</a>.scene</b>
	 * @param {object} [settings={}] the following settings are available
	 * @param {object|Options} [settings.options=new Options()] the following options are available.
	 * See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
	 * @param {Object} [settings.options.point] point options.
	 * See <a href="../../jsdoc/Options/global.html#point" target="_blank">Options.point</a> for details.
	 * @param {object} [settings.options.scales.w] followed w axis scale params is available
	 * @param {object} [settings.options.scales.w.min=0] Minimal range of the [color palette]{@link https://github.com/anhr/commonNodeJS/tree/master/colorpicker}.
	 * @param {object} [settings.options.scales.w.max=1] Maximal range of the [color palette]{@link https://github.com/anhr/commonNodeJS/tree/master/colorpicker}.
	 * @param {object} [settings.options.palette=new ColorPicker.palette();//palette: ColorPicker.paletteIndexes.BGYW] See <a href="../../colorPicker/jsdoc/module-ColorPicker.html#~Palette" target="_blank">ColorPicker.palette</a>.
	 * <pre>
	 * Example:
	 * <b>new ColorPicker.palette( { palette: ColorPicker.paletteIndexes.bidirectional } );</b>
	 * </pre>
	 * @param {GuiSelectPoint} [settings.options.guiSelectPoint] A [dat.gui]{@link https://github.com/anhr/dat.gui} based graphical user interface for select a point from the mesh.
	 * See <a href="../../guiSelectPoint/jsdoc/index.html" target="_blank">GuiSelectPoint</a> for details.
	 * @param {object} [settings.options.raycaster] Followed [raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster} options is available.
	 * @param {Function(particle)} [settings.options.raycaster.addParticle] Callback function that take as input the <b>new THREE.Points</b>.
	 * Add new particle into array of objects to check for intersection with the ray. See [THREE.Raycaster.intersectObject]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObject} for details.
	 * @param {Function(particle)} [settings.options.raycaster.removeParticle] Callback function that take as input the <b>new THREE.Points</b>.
	 * Remove particle from array of objects to check for intersection with the ray. See [THREE.Raycaster.intersectObject]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObject} for details.
	 * @param {object} [settings.pointsOptions={}] followed points options is availablee:
	 * @param {FrustumPoints} [settings.pointsOptions.frustumPoints] Include this points into array of points with cloud. See <a href="../../frustumPoints/jsdoc/index.html" target="_blank">FrustumPoints</a>.
	 * @param {number} [settings.pointsOptions.tMin=0] start time. Uses for playing of the points..
	 * @param {string} [settings.pointsOptions.name=""] Name of the points. Used for displaying of items of the <b>Select</b> drop down control of the <b>Meshes</b> folder of the [dat.gui]{@link https://github.com/anhr/dat.gui}.
	 * @param {object|boolean} [settings.pointsOptions.shaderMaterial] creates the [THREE.Points]{@link https://threejs.org/docs/index.html?q=poi#api/en/objects/Points} with [THREE.ShaderMaterial]{@link https://threejs.org/docs/index.html#api/en/materials/ShaderMaterial} material.
	 * The size of the each point of the <b>THREE.Points</b> seems the same on canvas
	 * because I reduce the size of the points closest to the camera and increase the size of the points farthest to the camera.
	 * <p>false - no shaderMaterial.
	 * @param {object} [settings.pointsOptions.shaderMaterial.point]
	 * @param {number} [settings.pointsOptions.shaderMaterial.point.size] point size.
	 * @param {THREE.Vector3} [settings.pointsOptions.position=new THREE.Vector3( 0, 0, 0 )] position of the points.
	 * <pre>
	 * Vector's x, y, z is position of the points.
	 * Can be as:
	 * float - position of the points.
	 * [float] - array of positions of the points.
	 * Function - position of the points is function of the t. Example:
	 *	<b>new Function( 't', 'return 0.1 + t' )</b>
	 * </pre>
	 * Example:
	 * @param {Array} [settings.pointsOptions.colors] Array of colors for the each vertex.
	 * <pre>
	 * Every vertex is associated with 3 values of the <b>colors</b> array.
	 * Each value of the <b>colors</b> array is red or green or blue color of the particular vertex in range from 0 to 1.
	 * 
	 * 0 is no color.
	 * 1 is full color.
	 * 
	 * For example:
	 * settings.object.geometry.colors: [
	 * 	1, 0, 0,//red color of the <b>position[0]</b> vertex.
	 * 	0, 1, 0,//green color of the <b>position[1]</b> vertex.
	 * 	0, 0, 1,//blue color of the <b>position[2]</b> vertex.
	 * 	1, 1, 1,//white color of the <b>position[3]</b> vertex.
	 * ],
	 * Have effect only if <b>arrayFuncs</b> points are not <b>THREE.Vector4</b> type. See <b>arrayFuncs</b> parametr of the <a href="../../player/jsdoc/module-Player-Player.getPoints.html" target="_blank">Player.getPoints(...)</a> for details.
	 * </pre>
	 * @param {String|number} [settings.pointsOptions.color='lime'] color of points.
	 * <pre>
	 * String - color name. See list of available color names in the <b>_colorKeywords</b> object in the [Color.js]{@link https://github.com/mrdoob/three.js/blob/dev/src/math/Color.js} file.
	 * number - color [Hex triplet]{@link https://en.wikipedia.org/wiki/Web_colors#Hex_triplet}. Example: 0x0000ff - blue color.
	 * Have effect only if <b>settings.pointsOptions.colors</b> are not defined.
	 * <pre>
	 * @param {boolean|array} [settings.pointsOptions.opacity] 
	 * <pre>
	 * boolean -
	 *	If true then opacity of the point is depend from distance to all meshes points from the group with defined <b>mesh.userData.cloud</b>.
	 *	See <b>optionsColor.opacity</b> parameter of the <a href="../../player/jsdoc/module-Player-Player.getColors.html" target="_blank">Player.getColors(...)</a>ions.getColors for details.
	 * array -
	 *	Array of opacities of each position of the points.
	 *	Each item of array is float value in the range of 0.0 - 1.0 indicating how transparent the material is.
	 *	A value of 0.0 indicates fully transparent, 1.0 is fully opaque.
	 * @param {THREE.Vector3} [settings.pointsOptions.scale=new THREE.Vector3( 1, 1, 1 )] scale of the points.
	 * <pre>
	 * Vector's x, y, z is scale of the points.
	 * Can be as:
	 * float - scale of the points.
	 * [float] - array of scales of the points.
	 * Function - scale of the points is function of the t. Example:
	 *	<b>new Function( 't', 'return 1.1 + t' )</b>
	 * </pre>
	 * Example:
	 * <b>new THREE.Vector3 ( new Function( 't', 'return 1 + t' ), 1, 1)</b>
	 * @param {THREE.Vector3} [settings.pointsOptions.rotation=new THREE.Vector3( 0, 0, 0 )] rotation of the points.
	 * <pre>
	 * Vector's x, y, z is rotation of the points.
	 * Can be as:
	 * float - rotation of the points.
	 * [float] - array of rotations of the points.
	 * Function - rotation of the points is function of the t. Example:
	 *	<b>new Function( 't', 'return Math.PI / 2 + t * Math.PI * 2' )</b>
	 * </pre>
	 * Example:
	 * <b>new THREE.Vector3 ( new Function( 't', 'return Math.PI / 2 + t * Math.PI * 2' ), 0, 0)</b>
	 * @param {function} [settings.pointsOptions.onReady] Callback function that take as input the <b>new THREE.Points</b>.
	 * Fires after creating of the points.
	 * <pre>
	 * function( points )
	 *	<b>points</b> - [Points]{@link https://threejs.org/docs/index.html?q=Poin#api/en/objects/Points}.
	 *	Call the <b>points.userData.opacity(opacity)</b> function if you want to change the opacity of the points.
	 *		The <b>opacity</b> parameter is float in the range of 0.0 - 1.0 indicating how transparent the points is.
	 *		A value of 0.0 indicates fully transparent, 1.0 is fully opaque.
	 * </pre>
	 * @param {object} [settings.pointsOptions.raycaster] Followed [raycaster]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster} options is available.
	 * @param {Function(intersection, mouse)} [settings.pointsOptions.raycaster.onIntersection] Callback function that take as input the <b>[intersectObject]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObject} </b>, and <b>mouse position</b>.
	 * Fires after intersection of the mouse pointer with a point.
	 * @param {Function()} [settings.pointsOptions.raycaster.onIntersectionOut] Callback function.
	 * Fires if mouse pointer leaves of intersection with the point.
	 * @param {Function(intersection)} [settings.pointsOptions.raycaster.onMouseDown] Callback function that take as input the <b>[intersectObject]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObject} </b>.
	 * User has clicked over point.
	 * @param {object} [settings.pointsOptions.elements] Followed elements can display on the web page
	 * @param {HTMLElement|string} [settings.pointsOptions.elements.pointsName] Display of the <b>settings.pointsOptions.name</b> on the web page.
	 * <pre>
	 * HTMLElement - element for displaying.
	 * string - <b>id</b> of the element.
	 * </pre>
	 */
	constructor(arrayFuncs, group, settings) {

		super(settings, arrayFuncs);

		const _this = this;
		
		const THREE = three.THREE;

		group = group || three.scene;

		if ((arrayFuncs === undefined) && !settings.bufferGeometry.attributes.position) console.error('MyPoints: Vertices was not defined')
		if (
			(arrayFuncs != undefined) &&//вершины заданы в settings.bufferGeometry
			(typeof arrayFuncs != 'function') && (arrayFuncs.length === 0)
		)
			arrayFuncs.push(new THREE.Vector3());

		settings.pointsOptions = settings.pointsOptions || {};
		const pointsOptions = settings.pointsOptions;
		settings.options = settings.options || new Options();
		var options = settings.options;
		if (!options.boOptions) options = new Options(options);
		pointsOptions.tMin = pointsOptions.tMin || 0;
		pointsOptions.name = pointsOptions.name || '';
		pointsOptions.position = pointsOptions.position || new THREE.Vector3(0, 0, 0);
		pointsOptions.scale = pointsOptions.scale || new THREE.Vector3(1, 1, 1);
		pointsOptions.rotation = pointsOptions.rotation || new THREE.Vector3();
		pointsOptions.group = group;

		//points name
		if (pointsOptions.name !== '' && pointsOptions.elements) {

			if (pointsOptions.elements.pointsName === null) console.warn('MyPoints: Points name element is not exists');
			if (!pointsOptions.elements.pointsName) pointsOptions.elements.pointsName = 'pointsName';
			const elPointsName = typeof pointsOptions.elements.pointsName === "string" ?
				document.getElementById(pointsOptions.elements.pointsName) : pointsOptions.elements.pointsName;
			if (elPointsName) elPointsName.innerHTML = pointsOptions.name;
			else console.warn('MyPoints: Element with id: "' + pointsOptions.elements.pointsName + '" is not exists');

		}

		//Эту строку нужно вызывать до создания точек THREE.Points
		//что бы вызывалась моя версия THREE.BufferGeometry().setFromPoints для создания geometry c itemSize = 4
		//потому что в противном случае при добавлени этих точек в FrustumPoints.pushArrayCloud() координата w будет undefined
		Player.assign();

		if (pointsOptions.shaderMaterial !== false)
			new getShaderMaterialPoints(group, arrayFuncs,function (points) { Points(points); }, {

				options: options,
				pointsOptions: pointsOptions,
				object: { geometry: { position: settings.object.geometry.position, opacity: settings.object.geometry.opacity } },
				bufferGeometry: settings.bufferGeometry,

			});
		else {

			let buffer;
			if ( typeof arrayFuncs === 'function' ) buffer = arrayFuncs();
			else {

				const points = Player.getPoints(arrayFuncs, { options: options, group: group, t: pointsOptions.tMin });
				this.getPoint = (i) => { return points[i]; }
				buffer = this.setPositionAttributeFromPoints( points );

			}
			const points = new THREE.Points( buffer,
				new THREE.PointsMaterial({

					size: options.point.size / options.point.sizePointsMaterial,

					//THREE.Material: 'vertexColors' parameter is undefined.
					//vertexColors: THREE.VertexColors,
					vertexColors: true,
					transparent: settings.pointsOptions.opacity ?
						true ://установлена прозрачность вершин
						undefined,

				})

			);

			if (pointsOptions.frustumPoints)
				points.userData.cloud = {

					indexArray: pointsOptions.frustumPoints.pushArrayCloud(points.geometry),//индекс массива точек в FrustumPoints.arrayCloud которые принадлежат этому points

				}
			Points(points);

		}
		function Points(points) {

			_this.object3D = points;
			points.name = pointsOptions.name;//'Wave';
			if (pointsOptions.pointIndexes !== undefined)
				points.userData.pointIndexes = function (pointIndex) { return pointsOptions.pointIndexes(pointIndex); }
			if (pointsOptions.pointName !== undefined)
				points.userData.pointName = function (pointIndex) { return pointsOptions.pointName(pointIndex); }
			if (pointsOptions.controllers !== undefined) {

				points.userData.addControllers = pointsOptions.addControllers;
				points.userData.controllers = function ( /*cFrustumPoints*/) { return pointsOptions.controllers( /*cFrustumPoints*/); }

			}
			if (settings.pointsOptions.raycaster) points.userData.raycaster = settings.pointsOptions.raycaster;
			var arrayFuncsPlayer;
			if (arrayFuncs instanceof THREE.BufferGeometry) {

				arrayFuncsPlayer = [];
				for (var i = 0; i < arrayFuncs.attributes.position.count; i++)
					arrayFuncsPlayer.push(new THREE.Vector3().fromBufferAttribute(arrayFuncs.attributes.position, i));

			} else arrayFuncsPlayer = arrayFuncs;
			points.userData.player = {

				arrayFuncs: arrayFuncsPlayer,
				selectPlayScene: function (t) {

					setPositions(t);
					setScales(t);
					setRotations(t);

				}

			}
			function setPositions(t) {

				t = t || pointsOptions.tMin;
				function setPosition(axisName) {

					points.position[axisName] = typeof pointsOptions.position[axisName] === "function" ?
						pointsOptions.position[axisName](t, options.a, options.b) :
						pointsOptions.position[axisName];

				}
				setPosition('x');
				setPosition('y');
				setPosition('z');

			}
			setPositions();
			function setScales(t) {

				t = t || pointsOptions.tMin;
				function setScale(axisName) {

					points.scale[axisName] = typeof pointsOptions.scale[axisName] === "function" ?
						pointsOptions.scale[axisName](t, options.a, options.b) :
						pointsOptions.scale[axisName];

				}
				setScale('x');
				setScale('y');
				setScale('z');

			}
			setScales();
			function setRotations(t) {

				t = t || pointsOptions.tMin;
				function setRotation(axisName) {

					points.rotation[axisName] = typeof pointsOptions.rotation[axisName] === "function" ?
						pointsOptions.rotation[axisName](t, options.a, options.b) :
						pointsOptions.rotation[axisName];
					while (points.rotation[axisName] < 0) points.rotation[axisName] += Math.PI * 2;
					while (points.rotation[axisName] > Math.PI * 2) points.rotation[axisName] -= Math.PI * 2

				}
				setRotation('x');
				setRotation('y');
				setRotation('z');

			}
			setRotations();
			group.add(points);

			points.userData.myObject = _this;
			points.userData.opacity = (opacity) => { _this.verticesOpacity(false, opacity); }

			if (pointsOptions.boFrustumPoints) points.userData.boFrustumPoints = pointsOptions.boFrustumPoints;
			if (options.guiSelectPoint) options.guiSelectPoint.addMesh(points);
			if (options.eventListeners) options.eventListeners.addParticle(points);
			if (pointsOptions.onReady !== undefined) pointsOptions.onReady(points);

			if (!points.userData.boFrustumPoints && options.raycaster && options.raycaster.addParticle)
				options.raycaster.addParticle(points);

		}

	}
//	get defaultColor() { return 'white'; }

}
export default MyPoints;