Source: lib/xutils/xcommon.js

import * as THREE from 'three';

/**
 * Stub for jsdoc, a collection of common methods
 * <b>NOTE: no 'xutils.' prefix when calling functions.</b>
 * <br>This class name is all in lower case. X-visual use this convention for a
 * collection of common global methods when using jsdoc generating API doc.
 * @class xutils
 */
function xutils() { }

/**
 * example: await sleep(1000);
 * @param {number} ms
 * @member xutils.sleep
 * @function
 */
export function sleep(ms) {
	return new Promise(resolve => setTimeout(resolve, ms));
}

/**
 * Get a texture generated in memory.
 * @param {number} w 0 - 1
 * @param {number} h 0 - 1
 * @param {object} options
 * alpha: 0 - 1<br>
 * color: number, optional, if not exists, will create a blocking texture.
 * @return {Unit8Array} size = w * h * 4
 * @member xutils.ramTexture
 * @function
 */
export function ramTexture(w, h, options) {
	var size = w * h;
	var data = new Uint8Array( 4 * size );

	var a = Math.floor(
		(options.alpha === undefined ? 0 : options.alpha) * 255 );

	for ( var i = 0; i < size; i ++ ) {

		var stride = i * 4;

		if (typeof options.color === 'number') {
			data[ stride ] = options.color >> 8;
			data[ stride + 1 ] = options.color >> 16;
			data[ stride + 2 ] = options.color >> 24;
			data[ stride + 3 ] = options.color;
		}
		else {
			var x = i + 127
			data[ stride ] = (x % w) / w * 255;
			data[ stride + 1 ] = (x / w) / h * 255;
			data[ stride + 2 ] = (data[ stride ] + data[ stride + 1]) / 2;
			data[ stride + 3 ] = a;
		}
	}

	var texture = new THREE.DataTexture( data, w, h, THREE.RGBAFormat );
	return texture;
}

/**
 * Get a randome integer, between min (0), max
 * @param {number} min
 * @param {number} [max]
 * @member xutils.randInt
 * @function
 */
export function randInt(min, max) {
	if (max === undefined) {
		max = min;
		min = 0;
	}
	return Math.random() * (max - min) + min | 0;
}

/**
 * Get a randome color
 * @return {string} e.g. '0x0f3271'
 * @member xutils.randomColor
 * @function
 */
export function randomColor() {
	return `#${randInt(0x1000000).toString(16).padStart(6, '0')}`
}

/**
 * Get a randome color
 * @return {THREE.Color} color
 * @member xutils.randomRGB
 * @function
 */
export function randomRGB() {
	var color = new THREE.Color(randInt(0, 0xffffff));
	return color;
}

