Source: getShaderMaterialPoints.js

/**
 * @module getShaderMaterialPoints
 * @description get THREE.Points with THREE.ShaderMaterial material
 * @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 three from '../three.js'
import Player from '../player/player.js';
import Options from '../Options.js'
import MyObject from '../myObject.js'

//Thanks to https://stackoverflow.com/a/27369985/5175935
//Такая же функция есть в frustumPoints.js но если ее использовать то она будет возвращать путь на frustumPoints.js
const getCurrentScript = function () {

	if ( document.currentScript && ( document.currentScript.src !== '' ) )
		return document.currentScript.src;
	const scripts = document.getElementsByTagName( 'script' ),
		str = scripts[scripts.length - 1].src;
	if ( str !== '' )
		return src;
	//Thanks to https://stackoverflow.com/a/42594856/5175935
	return new Error().stack.match( /(https?:[^:]*)/ )[0];

};
//Thanks to https://stackoverflow.com/a/27369985/5175935
const getCurrentScriptPath = function () {
	const script = getCurrentScript(),
		path = script.substring( 0, script.lastIndexOf( '/' ) );
	return path;
};
const currentScriptPath = getCurrentScriptPath(),
	_vertex_text = {

		array: [],
		setItem: function ( path, text ) { this.array.push( { path: path, text: text } ) },
		getItem: function ( path ) {

			for ( var i = 0; i < this.array.length; i++ ) { if ( this.array[i].path === path ) return this.array[i].text; }

		}

	},
	_fragment_text = {

		array: [],
		setItem: function ( path, text ) { this.array.push( { path: path, text: text } ) },
		getItem: function ( path ) {

			for ( var i = 0; i < this.array.length; i++ ) { if ( this.array[i].path === path ) return this.array[i].text; }

		}

	};

/**
 * Gets [THREE.Points]{@link https://threejs.org/docs/index.html?q=Points#api/en/objects/Points} with [THREE.ShaderMaterial]{@link https://threejs.org/docs/index.html?q=ShaderMaterial#api/en/materials/ShaderMaterial} material.
 * Extends <a href="../../jsdoc/MyObject/module-myObject-MyObject.html" target="_blank">MyObject</a>.
 * @class
 * @extends MyObject
 */
class getShaderMaterialPoints extends MyObject {

