text: import { THREE } from 'expo-three';
  
  /*
   * GPU Particle System
   * @author flimshaw - Charlie Hoey - http://charliehoey.com
   *
   * A simple to use, general purpose GPU system. Particles are spawn-and-forget with
   * several options available, and do not require monitoring or cleanup after spawning.
   * Because the paths of all particles are completely deterministic once spawned, the scale
   * and direction of time is also variable.
   *
   * Currently uses a static wrapping perlin noise texture for turbulence, and a small png texture for
   * particles, but adding support for a particle texture atlas or changing to a different type of turbulence
   * would be a fairly light day's work.
   *
   * Shader and javascript packing code derrived from several Stack Overflow examples.
   * 
   * https://github.com/mrdoob/three.js/blob/dev/examples/js/GPUParticleSystem.js
   * 
   */
  
  const GPUParticleSystem = function(options) {
  	THREE.Object3D.apply(this, arguments);
  
  	options = options || {};
  
  	// parse options and use defaults
  
  	this.PARTICLE_COUNT = options.maxParticles || 1000000;
  	this.PARTICLE_CONTAINERS = options.containerCount || 1;
  
  	this.PARTICLE_NOISE_TEXTURE = options.particleNoiseTex || null;
  	this.PARTICLE_SPRITE_TEXTURE = options.particleSpriteTex || null;
  
  	this.PARTICLES_PER_CONTAINER = Math.ceil(
  		this.PARTICLE_COUNT / this.PARTICLE_CONTAINERS
  	);
  	this.PARTICLE_CURSOR = 0;
  	this.time = 0;
  	this.particleContainers = [];
  	this.rand = [];
  
  	// custom vertex and fragement shader
  
  	var GPUParticleShader = {
  		vertexShader: `
  			uniform float uTime;
  			uniform float uScale;
  			uniform sampler2D tNoise;
  
  			attribute vec3 positionStart;
  			attribute float startTime;
  			attribute vec3 velocity;
  			attribute float turbulence;
  			attribute vec3 color;
  			attribute float size;
  			attribute float lifeTime;
  
  			varying vec4 vColor;
  			varying float lifeLeft;
  
  			void main() {
  				// unpack things from our attributes'
  
  				vColor = vec4( color, 1.0 );
  
  				// convert our velocity back into a value we can use'
  
  				vec3 newPosition;
  				vec3 v;
  
  				float timeElapsed = uTime - startTime;
  
  				lifeLeft = 1.0 - ( timeElapsed / lifeTime );
  
  				gl_PointSize = ( uScale * size ) * lifeLeft;
  
  				v.x = ( velocity.x - 0.5 ) * 3.0;
  				v.y = ( velocity.y - 0.5 ) * 3.0;
  				v.z = ( velocity.z - 0.5 ) * 3.0;
  
  				newPosition = positionStart + ( v * 10.0 ) * timeElapsed;
  
  				vec3 noise = texture2D( tNoise, vec2( newPosition.x * 0.015 + ( uTime * 0.05 ), newPosition.y * 0.02 + ( uTime * 0.015 ) ) ).rgb;
  				vec3 noiseVel = ( noise.rgb - 0.5 ) * 30.0;
  
  				newPosition = mix( newPosition, newPosition + vec3( noiseVel * ( turbulence * 5.0 ) ), ( timeElapsed / lifeTime ) );
  
  				if( v.y > 0. && v.y < .05 ) {
  					lifeLeft = 0.0;
  				}
  
  				if( v.x < - 1.45 ) {
  					lifeLeft = 0.0;
  				}
  
  				if( timeElapsed > 0.0 ) {
  					gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );
  				} else {
  					gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  					lifeLeft = 0.0;
  					gl_PointSize = 0.;
  				}
  			}`,
  
  		fragmentShader: `
  			float scaleLinear( float value, vec2 valueDomain ) {
  				return ( value - valueDomain.x ) / ( valueDomain.y - valueDomain.x );
  			}
  
  			float scaleLinear( float value, vec2 valueDomain, vec2 valueRange ) {
  				return mix( valueRange.x, valueRange.y, scaleLinear( value, valueDomain ) );
  			}
  
  			varying vec4 vColor;
  			varying float lifeLeft;
  
  			uniform sampler2D tSprite;
  
  			void main() {
  				float alpha = 0.;
  
  				if( lifeLeft > 0.995 ) {
  					alpha = scaleLinear( lifeLeft, vec2( 1.0, 0.995 ), vec2( 0.0, 1.0 ) );
  				} else {
  					alpha = lifeLeft * 0.75;
  				}
  
  				vec4 tex = texture2D( tSprite, gl_PointCoord );
  				gl_FragColor = vec4( vColor.rgb * tex.a, alpha * tex.a );
  			}
  		`
  	};
  
  	// preload a million random numbers
  
  	var i;
  
  	for (i = 1e5; i > 0; i--) {
  		this.rand.push(Math.random() - 0.5);
  	}
  
  	this.random = function() {
  		return ++i >= this.rand.length ? this.rand[(i = 1)] : this.rand[i];
  	};
  
  	this.particleNoiseTex = this.PARTICLE_NOISE_TEXTURE;
  	this.particleNoiseTex.wrapS = this.particleNoiseTex.wrapT = THREE.RepeatWrapping;
  
  	this.particleSpriteTex = this.PARTICLE_SPRITE_TEXTURE;
  	this.particleSpriteTex.wrapS = this.particleSpriteTex.wrapT = THREE.RepeatWrapping;
  
  	this.particleShaderMat = new THREE.ShaderMaterial({
  		transparent: true,
  		depthWrite: false,
  		uniforms: {
  			uTime: {
  				value: 0.0
  			},
  			uScale: {
  				value: 1.0
  			},
  			tNoise: {
  				value: this.particleNoiseTex
  			},
  			tSprite: {
  				value: this.particleSpriteTex
  			}
  		},
  		blending: THREE.AdditiveBlending,
  		vertexShader: GPUParticleShader.vertexShader,
  		fragmentShader: GPUParticleShader.fragmentShader
  	});
  
  	// define defaults for all values
  
  	this.particleShaderMat.defaultAttributeValues.particlePositionsStartTime = [
  		0,
  		0,
  		0,
  		0
  	];
  	this.particleShaderMat.defaultAttributeValues.particleVelColSizeLife = [
  		0,
  		0,
  		0,
  		0
  	];
  
  	this.init = function() {
  		for (var i = 0; i < this.PARTICLE_CONTAINERS; i++) {
  			var c = new GPUParticleContainer(
  				this.PARTICLES_PER_CONTAINER,
  				this
  			);
  			this.particleContainers.push(c);
  			this.add(c);
  		}
  	};
  
  	this.spawnParticle = function(options) {
  		this.PARTICLE_CURSOR++;
  
  		if (this.PARTICLE_CURSOR >= this.PARTICLE_COUNT) {
  			this.PARTICLE_CURSOR = 1;
  		}
  
  		var currentContainer = this.particleContainers[
  			Math.floor(this.PARTICLE_CURSOR / this.PARTICLES_PER_CONTAINER)
  		];
  
  		currentContainer.spawnParticle(options);
  	};
  
  	this.update = function(time) {
  		for (var i = 0; i < this.PARTICLE_CONTAINERS; i++) {
  			this.particleContainers[i].update(time);
  		}
  	};
  
  	this.dispose = function() {
  		this.particleShaderMat.dispose();
  		this.particleNoiseTex.dispose();
  		this.particleSpriteTex.dispose();
  
  		for (var i = 0; i < this.PARTICLE_CONTAINERS; i++) {
  			this.particleContainers[i].dispose();
  		}
  	};
  
  	this.init();
  };
  
  GPUParticleSystem.prototype = Object.create( THREE.Object3D.prototype );
  GPUParticleSystem.prototype.constructor = GPUParticleSystem;
  
  // Subclass for particle containers, allows for very large arrays to be spread out
  
  const GPUParticleContainer = function ( maxParticles, particleSystem ) {
  	THREE.Object3D.apply( this, arguments );
  
  	this.PARTICLE_COUNT = maxParticles || 100000;
  	this.PARTICLE_CURSOR = 0;
  	this.time = 0;
  	this.offset = 0;
  	this.count = 0;
  	this.DPR = window.devicePixelRatio;
  	this.GPUParticleSystem = particleSystem;
  	this.particleUpdate = false;
  
  	// geometry
  
  	this.particleShaderGeo = new THREE.BufferGeometry();
  
  	this.particleShaderGeo.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT * 3 ), 3 ).setDynamic( true ) );
  	this.particleShaderGeo.addAttribute( 'positionStart', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT * 3 ), 3 ).setDynamic( true ) );
  	this.particleShaderGeo.addAttribute( 'startTime', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT ), 1 ).setDynamic( true ) );
  	this.particleShaderGeo.addAttribute( 'velocity', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT * 3 ), 3 ).setDynamic( true ) );
  	this.particleShaderGeo.addAttribute( 'turbulence', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT ), 1 ).setDynamic( true ) );
  	this.particleShaderGeo.addAttribute( 'color', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT * 3 ), 3 ).setDynamic( true ) );
  	this.particleShaderGeo.addAttribute( 'size', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT ), 1 ).setDynamic( true ) );
  	this.particleShaderGeo.addAttribute( 'lifeTime', new THREE.BufferAttribute( new Float32Array( this.PARTICLE_COUNT ), 1 ).setDynamic( true ) );
  
  	// material
  
  	this.particleShaderMat = this.GPUParticleSystem.particleShaderMat;
  
  	var position = new THREE.Vector3();
  	var velocity = new THREE.Vector3();
  	var color = new THREE.Color();
  
  	this.spawnParticle = function ( options ) {
  		var positionStartAttribute = this.particleShaderGeo.getAttribute( 'positionStart' );
  		var startTimeAttribute = this.particleShaderGeo.getAttribute( 'startTime' );
  		var velocityAttribute = this.particleShaderGeo.getAttribute( 'velocity' );
  		var turbulenceAttribute = this.particleShaderGeo.getAttribute( 'turbulence' );
  		var colorAttribute = this.particleShaderGeo.getAttribute( 'color' );
  		var sizeAttribute = this.particleShaderGeo.getAttribute( 'size' );
  		var lifeTimeAttribute = this.particleShaderGeo.getAttribute( 'lifeTime' );
  
  		options = options || {};
  
  		// setup reasonable default values for all arguments
  
  		position = options.position !== undefined ? position.copy( options.position ) : position.set( 0, 0, 0 );
  		velocity = options.velocity !== undefined ? velocity.copy( options.velocity ) : velocity.set( 0, 0, 0 );
  		color = options.color !== undefined ? color.set( options.color ) : color.set( 0xffffff );
  
  		var positionRandomness = options.positionRandomness !== undefined ? options.positionRandomness : 0;
  		var velocityRandomness = options.velocityRandomness !== undefined ? options.velocityRandomness : 0;
  		var colorRandomness = options.colorRandomness !== undefined ? options.colorRandomness : 1;
  		var turbulence = options.turbulence !== undefined ? options.turbulence : 1;
  		var lifetime = options.lifetime !== undefined ? options.lifetime : 5;
  		var size = options.size !== undefined ? options.size : 10;
  		var sizeRandomness = options.sizeRandomness !== undefined ? options.sizeRandomness : 0;
  		var smoothPosition = options.smoothPosition !== undefined ? options.smoothPosition : false;
  
  		if ( this.DPR !== undefined ) size *= this.DPR;
  
  		var i = this.PARTICLE_CURSOR;
  
  		// position
  
  		positionStartAttribute.array[ i * 3 + 0 ] = position.x + ( particleSystem.random() * positionRandomness );
  		positionStartAttribute.array[ i * 3 + 1 ] = position.y + ( particleSystem.random() * positionRandomness );
  		positionStartAttribute.array[ i * 3 + 2 ] = position.z + ( particleSystem.random() * positionRandomness );
  
  		if ( smoothPosition === true ) {
  			positionStartAttribute.array[ i * 3 + 0 ] += - ( velocity.x * particleSystem.random() );
  			positionStartAttribute.array[ i * 3 + 1 ] += - ( velocity.y * particleSystem.random() );
  			positionStartAttribute.array[ i * 3 + 2 ] += - ( velocity.z * particleSystem.random() );
  		}
  
  		// velocity
  
  		var maxVel = 2;
  
  		var velX = velocity.x + particleSystem.random() * velocityRandomness;
  		var velY = velocity.y + particleSystem.random() * velocityRandomness;
  		var velZ = velocity.z + particleSystem.random() * velocityRandomness;
  
  		velX = THREE.Math.clamp( ( velX - ( - maxVel ) ) / ( maxVel - ( - maxVel ) ), 0, 1 );
  		velY = THREE.Math.clamp( ( velY - ( - maxVel ) ) / ( maxVel - ( - maxVel ) ), 0, 1 );
  		velZ = THREE.Math.clamp( ( velZ - ( - maxVel ) ) / ( maxVel - ( - maxVel ) ), 0, 1 );
  
  		velocityAttribute.array[ i * 3 + 0 ] = velX;
  		velocityAttribute.array[ i * 3 + 1 ] = velY;
  		velocityAttribute.array[ i * 3 + 2 ] = velZ;
  
  		// color
  
  		color.r = THREE.Math.clamp( color.r + particleSystem.random() * colorRandomness, 0, 1 );
  		color.g = THREE.Math.clamp( color.g + particleSystem.random() * colorRandomness, 0, 1 );
  		color.b = THREE.Math.clamp( color.b + particleSystem.random() * colorRandomness, 0, 1 );
  
  		colorAttribute.array[ i * 3 + 0 ] = color.r;
  		colorAttribute.array[ i * 3 + 1 ] = color.g;
  		colorAttribute.array[ i * 3 + 2 ] = color.b;
  
  		// turbulence, size, lifetime and starttime
  
  		turbulenceAttribute.array[ i ] = turbulence;
  		sizeAttribute.array[ i ] = size + particleSystem.random() * sizeRandomness;
  		lifeTimeAttribute.array[ i ] = lifetime;
  		startTimeAttribute.array[ i ] = this.time + particleSystem.random() * 2e-2;
  
  		// offset
  
  		if ( this.offset === 0 ) {
  			this.offset = this.PARTICLE_CURSOR;
  		}
  
  		// counter and cursor
  
  		this.count ++;
  		this.PARTICLE_CURSOR ++;
  
  		if ( this.PARTICLE_CURSOR >= this.PARTICLE_COUNT ) {
  			this.PARTICLE_CURSOR = 0;
  		}
  
  		this.particleUpdate = true;
  	};
  
  	this.init = function () {
  		this.particleSystem = new THREE.Points( this.particleShaderGeo, this.particleShaderMat );
  		this.particleSystem.frustumCulled = false;
  		this.add( this.particleSystem );
  	};
  
  	this.update = function ( time ) {
  		this.time = time;
  		this.particleShaderMat.uniforms.uTime.value = time;
  
  		this.geometryUpdate();
  	};
  
  	this.geometryUpdate = function () {
  		if (this.particleUpdate === true) {
  
  			this.particleUpdate = false;
  
  			var positionStartAttribute = this.particleShaderGeo.getAttribute( 'positionStart' );
  			var startTimeAttribute = this.particleShaderGeo.getAttribute( 'startTime' );
  			var velocityAttribute = this.particleShaderGeo.getAttribute( 'velocity' );
  			var turbulenceAttribute = this.particleShaderGeo.getAttribute( 'turbulence' );
  			var colorAttribute = this.particleShaderGeo.getAttribute( 'color' );
  			var sizeAttribute = this.particleShaderGeo.getAttribute( 'size' );
  			var lifeTimeAttribute = this.particleShaderGeo.getAttribute( 'lifeTime' );
  
  			if ( this.offset + this.count < this.PARTICLE_COUNT ) {
  
  				positionStartAttribute.updateRange.offset = this.offset * positionStartAttribute.itemSize;
  				startTimeAttribute.updateRange.offset = this.offset * startTimeAttribute.itemSize;
  				velocityAttribute.updateRange.offset = this.offset * velocityAttribute.itemSize;
  				turbulenceAttribute.updateRange.offset = this.offset * turbulenceAttribute.itemSize;
  				colorAttribute.updateRange.offset = this.offset * colorAttribute.itemSize;
  				sizeAttribute.updateRange.offset = this.offset * sizeAttribute.itemSize;
  				lifeTimeAttribute.updateRange.offset = this.offset * lifeTimeAttribute.itemSize;
  
  				positionStartAttribute.updateRange.count = this.count * positionStartAttribute.itemSize;
  				startTimeAttribute.updateRange.count = this.count * startTimeAttribute.itemSize;
  				velocityAttribute.updateRange.count = this.count * velocityAttribute.itemSize;
  				turbulenceAttribute.updateRange.count = this.count * turbulenceAttribute.itemSize;
  				colorAttribute.updateRange.count = this.count * colorAttribute.itemSize;
  				sizeAttribute.updateRange.count = this.count * sizeAttribute.itemSize;
  				lifeTimeAttribute.updateRange.count = this.count * lifeTimeAttribute.itemSize;
  
  			} else {
  
  				positionStartAttribute.updateRange.offset = 0;
  				startTimeAttribute.updateRange.offset = 0;
  				velocityAttribute.updateRange.offset = 0;
  				turbulenceAttribute.updateRange.offset = 0;
  				colorAttribute.updateRange.offset = 0;
  				sizeAttribute.updateRange.offset = 0;
  				lifeTimeAttribute.updateRange.offset = 0;
  
  				// Use -1 to update the entire buffer, see #11476
  				positionStartAttribute.updateRange.count = - 1;
  				startTimeAttribute.updateRange.count = - 1;
  				velocityAttribute.updateRange.count = - 1;
  				turbulenceAttribute.updateRange.count = - 1;
  				colorAttribute.updateRange.count = - 1;
  				sizeAttribute.updateRange.count = - 1;
  				lifeTimeAttribute.updateRange.count = - 1;
  
  			}
  
  			positionStartAttribute.needsUpdate = true;
  			startTimeAttribute.needsUpdate = true;
  			velocityAttribute.needsUpdate = true;
  			turbulenceAttribute.needsUpdate = true;
  			colorAttribute.needsUpdate = true;
  			sizeAttribute.needsUpdate = true;
  			lifeTimeAttribute.needsUpdate = true;
  
  			this.offset = 0;
  			this.count = 0;
  		}
  	};
  
  	this.dispose = function () {
  		this.particleShaderGeo.dispose();
  	};
  
  	this.init();
  
  };
  
  GPUParticleContainer.prototype = Object.create( THREE.Object3D.prototype );
  GPUParticleContainer.prototype.constructor = GPUParticleContainer;
  
  export default GPUParticleSystem;