/**
 * Get color array that's common in shader uniform parameter format<br>
 * Stolen from <a href='https://github.com/mrdoob/three.js/blob/fd046cbc06b7a93ed2e34090e7fe19c72afdde9f/src/math/Color.js#L151'>
 * Three.js/Color#setStyle</a>.
 * @param {number} min
 * @return {array} color [r, g, b], without 'a'.
 * @member xutils.cssColor
 * @function
 */
 export function cssColor( style ) {
	function handleAlpha( string ) {
		if ( string === undefined ) return;
		if ( parseFloat( string ) < 1 ) {
			console.warn( 'xutils.cssColor: Alpha component of ' + style + ' will be ignored.' );
		}
	}
	function clamp( value, min, max ) {
		return Math.max( min, Math.min( max, value ) );
	}
	function hue2rgb( p, q, t ) {
		if ( t < 0 ) t += 1;
		if ( t > 1 ) t -= 1;
		if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t;
		if ( t < 1 / 2 ) return q;
		if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t );
		return p;
	}
	function setHSL( h, s, l ) {
		let r, g, b;
		// h,s,l ranges are in 0.0 - 1.0
		h = ( ( h % 1 ) + 1 ) % 1;
		s = clamp( s, 0, 1 );
		l = clamp( l, 0, 1 );
		if ( s === 0 ) {
			r = g = b = l;
		} else {
			const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s );
			const q = ( 2 * l ) - p;
			r = hue2rgb( q, p, h + 1 / 3 );
			g = hue2rgb( q, p, h );
			b = hue2rgb( q, p, h - 1 / 3 );
		}
		return [r, g, b];
	}
	function hexNum(hex) {
		return [( hex >> 16 & 255 ) / 255,
				( hex >> 8 & 255 ) / 255,
				( hex & 255 ) / 255 ];
	}
	function hexString(css) {
		var hex = Math.floor( eval(css) );
		return hexNum(hex);
	}

	let m;
	if ( m = /^((?:rgb|hsl)a?)\(\s*([^\)]*)\)/.exec( style ) ) {
		// rgb / hsl
		let color;
		const name = m[ 1 ];
		const components = m[ 2 ];

		switch ( name ) {
			case 'rgb':
			case 'rgba':
				if ( color = /^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) {
					// rgb(255,0,0) rgba(255,0,0,0.5)
					let r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255;
					let g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255;
					let b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255;

					handleAlpha( color[ 5 ] );

					return [r, g, b];
				}

				if ( color = /^(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) {

					// rgb(100%,0%,0%) rgba(100%,0%,0%,0.5)
					let r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100;
					let g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100;
					let b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100;

					handleAlpha( color[ 5 ] );
					return [r, g, b];
				}
				break;
			case 'hsl':
			case 'hsla':
				if ( color = /^([0-9]*\.?[0-9]+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) {
					// hsl(120,50%,50%) hsla(120,50%,50%,0.5)
					const h = parseFloat( color[ 1 ] ) / 360;
					const s = parseInt( color[ 2 ], 10 ) / 100;
					const l = parseInt( color[ 3 ], 10 ) / 100;

					debugger
					handleAlpha( color[ 5 ] );
					return setHSL( h, s, l );
				}
				break;
		}
	}
	else if ( m = /^\#([A-Fa-f0-9]+)$/.exec( style ) ) {
		// hex color
		const hex = m[ 1 ];
		const size = hex.length;
		if ( size === 3 ) {
			// #ff0
			let r = parseInt( hex.charAt( 0 ) + hex.charAt( 0 ), 16 ) / 255;
			let g = parseInt( hex.charAt( 1 ) + hex.charAt( 1 ), 16 ) / 255;
			let b = parseInt( hex.charAt( 2 ) + hex.charAt( 2 ), 16 ) / 255;
			return [r, g, b];
		} else if ( size === 6 ) {
			// #ff0000
			let r = parseInt( hex.charAt( 0 ) + hex.charAt( 1 ), 16 ) / 255;
			let g = parseInt( hex.charAt( 2 ) + hex.charAt( 3 ), 16 ) / 255;
			let b = parseInt( hex.charAt( 4 ) + hex.charAt( 5 ), 16 ) / 255;
			return [r, g, b];
		}
	}
	else if ( m = /^0[Xx]([A-Fa-f0-9]+)$/.exec( style ) ) {
		return hexString(style);
	}
	else if (typeof style === 'number') {
		return hexString(style)
	}

	if ( style && style.length > 0 ) {
		// return this.setColorName( style );
		console.error("TODO handle color name: ", style);
	}
	return [0, 0, 0];
}

/** Get browser name and version.
 * @param {bool} log true will log with console.
 * @return {object} {name, version}
 * @function
 */
export function browserVer(log) {
    var ua = navigator.userAgent,
		tem,
		M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
    if (/trident/i.test(M[1])) {
        tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
        return { name:'IE', version:(tem[1]||'') };
    }
    if (M[1] === 'Chrome') {
        tem = ua.match(/\bOPR|Edge\/(\d+)/)
        if (tem != null) {
			return {name:'Opera', version:tem[1]};
		}
    }
    M = M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
    if ( (tem = ua.match(/version\/(\d+)/i) ) != null ) {
		M.splice(1, 1, tem[1]);
	}
    var rep = {
		name: M[0],
		version: M[1]
    };
	if (log) console.log(rep);
	return rep;
}

/**
 * example: throw new XError('msg', 0);
 * @param {number} err error message
 * @param {number} [code] error code
 * @class XError
 */
export function XError(err, code) {
	this.code = code;
	this.message = err;
	this.name = 'XError';
}