JSFiddle - React, Tailwind, and code Playground

HTML

<script src="//s3-us-west-2.amazonaws.com/s.cdpn.io/98205/simutil.js"></script>
<script type="text/x-vertex-shader" id="vs">
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;

varying vec3 v_view;
varying vec3 v_light;
varying vec3 v_normal;
varying vec2 v_texcoord;

void main() {
	vec4 p = u_modelView * vec4(a_position, 1.0);
	v_normal = mat3(u_modelView) * a_normal;
	v_view = -p.xyz;
	v_light = u_lightPos - p.xyz;
	v_texcoord = a_texcoord;
	gl_Position = u_proj * p;
}
</script>

<script type="text/x-fragment-shader" id="fs">
varying vec3 v_view;
varying vec3 v_light;
varying vec3 v_normal;
varying vec2 v_texcoord;
uniform sampler2D u_albedo;
void main() {
	vec3 n = normalize(v_normal);
	vec3 l = normalize(v_light);
	vec3 v = normalize(v_view);
	vec3 r = reflect(-l, n);
	vec3 albedo = pow(texture2D(u_albedo, v_texcoord).xyz, vec3(2.2));
	vec3 diffuse = max(dot(n, l), 0.0)*albedo;
	vec3 specular = vec3(0.0);//pow(max(dot(r, v), 0.0), 30.0) * u_lightColor;
	vec3 color = saturate(u_ambient + diffuse + specular);
	gl_FragColor = vec4(pow(color, vec3(1.0/2.2)), 1.0);
}
</script>
<div class="info">
  click+drag to interact
  <div id="fps"></div>
</div>
<canvas></canvas>

CSS

/* Styles go here */
body, html, canvas { width: 100%; height: 100%; margin: 0; padding: 0; border: none; overflow: hidden; }

.info {
    position:absolute;
    top: 0;
    left: 0;
    width: 400px;
    font-family: sans-serif;
    padding: 10px;
    color: rgb(200, 110, 0);
    -webkit-user-select: none;
    user-select: none;
}

Babel + JSX

let DEBUG = false;
Sim.DEBUG = DEBUG;
let Options = {
	gravity: -9.8,
	structK: 100000,
	shearK: 5000,
	bendK: 1000,
	dampSpring: 10,
	dampAir: 5,
	clothWidth: 20,
	clothHeight: 20,
	mass: 1.0,
	sleepThreshold: 0.001,
	sleepCount: 100,
	tension: 1.0,
	timeStep: 0.016,
	pinned: {
	    bottomLeft: false,
	    bottomRight: false,
	    topLeft: true,
	    topRight: true
	},
	dynamicWind: true,
	wind: [0.0, 0.0, 0.0] // if not dynamic
};

const StructSpring = 0;
const ShearSpring = 1;
const BendSpring = 2;
const SpringConstants = [Options.structK, Options.shearK, Options.bendK];

class Spring {
	constructor(type, a, b, rest) {
		this.type = type; // index into SpringConstants -- probably should just store k value here...
		this.rest = rest;
		this.a = a;
		this.b = b;
		this.iab = -1;
		this.iba = -1;
	}
}
// big array of 3d vectors. only treated as 3d for convenience --
// math is the same as if it was a big 1d vector
class BigVec3D {
	constructor(initSize, tight=true) {
		this.size = initSize;
		this.data = new Float32Array(initSize*3);
	}

	nanCheck() {
		if (DEBUG) {
			for (let i = 0; i < this.size*3; ++i) {
				let x = this.data[i];
				if (+x !== x) { console.assert("NaNCheck failed"); debugger; }
			}
		}
	}

	copy(other) {
		console.assert(this.size === other.size);
		for (let i = 0; i < this.size*3; ++i) {
			this.data[i] = other.data[i];
		}
	}

	forEach(fn, self) {
		for (let i = 0, ii = 0; i < this.size; ++i, ii += 3) {
			fn.call(self, this.data[ii], this.data[ii+1], this.data[ii+2], i,this);
		}
	}

	init(x, y, z) {
		for (let i = 0; i < this.data.length; i += 3) {
			this.data[i+0] = x;
			this.data[i+1] = y;
			this.data[i+2] = z;
		}
	}

	zero() {
		for (let i = 0, l = this.size*3; i < l; ++i) {
			this.data[i] = 0.0;
		}
	}
}

function dot(a, b) {
	let r = 0.0, sz = a.size;
	console.assert(a.size === b.size);
	for (let i = 0, size = a.size*3; i < size; ++i) r += a.data[i]*b.data[i];
	return r;
}

class BigMat3D {
	constructor(diagSize) {
		this.size...