Code viewer for World: cloth simulation
	///https://threejs.org/examples/#webgl_animation_cloth
	https://github.com/mrdoob/three.js/blob/master/examples/webgl_animation_cloth.html
	
	
	//loaind in css
	AB.loadCSS ( '/uploads/threeport/main.css' ); 
	//import libarys needed
	
            import * as THREE from '/api/threemodule/libs/three.module.js';

            import Stats from '/uploads/threeport/stats.module.js';
			import { GUI } from '/uploads/threejs/dat.gui.module.js';
			import { OrbitControls } from '/uploads/threejs/OrbitControls.js';

			/*
			 * Cloth Simulation using a relaxed constraints solver
			 */

			// Suggested Readings

			// Advanced Character Physics by Thomas Jakobsen Character
			// http://freespace.virgin.net/hugo.elias/models/m_cloth.htm
			// http://en.wikipedia.org/wiki/Cloth_modeling
			// http://cg.alexandra.dk/tag/spring-mass-system/
			// Real-time Cloth Animation http://www.darwin3d.com/gamedev/articles/col0599.pdf

			const params = {
				enableWind: true,
				showBall: false,
				togglePins: togglePins
			};

			const DAMPING = 0.03;
			const DRAG = 1 - DAMPING;
			const MASS = 0.1;
			const restDistance = 25;

			const xSegs = 10;
			const ySegs = 10;

			const clothFunction = plane( restDistance * xSegs, restDistance * ySegs );

			const GRAVITY = 981 * 1.4;
			const gravity = new THREE.Vector3( 0, - GRAVITY, 0 ).multiplyScalar( MASS );


			const TIMESTEP = 18 / 1000;
			const TIMESTEP_SQ = TIMESTEP * TIMESTEP;

			let pins = [];

			const windForce = new THREE.Vector3( 0, 0, 0 );

			const ballPosition = new THREE.Vector3( 0, - 45, 0 );
			const ballSize = 60; //40

			const tmpForce = new THREE.Vector3();
			const diff = new THREE.Vector3();

			class Particle {

				constructor( x, y, z, mass ) {

					this.position = new THREE.Vector3();
					this.previous = new THREE.Vector3();
					this.original = new THREE.Vector3();
					this.a = new THREE.Vector3( 0, 0, 0 ); // acceleration
					this.mass = mass;
					this.invMass = 1 / mass;
					this.tmp = new THREE.Vector3();
					this.tmp2 = new THREE.Vector3();

					// init

					clothFunction( x, y, this.position ); // position
					clothFunction( x, y, this.previous ); // previous
					clothFunction( x, y, this.original );

				}

				// Force -> Acceleration

				addForce( force ) {

					this.a.add(
						this.tmp2.copy( force ).multiplyScalar( this.invMass )
					);

				}

				// Performs Verlet integration

				integrate( timesq ) {

					const newPos = this.tmp.subVectors( this.position, this.previous );
					newPos.multiplyScalar( DRAG ).add( this.position );
					newPos.add( this.a.multiplyScalar( timesq ) );

					this.tmp = this.previous;
					this.previous = this.position;
					this.position = newPos;

					this.a.set( 0, 0, 0 );

				}

			}

			class Cloth {

				constructor( w = 10, h = 10 ) {

					this.w = w;
					this.h = h;

					const particles = [];
					const constraints = [];

					// Create particles

					for ( let v = 0; v <= h; v ++ ) {

						for ( let u = 0; u <= w; u ++ ) {

							particles.push(
								new Particle( u / w, v / h, 0, MASS )
							);

						}

					}

					// Structural

					for ( let v = 0; v < h; v ++ ) {

						for ( let u = 0; u < w; u ++ ) {

							constraints.push( [
								particles[ index( u, v ) ],
								particles[ index( u, v + 1 ) ],
								restDistance
							] );

							constraints.push( [
								particles[ index( u, v ) ],
								particles[ index( u + 1, v ) ],
								restDistance
							] );

						}

					}

					for ( let u = w, v = 0; v < h; v ++ ) {

						constraints.push( [
							particles[ index( u, v ) ],
							particles[ index( u, v + 1 ) ],
							restDistance

						] );

					}

					for ( let v = h, u = 0; u < w; u ++ ) {

						constraints.push( [
							particles[ index( u, v ) ],
							particles[ index( u + 1, v ) ],
							restDistance
						] );

					}


					// While many systems use shear and bend springs,
					// the relaxed constraints model seems to be just fine
					// using structural springs.
					// Shear
					// const diagonalDist = Math.sqrt(restDistance * restDistance * 2);


					// for (v=0;v<h;v++) {
					// 	for (u=0;u<w;u++) {

					// 		constraints.push([
					// 			particles[index(u, v)],
					// 			particles[index(u+1, v+1)],
					// 			diagonalDist
					// 		]);

					// 		constraints.push([
					// 			particles[index(u+1, v)],
					// 			particles[index(u, v+1)],
					// 			diagonalDist
					// 		]);

					// 	}
					// }


					this.particles = particles;
					this.constraints = constraints;

					function index( u, v ) {

						return u + v * ( w + 1 );

					}

					this.index = index;

				}

			}

			function plane( width, height ) {

				return function ( u, v, target ) {

					const x = ( u - 0.5 ) * width;
					const y = ( v + 0.5 ) * height;
					const z = 0;

					target.set( x, y, z );

				};

			}

			function satisfyConstraints( p1, p2, distance ) {

				diff.subVectors( p2.position, p1.position );
				const currentDist = diff.length();
				if ( currentDist === 0 ) return; // prevents division by 0
				const correction = diff.multiplyScalar( 1 - distance / currentDist );
				const correctionHalf = correction.multiplyScalar( 0.5 );
				p1.position.add( correctionHalf );
				p2.position.sub( correctionHalf );

			}

			function simulate( now ) {

				const windStrength = Math.cos( now / 7000 ) * 20 + 40;

				windForce.set( Math.sin( now / 2000 ), Math.cos( now / 3000 ), Math.sin( now / 1000 ) );
				windForce.normalize();
				windForce.multiplyScalar( windStrength );

				// Aerodynamics forces

				const particles = cloth.particles;

				if ( params.enableWind ) {

					let indx;
					const normal = new THREE.Vector3();
					const indices = clothGeometry.index;
					const normals = clothGeometry.attributes.normal;

					for ( let i = 0, il = indices.count; i < il; i += 3 ) {

						for ( let j = 0; j < 3; j ++ ) {

							indx = indices.getX( i + j );
							normal.fromBufferAttribute( normals, indx );
							tmpForce.copy( normal ).normalize().multiplyScalar( normal.dot( windForce ) );
							particles[ indx ].addForce( tmpForce );

						}

					}

				}

				for ( let i = 0, il = particles.length; i < il; i ++ ) {

					const particle = particles[ i ];
					particle.addForce( gravity );

					particle.integrate( TIMESTEP_SQ );

				}

				// Start Constraints

				const constraints = cloth.constraints;
				const il = constraints.length;

				for ( let i = 0; i < il; i ++ ) {

					const constraint = constraints[ i ];
					satisfyConstraints( constraint[ 0 ], constraint[ 1 ], constraint[ 2 ] );

				}

				// Ball Constraints

				ballPosition.z = - Math.sin( now / 600 ) * 90; //+ 40;
				ballPosition.x = Math.cos( now / 400 ) * 70;

				if ( params.showBall ) {

					sphere.visible = true;

					for ( let i = 0, il = particles.length; i < il; i ++ ) {

						const particle = particles[ i ];
						const pos = particle.position;
						diff.subVectors( pos, ballPosition );
						if ( diff.length() < ballSize ) {

							// collided
							diff.normalize().multiplyScalar( ballSize );
							pos.copy( ballPosition ).add( diff );

						}

					}

				} else {

					sphere.visible = false;

				}


				// Floor Constraints

				for ( let i = 0, il = particles.length; i < il; i ++ ) {

					const particle = particles[ i ];
					const pos = particle.position;
					if ( pos.y < - 250 ) {

						pos.y = - 250;

					}

				}

				// Pin Constraints

				for ( let i = 0, il = pins.length; i < il; i ++ ) {

					const xy = pins[ i ];
					const p = particles[ xy ];
					p.position.copy( p.original );
					p.previous.copy( p.original );

				}


			}

			/* testing cloth simulation */

			const cloth = new Cloth( xSegs, ySegs );

			const pinsFormation = [];
			pins = [ 6 ];

			pinsFormation.push( pins );

			pins = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
			pinsFormation.push( pins );

			pins = [ 0 ];
			pinsFormation.push( pins );

			pins = []; // cut the rope ;)
			pinsFormation.push( pins );

			pins = [ 0, cloth.w ]; // classic 2 pins
			pinsFormation.push( pins );

			pins = pinsFormation[ 1 ];

			function togglePins() {

				pins = pinsFormation[ ~ ~ ( Math.random() * pinsFormation.length ) ];

			}

			let container, stats;
			let camera, scene, renderer;

			let clothGeometry;
			let sphere;
			let object;

			init();
			animate( 0 );

			function init() {

				container = document.createElement( 'div' );
				document.body.appendChild( container );

				// scene

				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0xcce0ff );
				scene.fog = new THREE.Fog( 0xcce0ff, 500, 10000 );

				// camera

				camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 1, 10000 );
				camera.position.set( 1000, 50, 1500 );

				// lights

				scene.add( new THREE.AmbientLight( 0x666666 ) );

				const light = new THREE.DirectionalLight( 0xdfebff, 1 );
				light.position.set( 50, 200, 100 );
				light.position.multiplyScalar( 1.3 );

				light.castShadow = true;

				light.shadow.mapSize.width = 1024;
				light.shadow.mapSize.height = 1024;

				const d = 300;

				light.shadow.camera.left = - d;
				light.shadow.camera.right = d;
				light.shadow.camera.top = d;
				light.shadow.camera.bottom = - d;

				light.shadow.camera.far = 1000;

				scene.add( light );

				// cloth material

				const loader = new THREE.TextureLoader();
				const clothTexture = loader.load( 'uploads/threejs/circuit_pattern.png' );
				clothTexture.anisotropy = 16;

				const clothMaterial = new THREE.MeshLambertMaterial( {
					alphaMap: clothTexture,
					side: THREE.DoubleSide,
					alphaTest: 0.5
				} );

				// cloth geometry

				clothGeometry = new THREE.ParametricBufferGeometry( clothFunction, cloth.w, cloth.h );

				// cloth mesh

				object = new THREE.Mesh( clothGeometry, clothMaterial );
				object.position.set( 0, 0, 0 );
				object.castShadow = true;
				scene.add( object );

				// sphere

				const ballGeo = new THREE.SphereGeometry( ballSize, 32, 16 );
				const ballMaterial = new THREE.MeshLambertMaterial();

				sphere = new THREE.Mesh( ballGeo, ballMaterial );
				sphere.castShadow = true;
				sphere.receiveShadow = true;
				sphere.visible = false;
				scene.add( sphere );

				// ground

				const groundTexture = loader.load( 'uploads/threejs/grasslight-big.jpg' );
				groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping;
				groundTexture.repeat.set( 25, 25 );
				groundTexture.anisotropy = 16;
				groundTexture.encoding = THREE.sRGBEncoding;

				const groundMaterial = new THREE.MeshLambertMaterial( { map: groundTexture } );

				let mesh = new THREE.Mesh( new THREE.PlaneGeometry( 20000, 20000 ), groundMaterial );
				mesh.position.y = - 250;
				mesh.rotation.x = - Math.PI / 2;
				mesh.receiveShadow = true;
				scene.add( mesh );

				// poles

				const poleGeo = new THREE.BoxGeometry( 5, 375, 5 );
				const poleMat = new THREE.MeshLambertMaterial();

				mesh = new THREE.Mesh( poleGeo, poleMat );
				mesh.position.x = - 125;
				mesh.position.y = - 62;
				mesh.receiveShadow = true;
				mesh.castShadow = true;
				scene.add( mesh );

				mesh = new THREE.Mesh( poleGeo, poleMat );
				mesh.position.x = 125;
				mesh.position.y = - 62;
				mesh.receiveShadow = true;
				mesh.castShadow = true;
				scene.add( mesh );

				mesh = new THREE.Mesh( new THREE.BoxGeometry( 255, 5, 5 ), poleMat );
				mesh.position.y = - 250 + ( 750 / 2 );
				mesh.position.x = 0;
				mesh.receiveShadow = true;
				mesh.castShadow = true;
				scene.add( mesh );

				const gg = new THREE.BoxGeometry( 10, 10, 10 );
				mesh = new THREE.Mesh( gg, poleMat );
				mesh.position.y = - 250;
				mesh.position.x = 125;
				mesh.receiveShadow = true;
				mesh.castShadow = true;
				scene.add( mesh );

				mesh = new THREE.Mesh( gg, poleMat );
				mesh.position.y = - 250;
				mesh.position.x = - 125;
				mesh.receiveShadow = true;
				mesh.castShadow = true;
				scene.add( mesh );

				// renderer

				renderer = new THREE.WebGLRenderer( { antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );

				container.appendChild( renderer.domElement );

				renderer.outputEncoding = THREE.sRGBEncoding;

				renderer.shadowMap.enabled = true;

				// controls
				const controls = new OrbitControls( camera, renderer.domElement );
				controls.maxPolarAngle = Math.PI * 0.5;
				controls.minDistance = 1000;
				controls.maxDistance = 5000;

				// performance monitor

				stats = new Stats();
				container.appendChild( stats.dom );

				//

				window.addEventListener( 'resize', onWindowResize );

				//

				const gui = new GUI();
				gui.add( params, 'enableWind' ).name( 'Enable wind' );
				gui.add( params, 'showBall' ).name( 'Show ball' );
				gui.add( params, 'togglePins' ).name( 'Toggle pins' );

			}

			//

			function onWindowResize() {

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );

			}

			//

			function animate( now ) {

				requestAnimationFrame( animate );
				simulate( now );
				render();
				stats.update();

			}

			function render() {

				const p = cloth.particles;

				for ( let i = 0, il = p.length; i < il; i ++ ) {

					const v = p[ i ].position;

					clothGeometry.attributes.position.setXYZ( i, v.x, v.y, v.z );

				}

				clothGeometry.attributes.position.needsUpdate = true;

				clothGeometry.computeVertexNormals();

				sphere.position.copy( ballPosition );

				renderer.render( scene, camera );

			}