	/**
	 * Gets [THREE.Points]{@link https://threejs.org/docs/index.html?q=Points#api/en/objects/Points} with [THREE.ShaderMaterial]{@link https://threejs.org/docs/index.html?q=ShaderMaterial#api/en/materials/ShaderMaterial} material
	 * @param {THREE.Group|THREE.Scene} group [THREE.Group]{@link https://threejs.org/docs/index.html?q=group#api/en/objects/Group} or [THREE.Scene]{@link https://threejs.org/docs/index.html?q=sce#api/en/scenes/Scene}.
	 * @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 {function(THREE.Points)} onReady Callback function that take as input the new THREE.Points.
	 * @param {object} [settings={}]
	 * @param {number} [settings.tMin=0] start time. Uses for playing of the points.
	 * @param {object} [settings.pointsOptions] points options.
	 * See <b>settings.pointsOptions</b> of <a href="../../MyPoints/jsdoc/module-MyPoints.html" target="_blank">MyPoints</a> for details.
	 * @param {object} [settings.options={}] see <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> <b>options</b> parameter for details
	 * @param {Object} [settings.options.point] point options.
	 * See <a href="../../jsdoc/Options/Options.html#point" target="_blank">Options get point</a> member for details.
	 * @param {number} [settings.options.scales] Axis scales.
	 * See  <b>options.scales</b> of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> for details.
	 * @param {object} [settings.options.scales.w] w axis scale.
	 * See <a href="../../jsdoc/Options/Options.html#setW" target="_blank">Options.setW(options)</a> for details.
	*/
	constructor(group, arrayFuncs, onReady, settings = {}) {

		super(settings, arrayFuncs);

		const THREE = three.THREE, tMin = settings.pointsOptions === undefined ?
			settings.tMin === undefined ? 0 : settings.tMin :
			settings.pointsOptions.tMin === undefined ? 0 : settings.pointsOptions.tMin;

		settings.options = settings.options || new Options();

		settings.pointsOptions = settings.pointsOptions || {};

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

		var geometry;
		if (arrayFuncs instanceof THREE.BufferGeometry) {

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

		} else if (typeof arrayFuncs === 'function')
			geometry = arrayFuncs();
		else {

			if (settings.pointsOptions.bufferGeometry)
				geometry = settings.pointsOptions.bufferGeometry;
			else {
				
				const points = arrayFuncs != undefined ? Player.getPoints(arrayFuncs, { options: settings.options, group: group, t: tMin, }) : undefined;
				this.getPoint = (i) => { return points[i]; }
				geometry = this.setPositionAttributeFromPoints(points);

			}

		}
		const indexArrayCloud = settings.pointsOptions.frustumPoints ? settings.pointsOptions.frustumPoints.pushArrayCloud(geometry) : undefined;//индекс массива точек в FrustumPoints.arrayCloud которые принадлежат этому points
/*
		if ((settings.pointsOptions === undefined) || !settings.pointsOptions.boFrustumPoints) {

			//если не делать эту проверку, то будет неправильный цвет точки, если не задана палитра и шкала w
			if (!settings.options.scales.w) settings.options.scales.setW();
			geometry.setAttribute(
				'color',
				new THREE.Float32BufferAttribute(Player.getColors
				(arrayFuncs,
					{

						color: settings.pointsOptions.color,
						colors: settings.pointsOptions.colors,
						opacity: settings.pointsOptions === undefined ? undefined : settings.pointsOptions.opacity,
						positions: geometry.attributes.position,
						options: settings.options,

					}),
				4));

		}
*/		

		const texture = new THREE.TextureLoader().load(currentScriptPath + "/textures/point.png",
			function (texture) { }, function () { }, function () { console.error('THREE.TextureLoader: error'); });
		texture.wrapS = THREE.RepeatWrapping;
		texture.wrapT = THREE.RepeatWrapping;

		const uniforms = {

			pointTexture: { value: texture },

			pointSize: {

				value: (settings.pointsOptions !== undefined) && (settings.pointsOptions.shaderMaterial !== undefined) && (settings.pointsOptions.shaderMaterial.point !== undefined) ?
					settings.pointsOptions.shaderMaterial.point.size :
					settings.options.point.size

			},

		}

		var cloud;
		if ((settings.pointsOptions !== undefined) && (settings.pointsOptions.uniforms !== undefined))
			cloud = settings.pointsOptions.uniforms(uniforms);


		/**
		 * Loading of the vertex and fragment contents from external files.
		 * Thanks to https://stackoverflow.com/a/48188509/5175935
		 * @param {function()} onLoad Callback function that called after success loading.
		 * */
		function loadShaderText(onload, path) {

			const shaderText = {};

			/**
			 * This is a basic asyncronous shader loader for THREE.js.
			 * Thanks to https://www.davideaversa.it/2016/10/three-js-shader-loading-external-file/
			 * https://github.com/THeK3nger/threejs-async-shaders-example
			 * 
			 * It uses the built-in THREE.js async loading capabilities to load shaders from files!
			 * 
			 * `onProgress` and `onError` are stadard TREE.js stuff. Look at 
			 * https://threejs.org/examples/webgl_loader_obj.html for an example. 
			 * 
			 * @param {String} vertex_url URL to the vertex shader code.
			 * @param {String} fragment_url URL to fragment shader code
			 * @param {function(String, String)} onLoad Callback function(vertex, fragment) that take as input the loaded vertex and fragment contents.
			 * @param {object} [options] the following options are available
			 * @param {function(event)} [options.onProgress] Callback for the `onProgress` event.
			 * @param {function(event)} [options.onError] Callback for the `onError` event.
			 */
			function ShaderLoader(vertex_url, fragment_url, onLoad, options) {

				options = options || {};

				var vertex_text = _vertex_text.getItem(vertex_url);
				function loadFragment() {

					const fragment_text = _fragment_text.getItem(fragment_url);
					if (!fragment_text) {

						//load fragment.c file
						const fragment_loader = new THREE.FileLoader(THREE.DefaultLoadingManager);
						fragment_loader.setResponseType('text');
						fragment_loader.load(fragment_url, function (fragment_text) {

							_fragment_text.setItem(fragment_url, fragment_text);
							onLoad(vertex_text, fragment_text);

						}, options.onProgress, options.onError);

					} else onLoad(vertex_text, fragment_text);

				}
				if (!vertex_text) {

					//load vertex.c file
					const vertex_loader = new THREE.FileLoader(THREE.DefaultLoadingManager);
					vertex_loader.setResponseType('text');
					vertex_loader.load(vertex_url, function (vertex_text_new) {

						_vertex_text.setItem(vertex_url, vertex_text_new);
						vertex_text = vertex_text_new;
						loadFragment();

					}, options.onProgress, options.onError);

				} else {

					loadFragment();

				}

			}

			path = path || {};
			path.vertex = path.vertex || currentScriptPath + "/getShaderMaterialPoints/vertex.c";
			path.fragment = path.fragment || currentScriptPath + "/getShaderMaterialPoints/fragment.c";
			ShaderLoader(path.vertex, path.fragment,
				function (vertex, fragment) {

					shaderText.vertex = vertex;
					shaderText.fragment = fragment;
					onload(shaderText);

				},
				{

					onError: function (event) {

						console.error(event.srcElement.responseURL + ' status = ' + event.srcElement.status + ' ' + event.srcElement.statusText);

					}

				}

			);

		}

		loadShaderText(function (shaderText) {

			//See description of the
			//const int cloudPointsWidth = %s;
			//in the \frustumPoints\vertex.c
			if (cloud !== undefined) {

				cloud.editShaderText(shaderText);

			}

			const points = new THREE.Points(geometry, new THREE.ShaderMaterial({

				//See https://threejs.org/examples/webgl_custom_attributes_points2.html
				//D: \My documents\MyProjects\webgl\three.js\GitHub\three.js\dev\examples\webgl_custom_attributes_points2.html
				//OpenGL Shading Language https://www.khronos.org/opengl/wiki/OpenGL_Shading_Language
				//Обзор спецификации GLSL ES 2.0 http://a-gro-pro.blogspot.com/2013/06/glsl-es-20.html
				//Open GL 4. Язык шейдеров. Книга рецептов http://www.cosmic-rays.ru/books61/2015ShadingLanguage.pdf
				//OpenGL® 4.5 Reference Pages. Ключевые слова по алфавиту https://www.khronos.org/registry/OpenGL-Refpages/gl4/

				uniforms: uniforms,
				vertexShader: shaderText.vertex,
				fragmentShader: shaderText.fragment,
				transparent: true,

			}));
			points.userData.shaderMaterial = settings.pointsOptions === undefined ? settings.shaderMaterial : settings.pointsOptions.shaderMaterial;
			if (settings.options.saveMeshDefault !== undefined)
				settings.options.saveMeshDefault(points);
			if ((settings.pointsOptions) && (settings.pointsOptions.frustumPoints))
				points.userData.cloud = { indexArray: indexArrayCloud, }
			points.userData.shaderMaterial = settings.pointsOptions === undefined ? settings.shaderMaterial : settings.pointsOptions.shaderMaterial;
			onReady(points);

			//Convert all points with cloud and shaderMaterial from local to world positions
			// i.e. calculate scales, positions and rotation of the points.
			//Converting of all points with cloud, but not shaderMaterial see updateCloudPoint in the frustumPoints.create function
			if (points.userData.boFrustumPoints) {

				settings.pointsOptions.group.children.forEach(function (mesh) {

					settings.options.frustumPoints.updateCloudPoint(mesh);

				});

			}

			//нужно что бы обновились точки в frustumPoints
			if (points.material.uniforms.palette !== undefined)
				points.material.uniforms.palette.value.needsUpdate = true;
			if (points.material.uniforms.cloudPoints !== undefined)
				points.material.uniforms.cloudPoints.value.needsUpdate = true;
			//Player.selectMeshPlayScene(points, { options: settings.options });
			//Что бы камера смотрела на выбранную точку сразу после запуска приложения
			const cameraTarget = settings.options.playerOptions.cameraTarget.get(settings.options);
			if (cameraTarget && cameraTarget.setCameraPosition) cameraTarget.setCameraPosition();

		}, settings.pointsOptions === undefined ? undefined : settings.pointsOptions.path);

	}

}
export default getShaderMaterialPoints;