<div class="canvas" name="bounce">
<canvas id="bounce" width="200" height="200"></canvas>
<script id="Vector.js">
function Vector(x,y) {this.x = x; this.y = y;}
function length(a) {return Math.sqrt(a.x*a.x + a.y*a.y);}
function negate(a) {return new Vector(-a.x, -a.y);}
function scale(a,k) {return new Vector(a.x * k, a.y * k);}
function add(a,b) {return new Vector(a.x + b.x, a.y + b.y);}
function subtract(a,b) {return new Vector(a.x - b.x, a.y - b.y);}
function scaleBy(a,k) {a.x *= k; a.y *= k; return a;}
function addBy(a,b) {a.x += b.x; a.y += b.y; return a;}
function subtractBy(a,b) {a.x -= b.x; a.y -= b.y; return a;}
function summate(array) {
	var s = new Vector(0,0);
	for (let a of array) addBy(s,a);
	return s;
}
function dot(a,b) {return a.x*b.x + a.y*b.y;}
function cross(a,b) {return a.x*b.y - a.y*b.x;}
function perp(a) {return new Vector(-a.y, a.x);}
function unit(a) {
	var l = length(a);
	if (l > 0) return new Vector(a.x / l, a.y / l);
	else return Object.create(a);
}
function rotate(a,θ) {
	var sinθ = Math.sin(θ);
	var cosθ = Math.cos(θ);
	return new Vector(a.x*cosθ - a.y*sinθ, a.x*sinθ + a.y*cosθ);
}
</script>
<script id="Ball.js">
// https://github.com/Apress/physics-for-javascript-games-animation-simulations
function Ball(radius,color,mass,gradient){
	if (typeof(radius)   === 'undefined') radius = 20;
	if (typeof(color)    === 'undefined') color = '#0000ff';
	if (typeof(mass)     === 'undefined') mass = 1;
	if (typeof(gradient) === 'undefined') gradient = false;
	this.radius   = radius;
	this.color    = color;
	this.mass     = mass;
	this.gradient = gradient;
	this.p        = new Vector(0,0);
	this.v        = new Vector(0,0);
}

Ball.prototype.draw = function(ctx) {
	var x = this.p.x;
	var y = this.p.y;
	var r = this.radius;
	var d = this.radius / 3;
	if (this.gradient) {
		grd = ctx.createRadialGradient(x+d,y-d,d/2,x+d,y-d,r);
		grd.addColorStop(0,'#ffffff');
		grd.addColorStop(1,this.color);
		ctx.fillStyle = grd;
	} else {
		ctx.fillStyle = this.color;
	}
	ctx.beginPath();
	ctx.arc(x, y, r, 0, 2*Math.PI, true);
	ctx.closePath();
	ctx.fill();
};
</script>
<script id="bounce.js">
// https://github.com/Apress/physics-for-javascript-games-animation-simulations
(function() {
	var canvas = document.getElementById('bounce');
	var ctx    = canvas.getContext('2d');

	var g = 0.5;
	var x = 50;
	var y = 50;
	var vx = 2;
	var vy = 0;
	var radius = 20;

	draw();

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){clearInterval(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = setInterval(update, 1000/60);}

	canvas.onmousedown = function() {
		vx = 2;
		vy = 15;
	};

	function update() {
		move();
		draw();
	}

	function move() {
		vy += g;
		x += vx;
		y += vy;

		// if hit ground, then bounce
		if (y > canvas.height - radius) {
			y = canvas.height - radius;
			vy *= -0.8; if (Math.abs(vy) < 0.0001) vy = 0;
			vx *= 0.97; if (Math.abs(vx) < 0.0001) vx = 0;
		}
		// if out screen, then wrap around
		if (x > canvas.width + radius) {
			x = -radius;
		}
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		grd = ctx.createRadialGradient(x+4, y-4, 8, x+4, y-4, 24);
		grd.addColorStop(0, '#c8c8ff');
		grd.addColorStop(1, '#0000ff');
		ctx.fillStyle = grd;
		ctx.beginPath();
		ctx.arc(x, y, radius, 0, 2*Math.PI, true);
		ctx.closePath();
		ctx.fill();
	};
})();
</script>
</div><div class="canvas" name="buoyancy">
<canvas id="buoyancy" width="200" height="200"></canvas>
<script id="buoyancy.js">
// https://github.com/Apress/physics-for-javascript-games-animation-simulations
(function() {
	var canvas = document.getElementById('buoyancy');
	var ctx    = canvas.getContext('2d'); 

	var g = 0.5;
	var x = 50;
	var y = 50;
	var vx = 4;
	var vy = -2;
	var ax, ay;
	var fx, fy;
	// wall
	var vfac = -0.8;
	// ball
	var radius = 20;
	var volume = 1;
	var mass = 1;
	// water
	var k = 0.01;
	var rho = 1.5;
	var water = 100;

	draw();

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){clearInterval(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = setInterval(update, 1000/60);}

	canvas.onmousedown = function(e) {
		vx = 3 * (e.offsetX - x > 0 ? -1 : +1);
		vy = -10;
	};

	function update() {
		force(); move(); draw();
	}

	function force(){
		var dr = (y - water) / radius;
		var ratio; // volume fraction in water
		if      (dr <= -1) ratio = 0; // outside water
		else if (dr >= +1) ratio = 1; // inside water
//		else               ratio = 0.5 + 0.5*dr; // cuboid
		else               ratio = 0.5 + 0.25*dr*(3-dr*dr); // sphere

		var gravity  = mass * g;
		var upthrust = -rho * volume * ratio * g;
		var drag     = -ratio * k * Math.sqrt(vx*vx + vy*vy);
		fx = drag * vx;
		fy = gravity + upthrust + drag * vy;
	}

	function move(){
		ax = fx / mass; vx += ax; x += vx;
		ay = fy / mass; vy += ay; y += vy;

		// bouncing off walls
		if (x < radius) {
			x = radius;
			vx *= vfac;
		}
		if (x > canvas.width - radius) {
			x = canvas.width - radius;
			vx *= vfac;
		}
		if (y < -radius)
			y = -radius;
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		// ball
		grd = ctx.createRadialGradient(x+4, y-4, 8, x+4, y-4, 24);
		grd.addColorStop(0, '#c8c8ff');
		grd.addColorStop(1, '#0000ff');
		ctx.fillStyle = grd;
		ctx.beginPath();
		ctx.arc(x, y, radius, 0, 2*Math.PI, true);
		ctx.closePath();
		ctx.fill();
		// water
		ctx.fillStyle = "rgba(0,255,255,0.5)";
		ctx.fillRect(0,water,canvas.width,canvas.height);
	}
})();
</script>
</div><div class="canvas" name="slide">
<canvas id="slide" width="200" height="200"></canvas>
<script id="slide.js">
// https://github.com/Apress/physics-for-javascript-games-animation-simulations
(function() {
	var canvas = document.getElementById('slide');
	var ctx    = canvas.getContext('2d'); 

	var g = 0.2;
	var x, y, w;
	var vx, vy, vw;
	var ax, ay, aw;
	var fx, fy, fw;
	// slope
	var angle;
	var cs = 0.15;	// static friction
	var ck = 0.1;	// kinetic friction
	// ball
	var radius  = 15;
	var mass    = 1;
	var angmass = 0.4 * mass * radius * radius; // solid sphere

	reset(canvas.height/2);
	draw();

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){clearInterval(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = setInterval(update, 1000/60);}

	canvas.onmousedown = function(e){reset(e.offsetY);};

	function reset(ey) {
		x = y = w = vx = vy = vw = 0;
		x = 15;
		y = ey;
		angle = Math.atan2(canvas.height - y, canvas.width - x);
		x += radius * Math.sin(angle);
		y -= radius * Math.cos(angle);
	}

	function update() {
		force(); move(); draw();
	}

	function force(){
		var gravity  = mass * g;
		var pressure = mass * g * Math.cos(angle);
		var motivate = mass * g * Math.sin(angle);

		var torque   = motivate / (1 + mass*radius*radius/angmass);
		if (torque > cs * pressure) torque = ck * pressure;	// slip

		var normal   = rotate(new Vector(0, pressure), -angle);
		var friction = scale(perp(normal), torque/radius);

		fx = normal.x - friction.x;
		fy = normal.y - friction.y - gravity;
		fw = radius * torque;
	}

	function move(){
		ax = fx / mass;    vx += ax; x += vx;
		ay = fy / mass;    vy += ay; y -= vy;
		aw = fw / angmass; vw += aw; w += vw;
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		// slope
		ctx.strokeStyle = 'black';
		ctx.fillStyle = 'gray';
		ctx.beginPath();
		ctx.moveTo(0, canvas.height - canvas.width * Math.tan(angle));
		ctx.lineTo(canvas.width, canvas.height);
		ctx.lineTo(0, canvas.height);
		ctx.closePath();
		ctx.fill();
		ctx.stroke();
		// ball
		grd = ctx.createRadialGradient(x+4, y-4, 8, x+4, y-4, 24);
		grd.addColorStop(0, '#c8c8ff');
		grd.addColorStop(1, '#0000ff');
		ctx.fillStyle = grd;
		ctx.beginPath();
		ctx.arc(x, y, radius, 0, 2*Math.PI, true);
		ctx.closePath();
		ctx.fill();
		// ball axis
		var dx = radius * Math.cos(w);
		var dy = radius * Math.sin(w);
		ctx.strokeStyle = '#0000ff';
		ctx.beginPath();
		ctx.moveTo(x+dx,y+dy);
		ctx.lineTo(x-dx,y-dy);
		ctx.moveTo(x-dy,y+dx);
		ctx.lineTo(x+dy,y-dx);
		ctx.stroke();
	}
})();
</script>
</div><div class="canvas" name="bubble">
<canvas id="bubble" width="200" height="200"></canvas>
<script id="bubble.js">
// https://github.com/Apress/physics-for-javascript-games-animation-simulations
(function() {
	var canvas = document.getElementById('bubble');
	var ctx = canvas.getContext('2d'); 

	var f;
	var g = 0.5;
	// air
	var rho = 1;
	// bubble
	var density = 0.99;
	var u = 0.99;
	// wind
	var kfac = 0.01;
	var wind = new Vector(1,0);

	var balls = new Array(10);
	for (var i=0; i<balls.length; i++) {
		var radius  = 5 + Math.random() * 15;
		var volume  = Math.pow(radius,3) * Math.PI * 4 / 3;
		var mass    = density * volume;
		balls[i]    = new Ball(radius,'rgba(128,200,255,0.5)',mass,true);
		var x = Math.random() * canvas.width;
		var y = Math.random() * canvas.height;
		balls[i].p  = new Vector(x,y);
		balls[i].v  = new Vector(0,0);
	}

	draw();

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){clearInterval(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = setInterval(update, 1000/60);}

	canvas.onmousedown = function(e){
		for (let ball of balls) {
			var dx = e.offsetX - ball.p.x;
			var dy = e.offsetY - ball.p.y;
			var d  = Math.sqrt(dx*dx+dy*dy) + 1;
			var v  = Math.min(5, 10/d);
			ball.v.x += v * (dx < 0 ? +1 : -1);
			ball.v.y += v * (dy < 0 ? +1 : -1);
		}
	};

	function update(){
		for (let ball of balls) {
			force(ball);
			move(ball);
		}
		draw();
	}

	function force(ball) {
		var volume   = ball.mass / density;
		var surface  = Math.PI * ball.radius * ball.radius * 4;

		var gravity  = new Vector(0, ball.mass*g);
		var upthrust = new Vector(0, -rho*volume*g);
		var velocity = subtract(wind, ball.v);
		var drag     = scale(velocity, surface*length(wind)*kfac);
		f = summate([gravity, upthrust, drag]);
	}

	function move(ball){
		var a = scale(f, 1/ball.mass);
		ball.v = addBy(ball.v, a);
		ball.v = scaleBy(ball.v, u);
		ball.p = addBy(ball.p, ball.v);
		// wrap around
		const maxr = 20;
		if      (ball.p.x > canvas.width + maxr) ball.p.x = -maxr;
		else if (ball.p.x < -maxr) ball.p.x = canvas.width + maxr;
		if      (ball.p.y > canvas.height + maxr) ball.p.y = -maxr;
		else if (ball.p.y < -maxr) ball.p.y = canvas.height + maxr;
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		for (let ball of balls)
			ball.draw(ctx);
	}
})();
</script>
</div><div class="canvas" name="attract">
<canvas id="attract" width="200" height="200"></canvas>
<script id="attract.js">
// https://github.com/Apress/physics-for-javascript-games-animation-simulations
(function() {
	var canvas = document.getElementById('attract');
	var ctx = canvas.getContext('2d'); 

	var attractors = new Array(2);
	function reset() {
		for (var i=0; i<attractors.length; i++){
			var r = 20*(Math.random()+0.5);
			var x = Math.random() * canvas.width/2  + canvas.width/4;
			var y = Math.random() * canvas.height/2 + canvas.height/4;
			attractors[i]        = new Ball(r,"rgba(0,0,255,0.3)");
			attractors[i].p      = new Vector(x,y);
			attractors[i].charge = -100000;
		}
	}

	var ball = new Ball(5,'#0000ff',1,true);
	var x = Math.random() * canvas.width;
	var y = Math.random() * canvas.height;
	ball.p = new Vector(x,y);
	ball.v = new Vector(0,0);
	ball.charge = 1;

	reset();
	draw();

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	canvas.onmousedown = function(e) {
		ball.p.x = e.offsetX;
		ball.p.y = e.offsetY;
		ball.v.x = 0;
		ball.v.y = 0;
	};

	var dt = 0.04;
	function update(){
		force(); move(); draw();
	}

	function force(){
		f = new Vector(0,0);
		for (let attractor of attractors){
			var p = subtract(ball.p, attractor.p);
			var k = 3;
			var q1 = attractor.charge;
			var q2 = ball.charge;
			var r  = length(p);
			r = Math.max(15, r);
			var electric = scale(p, (k*q1*q2)/(r*r*r));
			f = addBy(f, electric);
		}
	}

	function move(){
		var a = scale(f, 1/ball.mass);
		ball.v = addBy(ball.v, scale(a, dt));
		ball.p = addBy(ball.p, scale(ball.v, dt));

		if (ball.p.x < 0)             {ball.p.x = 0;             ball.v.x *= -0.9;}
		if (ball.p.x > canvas.width)  {ball.p.x = canvas.width;  ball.v.x *= -0.9;}
		if (ball.p.y < 0)             {ball.p.y = 0;             ball.v.y *= -0.9;}
		if (ball.p.y > canvas.height) {ball.p.y = canvas.height; ball.v.y *= -0.9;}
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		for (let attractor of attractors)
			attractor.draw(ctx);
		ball.draw(ctx);
	}
})();
</script>
</div><div class="canvas" name="spring">
<canvas id="spring" width="200" height="200"></canvas>
<script id="spring.js">
// https://github.com/Apress/physics-for-javascript-games-animation-simulations
(function() {
	var canvas = document.getElementById('spring');
	var ctx = canvas.getContext('2d'); 

	var f;
	var g = 100;
	var kDamping = 0.5;
	var kSpring = 10;
	var springLength = 15;

	var support = new Ball(2,'#000000');
	support.p   = new Vector(canvas.width/2, 20);

	var balls = new Array(3);
	for (var i=0; i<balls.length; i++) {
		balls[i]   = new Ball(10,'#0000ff',1,true);
		balls[i].p = new Vector(canvas.width/2, 80+30*i);
		balls[i].v = new Vector(0, 0);
	}

	draw();

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(animate, canvas);};

	var press = false;
	canvas.onmousedown = function(e) {
		press = true;
		support.p.x = e.offsetX;
		support.p.y = e.offsetY;
	};
	canvas.onmouseup   = function() {
		press = false;
	};
	canvas.onmousemove = function(e) {
		if (press) {
			support.p.x = e.offsetX;
			support.p.y = e.offsetY;
		}
	};

	var t0, t1, t, dt;
	function animate() {
		t0 = new Date().getTime();
		t = 0;
		frame();
	}
	function frame() {
		id = requestAnimationFrame(frame, canvas);
		t1 = new Date().getTime();
		dt = 0.001*(t1-t0);
		t0 = t1;
		if (dt>0.2) dt=0;
		t += dt;
		update();
	}

	function update(){
		for (var i=0; i<balls.length; i++) {
			force(balls[i],i);
			move(balls[i]);
		}
		// auto move support
//		support.p.x = 100 * Math.sin(1.0*t) + 0.5*canvas.width;
		draw();
	}

	function force(obj, num) {
		var centerPrev;
		if (num > 0)              centerPrev = balls[num-1].p;
		else                      centerPrev = support.p;
		var centerNext;
		if (num < balls.length-1) centerNext = balls[num+1].p;
		else                      centerNext = obj.p;

		var gravity = new Vector(0, obj.mass * g);
		var damping = scale(obj.v, -kDamping);
		var displPrev = subtract(obj.p, centerPrev);
		var displNext = subtract(obj.p, centerNext);
		var lengthPrev = scale(unit(displPrev), springLength);
		var lengthNext = scale(unit(displNext), springLength);
		var extensionPrev = subtract(displPrev, lengthPrev);
		var extensionNext = subtract(displNext, lengthNext);
		var restoringPrev = scale(extensionPrev, -kSpring);
		var restoringNext = scale(extensionNext, -kSpring);
		f = summate([gravity, damping, restoringPrev, restoringNext]);
	}

	function move(ball) {
		var a  = scale(f, 1/ball.mass);
		ball.v = addBy(ball.v, scale(a, dt));
		ball.p = addBy(ball.p, scale(ball.v, dt));
	}

	function draw(){
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		// support
		support.draw(ctx);
		// spring
		ctx.save();
		ctx.strokeStyle = '#999999';
		ctx.lineWidth = 2;
		ctx.moveTo(support.p.x, support.p.y);
		for (let ball of balls)
			ctx.lineTo(ball.p.x, ball.p.y);
		ctx.stroke();
		ctx.restore();
		// ball
		for (let ball of balls)
			ball.draw(ctx);
	}
})();
</script>
</div><div class="canvas" name="network1">
<canvas id="network1" width="200" height="200"></canvas>
<script id="network1.js">
// https://omrelli.ug/
(function(){
	var canvas = document.getElementById('network1');
	var ctx    = canvas.getContext('2d');
	ctx.strokeStyle = 'gray';
	ctx.lineWidth = 1;

	var mouse = [0, 0];
	var drag = false;
	canvas.onmousedown = function(e){mouse = [e.offsetX, e.offsetY]; drag = true;};
	canvas.onmousemove = function(e){mouse = [e.offsetX, e.offsetY];};
	canvas.onmouseup   = function(e){drag = false;}

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var params = {
		N: 75,
		size: 4,
		radius: 30,
		mult: 0.001,
		visc: .05,
		bounce: 0.1,
		fric: 0.5,
		xgrav: 0,
		ygrav: 0
	};

	function Particle() {
		return {
			x: Math.random() * canvas.width,
			y: Math.random() * canvas.height,
			vx: 0,
			vy: 0,
			ax: 0,
			ay: 0
		}
	}

	var vertices = [];
	var edges = [];
	for (let i=0; i<params.N; i++) vertices.push(Particle());

	function neighbor() {
		vertices.sort(function(p,q){return p.x - q.x});

		edges = [];
		for (let i=0; i<vertices.length; i++)
			for (let j=i+1; j<vertices.length; j++) {
				var p1 = vertices[i];
				var p2 = vertices[j];
				var dx = p1.x - p2.x;
				if (dx > params.radius) break;
				var dy = p1.y - p2.y;
				var d = dx*dx + dy*dy;
				if (d <= params.radius * params.radius)
					edges.push({p1:i, p2:j});
			}
	}

	function move() {
		if (drag) {
			for (let p of vertices) {
				var dx = mouse[0] - p.x;
				var dy = mouse[1] - p.y;
				p.ax -= dx / 1000;
				p.ay -= dy / 1000;
			}
		}

		for (let e of edges) {
			var p1 = vertices[e.p1];
			var p2 = vertices[e.p2];

			var dx = p1.x - p2.x;
			var dy = p1.y - p2.y;
			var d = Math.sqrt(dx*dx + dy*dy);
			if (d < 0.1) d = 0.1;

			var dvx = p1.vx - p2.vx;
			var dvy = p1.vy - p2.vy;
			var dot = (dx*dvx + dy*dvy)/d;

			var f = params.mult*(params.radius-d)*(params.radius-d)/d;
			var fx = dx * f + params.visc * (dvx-dot*dx)/d;
			var fy = dy * f + params.visc * (dvy-dot*dy)/d;

			p1.ax += fx;
			p1.ay += fy;
			p2.ax -= fx;
			p2.ay -= fy;
		}

		for (let p of vertices) {
			p.vx += p.ax + params.xgrav;
			p.vy += p.ay + params.ygrav;
			p.x  += p.vx;
			p.y  += p.vy;
			p.ax =  0;
			p.ay =  0;

			// boundary
			var x1 = params.size;
			var x2 = canvas.width - params.size;
			var y1 = params.size;
			var y2 = canvas.height - params.size;

			if      (p.x < x1) {p.x += 1.1*(x1-p.x); p.vx *= -params.bounce; p.vy *= params.fric;}
			else if (p.x > x2) {p.x -= 1.1*(p.x-x2); p.vx *= -params.bounce; p.vy *= params.fric;}
			if      (p.y < y1) {p.y += 1.1*(y1-p.y); p.vy *= -params.bounce; p.vx *= params.fric;}
			else if (p.y > y2) {p.y -= 1.1*(p.y-y2); p.vy *= -params.bounce; p.vx *= params.fric;}
		}
	}

	var node = (function(){
		var canvas = document.createElement('canvas');
		var ctx = canvas.getContext('2d');
		canvas.width = canvas.height = params.size * 2;
		ctx.fillStyle = 'lightgray';
		ctx.strokeStyle = 'black';
		ctx.lineWidth = .5;
		ctx.arc(canvas.width/2, canvas.height/2, params.size, Math.PI*2, false);
		ctx.fill();
		ctx.stroke();
		return canvas;
	})();

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);

		ctx.beginPath();
		for (let e of edges) {
			var p1 = vertices[e.p1];
			var p2 = vertices[e.p2];
			ctx.moveTo(p1.x, p1.y);
			ctx.lineTo(p2.x, p2.y);
		}
		ctx.stroke();

		for (let p of vertices)
			ctx.drawImage(node, p.x-params.size, p.y-params.size);
	}

	function update() {
		neighbor();
		move();
		draw();
	}

	update();
})();
</script>
</div><div class="canvas" name="network2">
<canvas id="network2" width="200" height="200"></canvas>
<script id="network2.js">
// https://omrelli.ug/
(function(){
	var canvas = document.getElementById('network2');
	var ctx    = canvas.getContext('2d');
	ctx.strokeStyle = 'gray';
	ctx.lineWidth = 1;

	var mouse = [0, 0];
	var drag = false;
	canvas.onmousedown = function(e){mouse = [e.offsetX, e.offsetY]; drag = true;};
	canvas.onmousemove = function(e){mouse = [e.offsetX, e.offsetY];};
	canvas.onmouseup   = function(e){drag = false;}

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var params = {
		N: 25,
		size: 4,
		radius: 30,
		mult: 0.0001,
		visc: .05,
		bounce: 0.1,
		fric: 0.5,
		xgrav: 0,
		ygrav: 0
	};

	function Particle() {
		return {
			x: Math.random() * canvas.width,
			y: Math.random() * canvas.height,
			vx: 0,
			vy: 0,
			ax: 0,
			ay: 0
		}
	}

	var vertices = [];
	var edges = [];
	for (let i=0; i<params.N; i++) vertices.push(Particle());

	function neighbor() {
		vertices.sort(function(p,q){return p.x - q.x});

		edges = [];
		for (let i=0; i<vertices.length; i++)
			for (let j=i+1; j<vertices.length; j++) {
				var p1 = vertices[i];
				var p2 = vertices[j];
				var dx = p1.x - p2.x;
				if (dx > params.radius) break;
				var dy = p1.y - p2.y;
				var d = dx*dx + dy*dy;
				if (d <= params.radius * params.radius)
					edges.push({p1:i, p2:j});
			}
	}

	function move() {
		if (drag) {
			for (let p of vertices) {
				var dx = mouse[0] - p.x;
				var dy = mouse[1] - p.y;
				p.ax += dx / 1000;
				p.ay += dy / 1000;
			}
		}

		for (let e of edges) {
			var p1 = vertices[e.p1];
			var p2 = vertices[e.p2];

			var dx = p1.x - p2.x;
			var dy = p1.y - p2.y;
			var d = Math.sqrt(dx*dx + dy*dy);
			if (d < 0.1) d = 0.1;

			var dvx = p1.vx - p2.vx;
			var dvy = p1.vy - p2.vy;
			var dot = (dx*dvx + dy*dvy)/d;

			var f = params.mult*(params.radius-d)*(params.radius/2-d)/d;
			var fx = dx * f + params.visc * (dvx-dot*dx)/d;
			var fy = dy * f + params.visc * (dvy-dot*dy)/d;

			p1.ax += fx;
			p1.ay += fy;
			p2.ax -= fx;
			p2.ay -= fy;
		}

		for (let p of vertices) {
			p.vx += p.ax + params.xgrav;
			p.vy += p.ay + params.ygrav;
			p.x  += p.vx;
			p.y  += p.vy;
			p.ax =  0;
			p.ay =  0;

			// boundary
			var x1 = params.size;
			var x2 = canvas.width - params.size;
			var y1 = params.size;
			var y2 = canvas.height - params.size;

			if      (p.x < x1) {p.x += 1.1*(x1-p.x); p.vx *= -params.bounce; p.vy *= params.fric;}
			else if (p.x > x2) {p.x -= 1.1*(p.x-x2); p.vx *= -params.bounce; p.vy *= params.fric;}
			if      (p.y < y1) {p.y += 1.1*(y1-p.y); p.vy *= -params.bounce; p.vx *= params.fric;}
			else if (p.y > y2) {p.y -= 1.1*(p.y-y2); p.vy *= -params.bounce; p.vx *= params.fric;}
		}
	}

	var node = (function(){
		var canvas = document.createElement('canvas');
		var ctx = canvas.getContext('2d');
		canvas.width = canvas.height = params.size * 2;
		ctx.fillStyle = 'lightgray';
		ctx.strokeStyle = 'black';
		ctx.lineWidth = .5;
		ctx.arc(canvas.width/2, canvas.height/2, params.size, Math.PI*2, false);
		ctx.fill();
		ctx.stroke();
		return canvas;
	})();

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);

		ctx.beginPath();
		for (let e of edges) {
			var p1 = vertices[e.p1];
			var p2 = vertices[e.p2];
			ctx.moveTo(p1.x, p1.y);
			ctx.lineTo(p2.x, p2.y);
		}
		ctx.stroke();

		for (let p of vertices)
			ctx.drawImage(node, p.x-params.size, p.y-params.size);
	}

	function update() {
		neighbor();
		move();
		draw();
	}

	update();
})();
</script>
</div><div class="canvas" name="bunny">
<canvas id="bunny" width="200" height="200"></canvas>
<script id="bunny.js">
(function(){
	var canvas = document.getElementById("bunny");
	var ctx = canvas.getContext("2d");
	ctx.fillStyle = 'white';
	ctx.strokeStyle = 'black';
	ctx.lineWidth = 0.5;
	ctx.globalAlpha = 1.0;

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var button_opacity = false;
	var button_shade = true;
	canvas.onkeydown = function(e) {
		if (e.keyCode == 79) {	// o
			button_opacity = !button_opacity;
			ctx.globalAlpha = (button_opacity ? 0.6 : 1.0);
		}
		if (e.keyCode == 83) {	// s
			button_shade = !button_shade;
			ctx.fillStyle = 'white';
		}
	}

	var angle_x = 0;
	var angle_y = 0;
	canvas.onmousemove = function(e) {
		angle_x = (e.offsetX/canvas.width-.5)*8;
		angle_y = (e.offsetY/canvas.height-.5)*8;
	}

	var press = false;
	canvas.onmousedown = function(e){press = true;};
	canvas.onmouseup   = function(e){press = false;};

	var params = {
		fric: 0.85,
		stress: 0.01,
		loose: 4
	};
	function Vec(x,y,z) {return {x:x,y:y,z:z,vx:0,vy:0,vz:0,ax:0,ay:0,az:0};}

	var screen_center = [canvas.width/2, canvas.height/2, 0];
	var focal_length = 950;
	var distance = 1000;
	var model = {};
	var scalar = 1.5;
	fetch('bunny.lowpoly.obj').then(r => r.text()).then((data) => {
		loadmodel(data, model);
		buildmodel(model, scalar);
		buildspring(model);
		update();
	});

	var points = [];
	var normals = [];
	var visible_faces = [];

	function force() {
		if (press) {
			for (let p of model.vertices) {
				p.ax -= p.x / 10;
				p.ay -= p.y / 10;
				p.az -= p.z / 10;
			}
		}

		for (let s of model.springs) {
			var p1 = model.vertices[s.p1];
			var p2 = model.vertices[s.p2];

			var dx = p1.x - p2.x;
			var dy = p1.y - p2.y;
			var dz = p1.z - p2.z;
			var d = Math.sqrt(dx*dx + dy*dy + dz*dz);
			if (d < 0.1) d = 0.1;

			var l = d - s.length;
			if (l > +params.loose) l = +params.loose;
			if (l < -params.loose) l = -params.loose;

			var f = -params.stress * l / d;
			var fx = dx * f;
			var fy = dy * f;
			var fz = dz * f;

			p1.ax += fx; p2.ax -= fx;
			p1.ay += fy; p2.ay -= fy;
			p1.az += fz; p2.az -= fz;
		}

		for (let p of model.vertices) {
			p.vx += p.ax; p.x += p.vx;
			p.vy += p.ay; p.y += p.vy;
			p.vz += p.az; p.z += p.vz;

			p.vx *= params.fric; p.ax = 0;
			p.vy *= params.fric; p.ay = 0;
			p.vz *= params.fric; p.az = 0;
		}
	}

	function project() {
		var c = Math.cos(angle_x);
		var s = Math.sin(angle_x);
		var rx = [c,0,-s,0,1,0,s,0,c];
		var c = Math.cos(-angle_y);
		var s = Math.sin(-angle_y);
		var ry = [1,0,0,0,c,s,0,-s,c];

		points = [];
		for (let p of model.vertices) {
			var q = rotate(rx, ry, p);
			q.z += distance;
			var length2 = q.x*q.x + q.y*q.y + q.z*q.z;
			q.x = q.x * focal_length / q.z + screen_center[0];
			q.y = q.y * focal_length / q.z + screen_center[1];
			q.z = length2;
			points.push(q);
		}

		normals = [];
		for (let f of model.faces) {
			var v = normal(
				model.vertices[f[0]],
				model.vertices[f[1]],
				model.vertices[f[2]]
			);
			normals.push( rotate(rx, ry, v) );
		}
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);

		visible_faces = [];
		for (let i=0; i<model.faces.length; i++) {
			var f = model.faces[i];
			var depth = Math.max(
				points[f[0]].z,
				points[f[1]].z,
				points[f[2]].z
			);
			visible_faces.push( {index:i, d:depth} );
		}
		visible_faces.sort(function(a,b){return b.d - a.d;});

		for (let vf of visible_faces) {
			var f = model.faces[vf.index];

			ctx.beginPath();
			ctx.moveTo(points[f[0]].x, points[f[0]].y);
			ctx.lineTo(points[f[1]].x, points[f[1]].y);
			ctx.lineTo(points[f[2]].x, points[f[2]].y);
			ctx.closePath();

			if (button_shade) {
				var c = 255 * Math.abs(normals[vf.index].z) | 0;
				ctx.fillStyle = 'rgb(' + (c/2|0) + ',' + c + ',' + (c/4|0) + ')';
			}
			ctx.fill();
			ctx.stroke();
		}
	}

	function title() {
		ctx.font = "12pt Verdana";
		ctx.textBaseline = "top";
		ctx.textAlign = "center";
		ctx.fillStyle = "gray";
		ctx.fillText("[o] opacity [s] shade", canvas.width/2, 0);
		ctx.fillStyle = "white";
	}

	function update() {
		force();
		project();
		draw();
		title();
	}

	function rotate(rx, ry, p) {
		var q = Vec(0,0,0), r = Vec(0,0,0);
		q.x = rx[0] * p.x + rx[1] * p.y + rx[2] * p.z;
		q.y = rx[3] * p.x + rx[4] * p.y + rx[5] * p.z;
		q.z = rx[6] * p.x + rx[7] * p.y + rx[8] * p.z;
		r.x = ry[0] * q.x + ry[1] * q.y + ry[2] * q.z;
		r.y = ry[3] * q.x + ry[4] * q.y + ry[5] * q.z;
		r.z = ry[6] * q.x + ry[7] * q.y + ry[8] * q.z;
		return r;
	}

	function normal(o, a, b) {
		var p = [o.x - a.x, o.y - a.y, o.z - a.z];
		var q = [o.x - b.x, o.y - b.y, o.z - b.z];
		var r = Vec(0,0,0);
		r.x = p[1] * q[2] - p[2] * q[1];
		r.y = p[2] * q[0] - p[0] * q[2];
		r.z = p[0] * q[1] - p[1] * q[0];
		var length = Math.sqrt(r.x*r.x + r.y*r.y + r.z*r.z);
		if (length > 0) {r.x /= length; r.y /= length; r.z /= length;}
		return r;
	}

	function loadmodel(data, model) {
		model.vertices = new Array();
		model.faces    = new Array();

		var lines = data.split('\n');
		for (let line of lines) {
			if (line.length == 0 || line[0] == '#') continue;
			var s = line.match(/\S+/g);
			switch (s[0]) {
			case 'v':
				model.vertices.push(Vec(parseFloat(s[1]), parseFloat(s[2]), parseFloat(s[3])));
				break;
			case 'f':
				model.faces.push([parseInt(s[1]) - 1, parseInt(s[2]) - 1, parseInt(s[3]) - 1]);
				break;
			}
		}
	}

	function buildmodel(model, scalar) {
		var center = Vec(0,0,0);
		for (let p of model.vertices) {
			center.x += p.x;
			center.y += p.y;
			center.z += p.z;
		}
		center.x /= model.vertices.length;
		center.y /= model.vertices.length;
		center.z /= model.vertices.length;

		for (let p of model.vertices) {
			p.x -= center.x; p.x *= -scalar;
			p.y -= center.y; p.y *= -scalar;
			p.z -= center.z; p.z *= -scalar;
		}
	}

	function buildspring(model) {
		model.springs = new Array();

		for (let i=0; i<model.vertices.length; i++)
			for (let j=i+1; j<model.vertices.length; j++) {
				var p1 = model.vertices[i];
				var p2 = model.vertices[j];
				var dx = p1.x - p2.x;
				var dy = p1.y - p2.y;
				var dz = p1.z - p2.z;
				var d = Math.sqrt(dx*dx + dy*dy + dz*dz);
				model.springs.push({p1:i, p2:j, length:d});
			}
	}
})();
</script>
</div><div class="canvas" name="fluid">
<canvas id="fluid" width="200" height="200"></canvas>
<script id="fluid.js">
// http://codepen.io/dissimulate/full/hszvg/
(function() {
	var canvas = document.getElementById("fluid");
	var ctx    = canvas.getContext('2d');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var mouse = {x: 0, y: 0, down: false};
	canvas.onmousedown = function(e) {mouse.down = true;};
	canvas.onmouseup   = function(e) {mouse.down = false;};
	canvas.onmousemove = function(e) {mouse.x = e.offsetX; mouse.y = e.offsetY;};

	var N    = 7;
	var type = 6;
	var size = 30;
	var gap  = 30;
	var fric = 0.95;

	function Particle(type) { return {
		type: type,
		x: Math.random() * canvas.width,
		y: Math.random() * canvas.height,
		vx: 0, vy: 0, ax: 0, ay: 0,
	}};

	var particles = [];
	for (let i = 0; i < type; i++)
		for (let j = 0; j < N; j++)
			particles.push( Particle(i) );

	var textures = new Array(type);
	for (let i = 0; i < type; i++) {
		textures[i] = (function(){
			var canvas = document.createElement("canvas");
			canvas.width  = size * 2;
			canvas.height = size * 2;
			var ctx = canvas.getContext("2d");
			var grad = ctx.createRadialGradient(size, size, 1, size, size, size);
			var c = Math.random() * 360 | 0;
			grad.addColorStop(0, 'hsla(' + c + ', 80%, 60%, 1)');
			grad.addColorStop(1, 'hsla(' + c + ', 80%, 60%, 0)');
			ctx.fillStyle = grad;
			ctx.beginPath();
			ctx.arc(size, size, size, 0, Math.PI * 2, true);
			ctx.fill();
			return canvas;
		})();
	}

	var num_x = (canvas.width  / gap | 0) + 1;
	var num_y = (canvas.height / gap | 0) + 1;
	var grids = new Array(num_x * num_y);
	for (let i = 0; i < num_x * num_y; i++)
		grids[i] = {length: 0, particles: []};

	function push_grid(p) {
		var x = p.x / gap | 0;
		var y = p.y / gap | 0;
		var g = grids[y * num_x + x];
		if (g) g.particles[g.length++] = p;
	}

	var ox = [-1,-1,-1,0,0,0,1,1,1];
	var oy = [-1,0,1,-1,0,1,-1,0,1];

	function find_neighbor(p1) {
		var x = p1.x / gap | 0;
		var y = p1.y / gap | 0;
		for (let i = 0; i < 9; ++i) {
			var k = (y + oy[i]) * num_x + (x + ox[i]);
			if (!grids[k]) continue;
			for (let p2 of grids[k].particles) {
				if (p2 === p1) continue;
				var dx = p2.x - p1.x;
				var dy = p2.y - p1.y;
				var d  = Math.sqrt(dx * dx + dy * dy);
				if (d > gap) continue;

				var m = 1 - d / gap;
				var f = m * m - 0.1;
				if (p2.type != p1.type) f *= -0.5;

				var fx = dx/d * f;
				var fy = dy/d * f;
				p1.ax -= fx;
				p1.ay -= fy;
				p2.ax += fx;
				p2.ay += fy;
			}
		}
	}

	function move(p) {
		if (mouse.down) {
			p.ax -= (p.x - mouse.x) / 100;
			p.ay -= (p.y - mouse.y) / 100;
		}

		var r = size / 2;
		var x1 = r, x2 = canvas.width - r;
		var y1 = r, y2 = canvas.height - r;
		if      (p.x < x1) p.ax += x1 - p.x;
		else if (p.x > x2) p.ax -= p.x - x2;
		if      (p.y < y1) p.ay += y1 - p.y;
		else if (p.y > y2) p.ay -= p.y - y2;

		p.vx += p.ax; p.vx *= fric;
		p.vy += p.ay; p.vy *= fric;
		p.x += p.vx;
		p.y += p.vy;
		p.ax = 0;
		p.ay = 0;
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		for (let p of particles) ctx.drawImage(textures[p.type], p.x - size, p.y - size, size * 2, size * 2);

		var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
		var pix       = imageData.data;
		var threshold = 220;
		for (let i = 0; i < pix.length; i += 4)
			if (pix[i + 3] < threshold)
				pix[i + 3] /= 6;
		ctx.putImageData(imageData, 0, 0);
	}

	function update() {
		for (let g of grids) g.length = 0;
		for (let p of particles) push_grid(p);
		for (let p of particles) find_neighbor(p);
		for (let p of particles) move(p);
		draw();
	};

	update();
})();
</script>
</div><div class="canvas" name="cloth">
<canvas id="cloth" width="200" height="200"></canvas>
<script id="cloth.js">
// https://github.com/dissimulate/Tearable-Cloth
(function() {
	var canvas = document.getElementById('cloth');
	var ctx = canvas.getContext('2d');
	ctx.strokeStyle = 'gray';
	ctx.lineWidth = 2;

	let X = 30;
	let Y = 20;
	let gap = 6;
	let tear = 30;
	let influence = 12;
	let pull = .8;
	let gravity = 0.02;
	let spring = 0.99;

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	let mouse = {x: 0, y: 0, dx: 0, dy: 0};
	canvas.onmousemove = function(e) {
		mouse.dx = Math.sign(e.offsetX - mouse.x);
		mouse.dy = Math.sign(e.offsetY - mouse.y);
		mouse.x = e.offsetX;
		mouse.y = e.offsetY;
	}
	canvas.onmousedown = function(e) {pull = 8; influence = 4;}
	canvas.onmouseup = function(e) {pull = .8; influence = 12;}
	canvas.ondblclick = init;

	class Vertex {
		constructor(x, y) {
			this.x = x;
			this.y = y;
			this.px = x;
			this.py = y;
			this.pin = false;
			this.edges = [];
		}

		update() {
			if (this.pin) return;

			let dx = this.x - mouse.x;
			let dy = this.y - mouse.y;
			let d = Math.sqrt(dx * dx + dy * dy);
			if (d < influence) {
				this.px = this.x - mouse.dx * pull;
				this.py = this.y - mouse.dy * pull;
			}

			let px = this.x;
			let py = this.y;
			this.x += (this.x - this.px) * spring;
			this.y += (this.y - this.py) * spring + gravity;
			this.px = px;
			this.py = py;

			if (this.x < 0) this.x = 0;
			if (this.x > canvas.width) this.x = canvas.width;
			if (this.y < 0) this.y = 0;
			if (this.y > canvas.height) this.y = canvas.height;
		}
	}

	class Edge {
		constructor(p1, p2) {
			this.p1 = p1;
			this.p2 = p2;
			this.connect = true;
		}

		resolve() {
			if (!this.connect) return;

			let dx = this.p1.x - this.p2.x;
			let dy = this.p1.y - this.p2.y;
			let d = Math.sqrt(dx * dx + dy * dy);
			if (d < gap) return;
			if (d > tear) {this.connect = false; return;}

			let k = (d - gap) / d / d;
			let fx = -dx * k;
			let fy = -dy * k;
			if (!this.p1.pin) this.p1.x += fx, this.p1.y += fy;
			if (!this.p2.pin) this.p2.x -= fx, this.p2.y -= fy;
		}
	}

	var vertices = [];
	var edges  = [];

	init();
	function init() {
		vertices = [];
		edges = [];
		let sx = canvas.width / 2 - (X-1) * gap / 2;
		let sy = 10;
		for (let y = 0; y < Y; y++) for (let x = 0; x < X; x++) {
			let p = new Vertex(sx + x * gap, sy + y * gap);
			if (y == 0) p.pin = true;
			if (x != 0) add_edge(p, vertices[vertices.length - 1]);
			if (y != 0) add_edge(p, vertices[x + (y - 1) * X]);
			vertices.push(p);
		}
	}

	function add_edge(p1, p2) {
		var e = new Edge(p1, p2);
		edges.push(e);
		p1.edges.push(e);
		p2.edges.push(e);
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.beginPath();
		for (let e of edges) if (e.connect) {
			ctx.moveTo(e.p1.x, e.p1.y);
			ctx.lineTo(e.p2.x, e.p2.y);
		}
		ctx.stroke();
	}

	function update() {
		for (let e of edges) e.resolve()
		for (let p of vertices) p.update();
		draw();
	}

	init();
	update();
})();
</script>
</div><div class="canvas" name="jelly">
<canvas id="jelly" width="200" height="200"></canvas>
<script id="jelly.js">
// https://github.com/dissimulate/jelly
(function() {
	var canvas = document.getElementById('jelly');
	var ctx = canvas.getContext('2d');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	const SPACING    = 14;
	const ITERATIONS = 14;
	const GRAVITY    = 0.05;	// g
	const SPEED      = 1;		// dt

	function clamp(val, min, max) {
		return Math.min(Math.max(val, min), max);
	};

	class Vector {
		constructor (x = 0, y = 0) {
			this.x = x;
			this.y = y;
		}

		add (v) {
			this.x += v.x;
			this.y += v.y;
			return this;
		}

		sub (v) {
			this.x -= v.x;
			this.y -= v.y;
			return this;
		}

		scale (k) {
			this.x *= k;
			this.y *= k;
			return this;
		}

		static add (v1, v2) {
			return new Vector(v1.x + v2.x, v1.y + v2.y);
		}

		static sub (v1, v2) {
			return new Vector(v1.x - v2.x, v1.y - v2.y);
		}

		static scale (v, k) {
			return new Vector(v.x * k, v.y * k);
		}

		static dist (v1, v2) {
			return Math.hypot(v1.x - v2.x, v1.y - v2.y);
		}

		static rot (v, t) {
			var cos_t = Math.cos(t);
			var sin_t = Math.sin(t);
			return new Vector(
				v.x * cos_t - v.y * sin_t,
				v.x * sin_t + v.y * cos_t 
			);
		}
	}

	class Particle {
		constructor (x, y, w) {
			// linear motion
			this.position = new Vector(x, y);
			this.velocity = new Vector();
			this.force    = new Vector();

			// angular motion
			this.theta  = 0;
			this.w      = w;
			this.torque = 0;
		}

		update () {
			// linear motion
			this.velocity.add(Vector.scale(this.force, SPEED));
			this.position.add(Vector.scale(this.velocity, SPEED));
			this.force = new Vector(0, GRAVITY / ITERATIONS);

			// angular motion
			const inertia = ((SPACING / 2) ** 2) / 2;
			this.w += this.torque / inertia * SPEED;
			const qPI = Math.PI / 4;
			this.w = clamp(this.w, -qPI, qPI);
			this.theta += this.w * SPEED;
			this.torque = 0;
		}

		wall () {
			const padding = 5;
			const damping = 0.6;

			var x = padding - this.position.x;
			if (x > 0) {
				this.force.add(new Vector(x, 0));
				this.velocity.y *= damping;
			}

			var x = canvas.width - padding - this.position.x;
			if (x < 0) {
				this.force.add(new Vector(x, 0));
				this.velocity.y *= damping;
			}

			var y = padding - this.position.y
			if (y > 0) {
				this.force.add(new Vector(0, y));
				this.velocity.x *= damping;
			}

			var y = canvas.height - padding - this.position.y;
			if (y < 0) {
				this.force.add(new Vector(0, y));
				this.velocity.x *= damping;
			}
		}

		interact () {
			const range = SPACING * 3;
			const pull  = 0.001;

			if (!press) {this.fixed = false; return;}
			if (!this.fixed) {	// first-time initialization
				if (Vector.dist(this.position, mouse) < range) { 
					this.fixed = true;
					this.offset = Vector.sub(this.position, mouse);
					this.velocity.scale(0);
					this.force.scale(0);
				}
			}
			if (!this.fixed) return;

			var target = Vector.add(mouse, this.offset);
			var diff   = Vector.sub(target, this.position);
			diff.scale(pull);
			this.force.add(diff.scale(SPEED));
			this.velocity.scale(0.99);
		}

		vat (target) {
			// radius vector
			var r = Vector.sub(target, this.position);
			// tangent velocity
			var vt = new Vector(-r.y, r.x).scale(this.w);
			// linear + angular
			return Vector.add(this.velocity, vt);
		}

		tat (target, f) {
			var arm = Vector.sub(target, this.position);
			var torque = f.y * arm.x - f.x * arm.y;
			return torque;
		}
	}

	class Constraint {
		constructor (a, b) {
			// original position
			this.a = a;
			this.b = b;
			this.dir = Vector.sub(b.position, a.position).scale(0.5);
		}

		resolve () {
			let {a, b, dir} = this;

			var dirA = Vector.rot(dir, a.theta);
			var dirB = Vector.rot(Vector.scale(dir, -1), b.theta);

			var refA = Vector.add(a.position, dirA);
			var refB = Vector.add(b.position, dirB);

			var diff = Vector.sub(refB, refA);
			var mid  = Vector.add(refA, Vector.scale(diff, 0.5));

			var velA = a.vat(mid);
			var velB = b.vat(mid);
			var velAB = Vector.sub(velB, velA);

			// linear spring (diff)
			// drag (velAB)
			const fk = 0.04;
			const tk = 0.02;
			var f = Vector.add(diff, velAB).scale(fk);
			var t = Vector.add(diff, velAB).scale(tk);
			a.force.add(f);
			b.force.sub(f);
			a.torque += a.tat(mid, t);
			b.torque -= b.tat(mid, t);

			// angular spring
			const k = 1;
			var diff = b.theta - a.theta;
			var theta = clamp(diff, -Math.PI, Math.PI);
			a.torque += theta * k;
			b.torque -= theta * k;
		}
	}

	var particles = [];
	var constraints = [];
	var drawPoints = [];
	const hue = Math.random() * 360 | 0;

	class Square {
		constructor (width, height) {
			const xbase = (canvas.width  - (width-1)  * SPACING) >> 1;
			const ybase = (canvas.height - (height-1) * SPACING) >> 1;

			particles = [];
			for (let i=0; i<height; ++i)
				for (let j=0; j<width; ++j) {
					var index = i * width + j;
					var x = xbase + i * SPACING;
					var y = ybase + j * SPACING;
					var w = -0.5 + Math.random();
					particles.push( new Particle(x, y, w) );
				}

			constraints = [];
			for (let i=0; i<height; ++i)
				for (let j=0; j<width; ++j) {
					var index = i * width + j;

					if (j > 0) {
						constraints.push( new Constraint(
							particles[index - 1],
							particles[index]
						));
					}

					if (i > 0) {
						constraints.push( new Constraint(
							particles[index - width],
							particles[index]
					));
				}
			}

			drawPoints = [];
			for (let i = 0; i < width; i++)
				drawPoints.push(particles[i].position);
			for (let i = 0; i < height; i++)
				drawPoints.push(particles[(width - 1) + width * i].position);
			for (let i = width - 1; i >= 0; i--)
				drawPoints.push(particles[(height - 1) * width + i].position);
			for (let i = height - 1; i >= 0; i--)
				drawPoints.push(particles[width * i].position);
		}

		draw (ctx) {
			ctx.lineWidth   = 2;
			ctx.fillStyle   = `hsla(${hue}, 90%, 80%, 0.8)`;
			ctx.strokeStyle = `hsla(${hue}, 90%, 70%, 0.8)`;

			ctx.beginPath();
			var last = drawPoints[drawPoints.length - 1];
			ctx.moveTo(last.x, last.y);
			for (let particle of drawPoints)
				ctx.lineTo(particle.x, particle.y);
			ctx.stroke();
			ctx.fill();
		}
	}

	var square = new Square(8, 8);
	update();

	function update() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);

		for (let i = ITERATIONS; i--;) {
			for (let constraint of constraints) {
				constraint.resolve();
			}

			for (let particle of particles) {
				particle.interact();
				particle.wall();
				particle.update();
			}
		}

		square.draw(ctx);
	};

	var press = false;
	canvas.onmousedown = function() {press = true;};
	canvas.onmouseup = function() {press = false;};
	canvas.onmouseenter = function(e) {press = (e.buttons == 1);}

	var mouse = new Vector(0, 0);
	canvas.onmousemove = function(e) {
		mouse.x = e.offsetX;
		mouse.y = e.offsetY;
	}
})();
</script>
</div><div class="canvas" name="ParticleSimulation1">
<canvas id="ParticleSimulation1" width="200" height="200"></canvas>
<script id="ParticleSimulation1.js">
(function() {
	var canvas = document.getElementById("ParticleSimulation1");
	var ctx    = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};

	init();
	draw();
	canvas.onmousedown = init;

	var x, y;		// 位置
	var vx, vy;		// 速度
	var ax, ay;		// 加速度

	function init() {
		x = canvas.width / 2;
		y = 0;
		vx = 0;
		vy = 0;
		ax = 0;
		ay = 0;
	}

	function frame() {
		id = requestAnimationFrame(frame, canvas);
		update();
		draw();
	}

	const dt = 1;
	const g = 0.1;
	function update() {
		ax = 0;
		ay = g;
		vx += ax * dt;
		vy += ay * dt;
		x  += vx * dt;
		y  += vy * dt;
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.beginPath();
		ctx.arc(x, y, 5, 0, 2.0 * Math.PI);
		ctx.fillStyle = "red";
		ctx.fill();
		ctx.strokeStyle = "black";
		ctx.stroke();
	}
})();
</script>
</div><div class="canvas" name="ParticleSimulation2">
<canvas id="ParticleSimulation2" width="200" height="200"></canvas>
<script id="ParticleSimulation2.js">
(function() {
	var canvas = document.getElementById("ParticleSimulation2");
	var ctx    = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};

	init();
	draw();
	canvas.onmousedown = init;

	var x, y;		// 位置
	var vx, vy;		// 速度
	var ax, ay;		// 加速度

	function init() {
		x = 0;
		y = canvas.height;
		vx = 0.5 + Math.random() * 2;
		vy = -6 + Math.random() * 2;
		ax = 0;
		ay = 0;
	}

	function frame() {
		id = requestAnimationFrame(frame, canvas);
		update();
		draw();
	}

	const dt = 1;
	const g = 0.1;
	function update() {
		ax = 0;
		ay = g;
		vx += ax * dt;
		vy += ay * dt;
		x  += vx * dt;
		y  += vy * dt;
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.beginPath();
		ctx.arc(x, y, 5, 0, 2.0 * Math.PI);
		ctx.fillStyle = "red";
		ctx.fill();
		ctx.strokeStyle = "black";
		ctx.stroke();
	}
})();
</script>
</div><div class="canvas" name="ParticleSimulation3">
<canvas id="ParticleSimulation3" width="200" height="200"></canvas>
<script id="ParticleSimulation3.js">
(function() {
	var canvas = document.getElementById("ParticleSimulation3");
	var ctx    = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};

	init();
	draw();
	canvas.onmousedown = function() {f = 0.1;};
	canvas.onmouseup   = function() {f = 0;};

	var x, y;		// 位置
	var vx, vy;		// 速度
	var ax, ay;		// 加速度
	var m;			// 質量
	var f;			// 力

	function init() {
		x = 0;
		y = canvas.height;
		vx = 0;
		vy = 0;
		ax = 0;
		ay = 0;
		m = 1;
		f = 0;
	}

	function frame() {
		id = requestAnimationFrame(frame, canvas);
		update();
		draw();
	}

	const dt = 1;
	const drag = 0.04;
	function update() {
		ax = f / m - drag * vx;
		ay = 0;
		vx += ax * dt;
		vy += ay * dt;
		x  += vx * dt;
		y  += vy * dt;

		if (x > canvas.width) x -= canvas.width + 20;
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.beginPath();
		ctx.rect(x, y-20, 20, 20);
		ctx.fillStyle = "red";
		ctx.fill();
		ctx.strokeStyle = "black";
		ctx.stroke();
	}
})();
</script>
</div><div class="canvas" name="ParticleSimulation4">
<canvas id="ParticleSimulation4" width="200" height="200"></canvas>
<script id="ParticleSimulation4.js">
(function() {
	var canvas = document.getElementById("ParticleSimulation4");
	var ctx    = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};

	init();
	draw();
	canvas.onmousedown = function() {f = 1;};
	canvas.onmouseup   = function() {f = 0;};

	var x0, y0;
	var x, y;		// 位置
	var vx, vy;		// 速度
	var ax, ay;		// 加速度
	var m;			// 質量
	var f;			// 力

	function init() {
		x0 = canvas.width / 2;
		y0 = 0;
		x = canvas.width / 2;
		y = canvas.height / 2;
		vx = 0;
		vy = 0;
		ax = 0;
		ay = 0;
		m = 1;
		f = 0;
	}

	function frame() {
		id = requestAnimationFrame(frame, canvas);
		update();
		draw();
	}

	const dt = 1;
	const length = canvas.width / 2;
	const spring = 0.05;
	const damping = 0.02;
	const g = 0.1;
	function update() {
		var dx = x - x0;
		var dy = y - y0;
		var d = Math.sqrt(dx*dx + dy*dy);
		var fx = -spring * (d - length) * dx/d;
		var fy = -spring * (d - length) * dy/d;

		var dot = vx*dx + vy*dy;
		fx += -damping * (dot/d * dx/d);
		fy += -damping * (dot/d * dy/d);

		fy += f;
		fy += g;

		ax = fx / m;
		ay = fy / m;
		vx += ax * dt;
		vy += ay * dt;
		x  += vx * dt;
		y  += vy * dt;
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.beginPath();
		ctx.rect(x - 10, y - 10, 20, 20);
		ctx.fillStyle = "red";
		ctx.fill();
		ctx.strokeStyle = "black";
		ctx.stroke();

		ctx.beginPath();
		ctx.moveTo(x0, y0);
		ctx.lineTo(x, y - 10);
		ctx.stroke();
	}
})();
</script>
</div><div class="canvas" name="ParticleSimulation5">
<canvas id="ParticleSimulation5" width="200" height="200"></canvas>
<script id="ParticleSimulation5.js">
(function() {
	var canvas = document.getElementById("ParticleSimulation5");
	var ctx    = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};

	init();
	draw();

	canvas.onmousedown = function(e) {
		gx = Math.sign(e.offsetX - x) * .5;
		gy = Math.sign(e.offsetY - y) * .5;
	}
	canvas.onmouseup   = function() {
		gx = gy = 0;
	}

	var x0, y0;
	var x, y;		// 位置
	var vx, vy;		// 速度
	var ax, ay;		// 加速度
	var fx, fy;		// 力
	var m;			// 質量
	var gx, gy;		// 滑鼠施力

	function init() {
		x0 = canvas.width / 2;
		y0 = 0;
		x = canvas.width / 2;
		y = canvas.height / 2;
		vx = vy = 0;
		ax = ay = 0;
		fx = fy = 0;
		m = 1;
		gx = gy = 0;
	}

	function frame() {
		id = requestAnimationFrame(frame, canvas);
		update();
		draw();
	}

	const dt = 1;
	const length = canvas.width / 2;
	const spring = 0.05;
	const damping = 0.03;
	const g = 1;

	function update() {
		var dx = x - x0;
		var dy = y - y0;
		var d = Math.sqrt(dx*dx + dy*dy);
		var fx = -spring * (d - length) * dx/d;
		var fy = -spring * (d - length) * dy/d;

		var dot = vx*dx + vy*dy;
		fx += -damping * (dot/d * dx/d);
		fy += -damping * (dot/d * dy/d);

		fx += gx;
		fy += gy + g;

		ax = fx / m;
		ay = fy / m;
		vx += ax * dt;   vx *= 0.995;
		vy += ay * dt;   vy *= 0.995;
		x  += vx * dt;
		y  += vy * dt;
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.beginPath();
		ctx.rect(x - 10, y - 10, 20, 20);
		ctx.fillStyle = "red";
		ctx.fill();
		ctx.strokeStyle = "black";
		ctx.stroke();

		ctx.beginPath();
		ctx.moveTo(x0, y0);
		ctx.lineTo(x, y - 10);
		ctx.stroke();
	}
})();
</script>
</div><div class="canvas" name="ParticleSimulation6">
<canvas id="ParticleSimulation6" width="200" height="200"></canvas>
<script id="ParticleSimulation6.js">
(function() {
	var canvas = document.getElementById("ParticleSimulation6");
	var ctx    = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};

	function Spring(x0, xp, k, damp) {
		this.x0 = x0;	// 彈簧長度
		this.k = k;		// 彈性係數
		this.damp = damp;	// 阻尼係數
		this.xp = xp;	// 拉伸長度
		this.x = x0;	// 位置
		this.v = 0;		// 速度
		this.a = 0;		// 加速度
		this.m = 1;		// 質量
		this.f = 0;		// 力
		this.dt = 1;

		this.pull = function() {
			this.x = this.xp;
			this.v = 0;
		}

		this.update = function() {
			with (this) {
				f = (x0 - x) * k;
				a = f / m;
				v += a * dt; v *= (1 - damp);
				x += v * dt;
			}
		}
	}

	var translate = new Spring(0, 30, .4, .1);
	var rotate    = new Spring(0, .3, .4, .05);
	var scale     = new Spring(1, 1.1, .2, .1);

	canvas.onmousedown = function() {
		translate.pull();
		scale.pull();
		rotate.pull();
	};

	function frame() {
		id = requestAnimationFrame(frame, canvas);
		update();
		draw();
	}

	function update() {
		translate.update();
		rotate.update();
		scale.update();
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.beginPath();

		var cx = canvas.width / 2;
		var cy = canvas.height / 2;

		var x = translate.x;
		var angle = rotate.x;
		ctx.translate(cx+x,cy);
		ctx.rotate(angle);
		ctx.translate(-cx-x,-cy);

		var size = scale.x * 50;
//		ctx.rect(cx - size/2, cy - size/2, size, size);
		roundRect(cx - size/2 + x, cy - size/2, size, size, 5);
		ctx.fillStyle = "red";
		ctx.fill();
		ctx.strokeStyle = "black";
		ctx.stroke();
		ctx.resetTransform();
	}

	function roundRect(x, y, width, height, radius) {
		if (typeof radius === 'undefined') {radius = 0;}
		ctx.beginPath();
		ctx.moveTo(x + radius, y);
		ctx.lineTo(x + width - radius, y);
		ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
		ctx.lineTo(x + width, y + height - radius);
		ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
		ctx.lineTo(x + radius, y + height);
		ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
		ctx.lineTo(x, y + radius);
		ctx.quadraticCurveTo(x, y, x + radius, y);
		ctx.closePath();
	}

	draw();
})();
</script>
</div><div class="canvas" name="ParticleSimulation7">
<canvas id="ParticleSimulation7" width="200" height="200"></canvas>
<script id="ParticleSimulation7.js">
(function() {
	var canvas = document.getElementById("ParticleSimulation7");
	var ctx    = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};

	function Spring(x0, xp, k, damp) {
		this.x0 = x0;	// 彈簧長度
		this.k = k;		// 彈性係數
		this.damp = damp;	// 阻尼係數
		this.xp = xp;	// 拉伸長度
		this.x = x0;	// 位置
		this.v = 0;		// 速度
		this.a = 0;		// 加速度
		this.m = 1;		// 質量
		this.f = 0;		// 力
		this.dt = 1;

		this.pull = function() {
			this.x = this.xp;
			this.v = 0;
		}

		this.update = function() {
			with (this) {
				f = (x0 - x) * k;
				a = f / m;
				v += a * dt; v *= (1 - damp);
				x += v * dt;
			}
		}
	}

	var translate = new Spring(0, 30, .1, .15);
	var scalex    = new Spring(1, 1.2, .1, .2);
	var scaley    = new Spring(1, 0.6, .1, .2);

	canvas.onmousedown = function() {
		translate.pull();
		scalex.pull();
		scaley.pull();
	};

	function frame() {
		id = requestAnimationFrame(frame, canvas);
		update();
		draw();
	}

	function update() {
		translate.update();
		scalex.update();
		scaley.update();
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.beginPath();

		var cx = canvas.width / 2;
		var cy = canvas.height / 2;

		var y = translate.x;
		var sizex = scalex.x * 50;
		var sizey = scaley.x * 50;
//		ctx.rect(cx - size/2, cy - size/2, size, size);
		roundRect(cx - sizex/2, cy - sizey/2 + y, sizex, sizey, 5);
		ctx.fillStyle = "red";
		ctx.fill();
		ctx.strokeStyle = "black";
		ctx.stroke();
		ctx.resetTransform();
	}

	function roundRect(x, y, width, height, radius) {
		if (typeof radius === 'undefined') {radius = 0;}
		ctx.beginPath();
		ctx.moveTo(x + radius, y);
		ctx.lineTo(x + width - radius, y);
		ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
		ctx.lineTo(x + width, y + height - radius);
		ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
		ctx.lineTo(x + radius, y + height);
		ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
		ctx.lineTo(x, y + radius);
		ctx.quadraticCurveTo(x, y, x + radius, y);
		ctx.closePath();
	}

	draw();
})();
</script>
</div><div class="canvas" name="RigidBodySimulation1">
<canvas id="RigidBodySimulation1" width="200" height="200"></canvas>
<script id="RigidBodySimulation1.js">
(function(){
	var canvas = document.getElementById('RigidBodySimulation1');
	var ctx    = canvas.getContext('2d');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var parameter = {
		N: 5,
		radius: 15,
		restitution: .5,
		friction: .5,
		spring: .5,
		depth: 3,
		gravity: .5,
		pull: .03,
		dt: 1
	};

	var body_index = 0;
	function Body() {
		this.index = body_index++;
		this.shape = 'ball';
		var x = Math.random() * canvas.width;
		var y = Math.random() * canvas.height;
		this.p     = new Vector(x,y);	// position
		this.v     = new Vector(0,0);	// velocity
		this.dv    = new Vector(0,0);	// change
		this.θ     = 0;					// angular position
		this.ω     = 0;					// angular velocity
		this.dω    = 0;					// change
		this.r     = parameter.radius;	// radius
		this.M     = 1;					// translational inertia
		this.invM  = 1 / this.M;
		this.I     = 0.4 * this.r * this.r * this.M;
										// rotational inertia
		this.invI  = 1 / this.I;
		this.fixed = false;				// fixed position
	}

	var balls = [];
	for (let i=0; i<parameter.N; i++)
		balls.push(new Body());

	var mouse = [0, 0];
	var drag = false;
	canvas.onmousedown = function(e){mouse = [e.offsetX, e.offsetY]; drag = true;};
	canvas.onmousemove = function(e){mouse = [e.offsetX, e.offsetY];};
	canvas.onmouseup   = function(e){drag = false;}
	canvas.onmouseout  = function(e){drag = false;}

	function interact() {
		if (drag) {
			for (let b of balls) {
				var dx = mouse[0] - b.p.x;
				var dy = mouse[1] - b.p.y;
				b.v.x += dx * parameter.pull * parameter.dt;
				b.v.y += dy * parameter.pull * parameter.dt;
			}
		}
	}

	function force() {
		for (let b of balls)
			b.v.y += parameter.gravity * parameter.dt;
	}

	var contacts = [];

	function collide() {
		// collision detection & contact resolution
		contacts = [];
		for (let b1 of balls)
			for (let b2 of balls)
				if (b1.index < b2.index)
					compute_contact(b1, b2);
		// momentum transport
		for (let c of contacts) compute_impulse(c);
		for (let b of balls) apply_impulse(b);

		// wall
		contacts = [];
		for (let b of balls) compute_contact(b, wall);
		for (let c of contacts) compute_impulse(c);
		for (let b of balls) apply_impulse(b);
	}

	function compute_contact(b1, b2) {
		if (b1.shape === 'ball') {
			if      (b2.shape === 'ball')
				ball_ball_collision(b1, b2);
			else if (b2.shape === 'cube')
				ball_cube_collision(b1, b2);
			else if (b2.shape === 'wall')
				ball_wall_collision(b1, b2);
		} else if (b1.shape === 'cube') {
			if      (b2.shape === 'ball')
				ball_cube_collision(b2, b1);
			else if (b2.shape === 'cube')
				cube_cube_collision(b1, b2);
			else if (b2.shape === 'wall')
				cube_wall_collision(b1, b2);
		}
	}

	function ball_ball_collision(b1, b2) {
		// distance
		var p12   = subtract(b2.p, b1.p);
		var ǁp12ǁ = length(p12);

		// penetrating depth
		var d = b1.r + b2.r - ǁp12ǁ;
		if (d < 0) return;

		// normal vector
		var n = scale(p12, 1/ǁp12ǁ);

		// radius vector
		var r1 = scale(n, +b1.r);
		var r2 = scale(n, -b2.r);

		contacts.push([b1, b2, r1, r2, n, d]);
	}

	function compute_impulse([b1, b2, r1, r2, n, d]) {
		// reciprocal of mass/inertia
		var invM1, invM2, invI1, invI2;
		if (b1.fixed) {
			invM1 = 1e-9;
			invI1 = 1e-9;
		} else {
			invM1 = b1.invM;
			invI1 = b1.invI;
		}
		if (b2.fixed) {
			invM2 = 1e-9;
			invI2 = 1e-9;
		} else {
			invM2 = b2.invM;
			invI2 = b2.invI;
		}

		// tangent vector
		var t   = perp(n);

		// relative velocity
		var v12 = subtract(b2.v, b1.v);
		var ω12 = scale(t, b2.ω * b2.r - b1.ω * b1.r);
		var u12 = add(v12, ω12);

		// normal impulse
		var r1xn = cross(r1, n);
		var r2xn = cross(r2, n);
		var kn  = invM1 + invM2
				+ invI1 * r1xn * r1xn
				+ invI2 * r2xn * r2xn;
		var Jn  = dot(u12, n) * (1 + parameter.restitution) / kn;

		// tangent impulse
		var r1xt = cross(r1, t);
		var r2xt = cross(r2, t);
		var kt  = invM1 + invM2
				+ invI1 * r1xt * r1xt
				+ invI2 * r2xt * r2xt;
		var Jt  = dot(u12, t) * (1 + parameter.restitution) / kt;
		var f   = parameter.friction * Jn;
		Jt = Math.sign(Jt) * Math.min(Math.abs(Jt), Math.abs(f));

		// spring force
		var s = Math.min(d, parameter.depth);
		var k = -parameter.spring * s;
		if (Jn * k < 0) Jn = k;

		// change in velocity
		if (!b1.fixed) {
			var dv   = scale(n, Jn * invM1);
			b1.dv.x += dv.x;
			b1.dv.y += dv.y;
			b1.dω   += r1xn * Jn * invI1
			         + r1xt * Jt * invI1;
		}
		if (!b2.fixed) {
			var dv   = scale(n, Jn * invM2);
			b2.dv.x -= dv.x;
			b2.dv.y -= dv.y;
			b2.dω   -= r2xn * Jn * invI2
			         - r2xt * Jt * invI2;
		}
	}

	function apply_impulse(b) {
		b.v.x += b.dv.x;
		b.v.y += b.dv.y;
		b.ω   += b.dω;
		b.dv.x = b.dv.y = b.dω = 0;
	}

	var wall = new Body();
	wall.shape = 'wall';
	wall.fixed = true;

	function ball_wall_collision(b, w) {
		var x1 = b.r, x2 = canvas.width  - b.r;
		var y1 = b.r, y2 = canvas.height - b.r;
		if      (b.p.x <= x1) macro(new Vector(-1,0), x1 - b.p.x);
		else if (b.p.x >= x2) macro(new Vector(+1,0), b.p.x - x2);
		if      (b.p.y <= y1) macro(new Vector(0,-1), y1 - b.p.y);
		else if (b.p.y >= y2) macro(new Vector(0,+1), b.p.y - y2);

		function macro(n, d) {
			var r1 = scale(n, b.r);
			var r2 = scale(n, -1.0);
			contacts.push([b, w, r1, r2, n, d]);
		}
	}

	function move() {
		for (let b of balls) {
			b.p.x += b.v.x;
			b.p.y += b.v.y;
			b.θ   += b.ω;
		}
	}

	function draw() {
		for (let ball of balls)
			if      (ball.shape === 'ball')
				drawBalls();
			else if (ball.shape === 'cube')
				drawCubes();
	}

	function drawBalls() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.fillStyle = 'orange';
		ctx.strokeStyle = 'black';
		ctx.lineWidth = 1;
		for (let b of balls) {
			// circle
			ctx.beginPath();
			ctx.arc(b.p.x, b.p.y, b.r, 0, Math.PI * 2, true);
			ctx.fill();
			ctx.stroke();
			// axis
			ctx.beginPath();
			ctx.moveTo(b.p.x, b.p.y);
			ctx.lineTo(b.p.x + b.r * Math.cos(b.θ), b.p.y + b.r * Math.sin(b.θ));
			ctx.stroke();
		}
	}

	function update() {
		interact();
		force();
		collide();
		move();
		draw();
	}

	draw();
})();
</script>
</div><div class="canvas" name="contact">
<canvas id="contact" width="200" height="200"></canvas>
<script id="contact.js">
(function(){
	var canvas = document.getElementById('contact');
	var ctx    = canvas.getContext('2d');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var parameter = {
		N: 20,
		radius: 15,
		speed: 1
	};

	function Body() {
		// radius
		var r  = parameter.radius;
		this.r = r;
		// position
		var x  = Math.random() * (canvas.width  - r*2) + r;
		var y  = Math.random() * (canvas.height - r*2) + r;
		this.p = new Vector(x,y);
		// angular position
		var θ  = Math.random() * Math.PI * 2;
		this.θ = θ;
	}

	var balls = [];

	canvas.onmousedown = reset;
	function reset() {
		balls = [];
		for (let i=0; i<parameter.N; i++)
			balls.push(new Body());
		collision = true;
	}

	var ball   = null;
	var d      = 0;
	var step   = new Vector(0,0);
	var target = new Vector(0,0);

	var collision = true;
	var tick  = 0;
	var index = 0;

	// Gauss–Seidel method
	function collide() {
		if (tick) return;
		if (!collision) return;
		collision = false;

		var N   = balls.length;
		var NxN = N * N;
		for (let i=0; i<NxN; ++i) {
			++index;
			index %= NxN;
			var b1 = balls[index / N | 0];
			var b2 = balls[index % N];
			if (b1 === b2) continue;
			if (collision = ball_ball_collision(b1, b2)) break;
		}
	}

	function ball_ball_collision(b1, b2) {
		// distance
		var p12   = subtract(b2.p, b1.p);
		var ǁp12ǁ = length(p12);
		if (ǁp12ǁ < .1) {ǁp12ǁ = .1; p12 = new Vector(ǁp12ǁ, 0);}

		// penetrating depth
		d = b1.r + b2.r - ǁp12ǁ;
		if (d <= 1e-1) return false;

		// normal vector
		var n = scale(p12, 1/ǁp12ǁ);

		// move
		ball = b2;
		target = add(b2.p, scale(n, d));
		step = scale(n, parameter.speed);
		return true;
	}

	function move() {
		if (!collision) return;
		tick++;
		if (parameter.speed * tick < d) {
			ball.p = addBy(ball.p, step);
		} else {
			ball.p = target;
			tick = 0;
		}
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.fillStyle = 'orange';
		ctx.strokeStyle = 'black';
		ctx.lineWidth = 1;
		for (let b of balls) {
			// circle
			ctx.beginPath();
			ctx.arc(b.p.x, b.p.y, b.r, 0, Math.PI * 2, true);
			ctx.fill();
			ctx.stroke();
			// axis
			ctx.beginPath();
			ctx.moveTo(b.p.x, b.p.y);
			ctx.lineTo(b.p.x + b.r * Math.cos(b.θ), b.p.y + b.r * Math.sin(b.θ));
			ctx.stroke();
		}
	}

	function update() {
		collide();
		move();
		draw();
	}

	reset();
	draw();
})();
</script>
</div><div class="canvas" name="penetration">
<canvas id="penetration" width="200" height="200"></canvas>
<script id="penetration.js">
(function(){
	var canvas = document.getElementById('penetration');
	var ctx    = canvas.getContext('2d');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var parameter = {
		N: 50,
		radius: 10,
		restitution: .5,
		friction: .5,
		spring: .1,
		depth: 3,
		gravity: .01,
		pull: .2,
		dt: 1
	};

	var body_index = 0;
	function Body() {
		this.index = body_index++;
		var x = Math.random() * canvas.width;
		var y = Math.random() * canvas.height;
		this.p     = new Vector(x,y);	// position
		this.v     = new Vector(0,0);	// velocity
		this.dv    = new Vector(0,0);	// change
		this.θ     = 0;					// angular position
		this.ω     = 0;					// angular velocity
		this.dω    = 0;					// change
		this.r     = parameter.radius;	// radius
		this.M     = 1;					// translational inertia
		this.invM  = 1 / this.M;
		this.I     = 0.4 * this.r * this.r * this.M;
										// rotational inertia
		this.invI  = 1 / this.I;
		this.fixed = false;				// fixed position
	}

	var balls = [];
	for (let i=0; i<parameter.N; i++)
		balls.push(new Body());

	var grids = [];
	parameter.diameter = parameter.radius * 2;
	parameter.grid = Math.ceil(canvas.height / parameter.diameter);

	function pushgrid(b) {
		var x = b.p.x / parameter.diameter | 0;
		var y = b.p.y / parameter.diameter | 0;
		var k = x * parameter.grid + y;
		grids[k] = grids[k] || [];
		grids[k].push(b);
	}

	function neighbors(b) {
		var array = new Array();
		var x = b.p.x / parameter.diameter | 0;
		var y = b.p.y / parameter.diameter | 0;
		var ix = [-1,-1,-1,0,0,0,1,1,1];
		var iy = [-1,0,1,-1,0,1,-1,0,1];
		for (let i=0; i<9; ++i) {
			var k = (x+ix[i]) * parameter.grid + (y+iy[i]);
			if (grids[k]) array = array.concat(grids[k]);
		}
		return array;
	}

	function force() {
		for (let b of balls)
			b.v.y += parameter.gravity * parameter.dt;
	}

	function collide() {
		grids = [];
		for (let b1 of balls)
			pushgrid(b1);

		for (let b1 of balls)
			for (let b2 of neighbors(b1))
				if (b1.index < b2.index)
					ball_ball_collision(b1, b2);
		apply_impulse();

		for (let b of balls)
			ball_wall_collision(b);
		apply_impulse();
	}

	function ball_ball_collision(b1, b2) {
		// distance
		var p12 = subtract(b2.p, b1.p);
		var l = length(p12);

		// penetrating depth
		var d = b1.r + b2.r - l;
		if (d < 0) return;

		// normal vector
		var n   = scale(p12, 1/l);

		// radius vector
		var r1  = scale(n, +b1.r);
		var r2  = scale(n, -b2.r);

		// momentum transport
		compute_impulse(b1, b2, r1, r2, n, d);
	}

	function compute_impulse(b1, b2, r1, r2, n, d) {
		// reciprocal of mass/inertia
		var invM1, invM2, invI1, invI2;
		if (b1.fixed) {
			invM1 = 1e-9;
			invI1 = 1e-9;
		} else {
			invM1 = b1.invM;
			invI1 = b1.invI;
		}
		if (b2.fixed) {
			invM2 = 1e-9;
			invI2 = 1e-9;
		} else {
			invM2 = b2.invM;
			invI2 = b2.invI;
		}

		// tangent vector
		var t   = perp(n);

		// relative velocity
		var v12 = subtract(b2.v, b1.v);
		var ω12 = scale(t, b2.ω * b2.r - b1.ω * b1.r);
		var u12 = add(v12, ω12);

		// normal impulse
		var r1xn = cross(r1, n);
		var r2xn = cross(r2, n);
		var kn  = invM1 + invM2
				+ invI1 * r1xn * r1xn
				+ invI2 * r2xn * r2xn;
		var Jn  = dot(u12, n) * (1 + parameter.restitution) / kn;

		// tangent impulse
		var r1xt = cross(r1, t);
		var r2xt = cross(r2, t);
		var kt  = invM1 + invM2
				+ invI1 * r1xt * r1xt
				+ invI2 * r2xt * r2xt;
		var Jt  = dot(u12, t) * (1 + parameter.restitution) / kt;
		var f   = parameter.friction * Jn;
		Jt = Math.sign(Jt) * Math.min(Math.abs(Jt), Math.abs(f));

		// spring force
		var s = Math.min(d, parameter.depth);
		var k = -parameter.spring * s;
		if (Jn * k < 0) Jn = k;

		// change in velocity
		if (!b1.fixed) {
			var dv   = scale(n, Jn * invM1);
			b1.dv.x += dv.x;
			b1.dv.y += dv.y;
			b1.dω   += r1xn * Jn * invI1
					+ r1xt * Jt * invI1;
		}
		if (!b2.fixed) {
			var dv   = scale(n, Jn * invM2);
			b2.dv.x -= dv.x;
			b2.dv.y -= dv.y;
			b2.dω   -= r2xn * Jn * invI2
			         - r2xt * Jt * invI2;
		}
	}

	function apply_impulse() {
		for (let b of balls) {
			b.v.x += b.dv.x;
			b.v.y += b.dv.y;
			b.ω   += b.dω;
			b.dv.x = b.dv.y = b.dω = 0;
		}
	}

	function ball_wall_collision(b) {
		var x1 = b.r, x2 = canvas.width  - b.r;
		var y1 = b.r, y2 = canvas.height - b.r;
		if      (b.p.x <= x1) macro(b, new Vector(-1,0), x1 - b.p.x);
		else if (b.p.x >= x2) macro(b, new Vector(+1,0), b.p.x - x2);
		if      (b.p.y <= y1) macro(b, new Vector(0,-1), y1 - b.p.y);
		else if (b.p.y >= y2) macro(b, new Vector(0,+1), b.p.y - y2);
	}

	var wall = new Body();
	wall.fixed = true;

	function macro(b, n, d) {
		var r1 = scale(n, b.r);
		var r2 = scale(n, -1.0);
		compute_impulse(b, wall, r1, r2, n, d);
	}

	function move() {
		for (let b of balls) {
			b.p.x += b.v.x;
			b.p.y += b.v.y;
			b.θ   += b.ω;
		}
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.fillStyle = 'orange';
		ctx.strokeStyle = 'black';
		ctx.lineWidth = 1;
		for (let b of balls) {
			// ball
			ctx.beginPath();
			ctx.arc(b.p.x, b.p.y, b.r, 0, Math.PI * 2, true);
			ctx.fill();
			ctx.stroke();
			// axis
			ctx.beginPath();
			ctx.moveTo(b.p.x, b.p.y);
			ctx.lineTo(b.p.x + b.r * Math.cos(b.θ), b.p.y + b.r * Math.sin(b.θ));
			ctx.stroke();
		}
	}

	var gravity = [0.01, 0.1, 1];
	var spring  = [0.01, 0.05, 0.1, 0.5, 1];

	var press = false;
	canvas.onmousedown = function(e) {press = true;}
	canvas.onmouseup = canvas.onmouseout = function(e) {press = false;}

	const rate = 20;
	var tick = 0;
	function interact() {
		if (!press) {tick = (tick/rate|0)*rate+rate-1; return;}
		tick++;
		tick %= gravity.length * spring.length * rate;
		var index         = tick / rate | 0;
		var gravity_index = index / spring.length | 0;
		var spring_index  = index % spring.length;
		parameter.gravity = gravity[gravity_index];
		parameter.spring  = spring[spring_index];
	}

	function title() {
		ctx.font = "12pt Verdana";
		ctx.textBaseline = "top";
		ctx.textAlign = "center";
		ctx.fillStyle = "gray";
		var str = "gravity = " + parameter.gravity;
		ctx.fillText(str, canvas.width/2, 0);
		var str = "spring = " + parameter.spring;
		ctx.fillText(str, canvas.width/2, 16);
		ctx.fillStyle = "white";
	}

	function update() {
		interact();
		force();
		collide();
		move();
		draw();
		title();
	}

	draw();
})();
</script>
</div><div class="canvas" name="jittering">
<canvas id="jittering" width="200" height="200"></canvas>
<script id="jittering.js">
(function() {
	var canvas = document.getElementById("jittering");
	var ctx = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur  = function(){clearInterval(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = setInterval(update, 20);};
	canvas.onmousedown = init;

	const radius = 20;
	const diameter = radius * 2;
	const N = 5;

	var particles = new Array(N).fill().map(Particle);
	function Particle() {return {x:0, y:0, vx:0, vy:0};};

	init();
	function init() {
		for (let p of particles) {
			p.x = Math.floor(Math.random() * (canvas.width - diameter));
			p.y = Math.floor(Math.random() * (canvas.height - diameter));
			p.vx = Math.ceil(Math.random() * 3);
			p.vy = Math.ceil(Math.random() * 3);
		}
	}

	function ball_wall_collision(p) {
		var x1 = radius, x2 = canvas.width - radius;
		var y1 = radius, y2 = canvas.height - radius;

		if      (p.x <= x1) {p.x = x1 * 2 - p.x; p.vx = -p.vx;}
		else if (p.x >= x2) {p.x = x2 * 2 - p.x; p.vx = -p.vx;}
		if      (p.y <= y1) {p.y = y1 * 2 - p.y; p.vy = -p.vy;}
		else if (p.y >= y2) {p.y = y2 * 2 - p.y; p.vy = -p.vy;}
	}

	function ball_ball_collision(p1, p2) {
		if (hit(p1, p2)) {
			negate(p1);
			negate(p2);
		}
	}

	function hit(p1, p2) {
		var dx = p2.x - p1.x;
		var dy = p2.y - p1.y;
		return dx*dx + dy*dy <= diameter*diameter;
	}

	function negate(p) {
		p.vx = -p.vx;
		p.vy = -p.vy;
	}

	function move() {
		for (let p of particles) {
			p.x += p.vx;
			p.y += p.vy;
		}

		for (let p of particles)
			ball_wall_collision(p);

		for (var i=0; i<N; ++i)
			for (var j=i+1; j<N; ++j) {
				var p1 = particles[i];
				var p2 = particles[j];
				ball_ball_collision(p1, p2);
			}
	}

	ctx.fillStyle = "rgba(255,128,0,0.8)";
	ctx.strokeStyle = "black";

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		for (let p of particles) {
			ctx.beginPath();
			ctx.arc(p.x, p.y, radius, 0, 2.0 * Math.PI);
			ctx.fill();
			ctx.stroke();
		}
	}

	function update() {
		move();
		draw();
	}

	draw();
})();
</script>
</div><div class="canvas" name="tunneling">
<canvas id="tunneling" width="200" height="200"></canvas>
<script id="tunneling.js">
(function(){
	var canvas = document.getElementById("tunneling");
	var ctx = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur  = function(){clearInterval(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = setInterval(update, 20);};
	canvas.onmousedown = init;

	const radius = 10;	// ball
	var x, y, vx, vy;	// ball
	var rx, ry, rw, rh;	// rod
	init();

	function init() {
		// ball
		x = canvas.width / 2;
		y = radius + 1;
		vx = 0;
		vy = 37;
		// rod
		rx = canvas.width / 4;
		ry = canvas.height / 2;
		rw = canvas.width / 2;
		rh = 4;
	}

	function move() {
		x += vx;
		y += vy;
		ball_rod_collision();
		ball_wall_collision();
	}

	function ball_rod_collision() {
		var y1 = ry - radius;
		var y2 = ry + rh + radius;
		if (y >= y1 && y <= y2)
		{
			if (vy > 0) {vy = -vy; y = y1;}
			else        {vy = -vy; y = y2;}
		}
	}

	function ball_wall_collision() {
		var y1 = radius;
		var y2 = canvas.height - radius;
		if (y <= y1) {vy = -vy; y = y1;}
		if (y >= y2) {vy = -vy; y = y2;}
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		// rod
		ctx.beginPath();
		ctx.rect(rx, ry, rw, rh);
		ctx.fillStyle = 'gray';
		ctx.strokeStyle = 'black';
		ctx.fill();
		ctx.stroke();
		// ball
		ctx.beginPath();
		ctx.arc(x, y, radius, 0, Math.PI * 2, true);
		ctx.fillStyle = 'orange';
		ctx.strokeStyle = 'black';
		ctx.fill();
		ctx.stroke();
	}

	var press = false;
	canvas.onmousedown = function(e) {press = true;}
	canvas.onmouseup = canvas.onmouseout = function(e) {press = false;}

	var speed = Math.sign(vy);
	function interact() {
		if (!press) return;
		speed++;
		if (speed > 50) speed = 1;
		vy = Math.sign(vy) * speed;
	}

	function title() {
		ctx.font = "12pt Verdana";
		ctx.textBaseline = "top";
		ctx.textAlign = "center";
		ctx.fillStyle = "gray";
		ctx.fillText("speed = " + speed, canvas.width/2, 0);
	}

	function update() {
		interact();
		move();
		draw();
		title();
	}

	draw();
	title();
})();
</script>
</div><div class="canvas" name="infinitebouncing">
<canvas id="infinitebouncing" width="200" height="200"></canvas>
<script id="infinitebouncing.js">
(function(){
	var canvas = document.getElementById("infinitebouncing");
	var ctx = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur  = function(){clearInterval(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = setInterval(update, 20);};
	canvas.onmousedown = init;

	const radius = 20;
	const k = 0.1;	// spring constant
	const g = 1;	// gravitational constant
	const e = 0.7	// coefficient of restitution

	var x, y, vx, vy;
	init();

	function init() {
		x = canvas.width / 2;
		y = canvas.height / 2;
		vx = 0;
		vy = 0;
	}

	function move() {
		vy += g;	// momentum increases
		collide();	// momentum decreases
		x += vx;
		y += vy;
	}

	function collide() {
		var x1 = radius, x2 = canvas.width - radius;
		var y1 = radius, y2 = canvas.height - radius;

		// momentum decreases
		// (abs() is for preventing jittering)
		if      (x <= x1) vx =  Math.abs(vx) * e;// x = x1;
		else if (x >= x2) vx = -Math.abs(vx) * e;// x = x2;
		if      (y <= y1) vy =  Math.abs(vy) * e;// y = y1;
		else if (y >= y2) vy = -Math.abs(vy) * e;// y = y2;

		// monentum increases
		// (spring force is for preventing jittering)
//		if      (x <= x1) vx += (x1 - x) * k;
//		else if (x >= x2) vx -= (x - x2) * k;
//		if      (y <= y1) vy += (y1 - y) * k;
//		else if (y >= y2) vy -= (y - y2) * k;

		// take a rest
//		if (Math.abs(vx) < .1) vx = 0;
//		if (Math.abs(vy) < .1) vy = 0;
	}

	ctx.fillStyle = 'orange';
	ctx.strokeStyle = 'black';
	ctx.lineWidth = 1;

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.beginPath();
		ctx.arc(x, y, radius, 0, Math.PI * 2, true);
		ctx.fill();
		ctx.stroke();
	}

	function update() {
		move();
		draw();
	}

	draw();
})();
</script>
</div><div class="canvas" name="stackbouncing">
<canvas id="stackbouncing" width="200" height="200"></canvas>
<script id="stackbouncing.js">
(function(){
	var canvas = document.getElementById("stackbouncing");
	var ctx = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur  = function(){clearInterval(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = setInterval(update, 20);};
	canvas.onmousedown = init;

	const radius = 20;
	const k = 0.1;	// spring constant
	const g = 1;	// gravitational constant
	var   e = 0;	// coefficient of restitution

	var b1 = {};	// at top
	var b2 = {};	// at bottom
	init();

	function init() {
		b1.x = canvas.width / 2;
		b1.y = canvas.height - radius * 3;
		b1.v = 0;
		b2.x = canvas.width / 2;
		b2.y = canvas.height - radius;
		b2.v = 0;
	}

	function move() {
		b1.v += g;
		b2.v += g;
		ball_ball_collide(b1, b2);
		ball_wall_collide(b1);
		ball_wall_collide(b2);
		b1.y += b1.v;
		b2.y += b2.v;

		// prevent overlap
		if (Math.abs(b2.y - b1.y) <= radius * 2)
			b1.y = b2.y - radius * 2 * Math.sign(b2.y - b1.y);
	}

	function ball_ball_collide(b1, b2) {
		if (Math.abs(b2.y - b1.y) <= radius * 2) {
			b1.v = (b1.v + b2.v + e * (b2.v - b1.v)) / 2;
			b2.v = (b1.v + b2.v + e * (b1.v - b2.v)) / 2;
		}
	}

	function ball_wall_collide(b) {
		var y1 = radius, y2 = canvas.height - radius;

		if      (b.y <= y1) b.v =  Math.abs(b.v) * e;
		else if (b.y >= y2) b.v = -Math.abs(b.v) * e;
	}

	function draw() {
		ctx.fillStyle = 'orange';
		ctx.strokeStyle = 'black';
		ctx.lineWidth = 1;
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.beginPath();
		ctx.arc(b1.x, b1.y, radius, 0, Math.PI * 2, true);
		ctx.fill();
		ctx.stroke();
		ctx.beginPath();
		ctx.arc(b2.x, b2.y, radius, 0, Math.PI * 2, true);
		ctx.fill();
		ctx.stroke();
	}

	var press = false;
	canvas.onmousedown = function(e) {press = true;}
	canvas.onmouseup = canvas.onmouseout = function(e) {press = false;}

	const rate = 10;
	const level = 10;
	var tick = 0;
	function interact() {
		if (!press) {tick = (tick/rate|0)*rate+rate-1; return;}
		tick++;
		tick %= level * rate;
		var index = tick / rate | 0;
		e = index / 10;
	}

	function title() {
		ctx.font = "12pt Verdana";
		ctx.textBaseline = "top";
		ctx.textAlign = "center";
		ctx.fillStyle = "gray";
		var str = "coefficient of restitution";
		ctx.fillText(str, canvas.width/2, 0);
		var str = "= " + e;
		ctx.fillText(str, canvas.width/2, 16);
		ctx.fillStyle = "white";
	}

	function update() {
		interact();
		move();
		draw();
		title();
	}

	draw();
})();
</script>
</div><div class="canvas" name="SpinMotion">
<canvas id="SpinMotion" width="200" height="200"></canvas>
<script src="three.min.js"></script>
<script src="axis.js"></script>
<script id="SpinMotion.js">
(function(){
	var canvas = document.getElementById('SpinMotion');
	var renderer = new THREE.WebGLRenderer({canvas: canvas, alpha: true, preserveDrawingBuffer: true});
	var scene = new THREE.Scene();

	var light = new THREE.PointLight( 0xffffff, 1, 100 );
	light.position.set( 50, 50, 50 );
	scene.add( light );

	var n = 5;
	var sphere = new Array(n);
	var r = [6, 8, 15, 10, 8];
	var z = [-20, -10, 0, 7, 15];
	for (let i=0; i<n; ++i) {
		var position = new THREE.Vector3( r[i], 0, z[i] );
		var geometry = new THREE.SphereGeometry( 1 );
		var material = new THREE.MeshPhongMaterial( {
			color : 'gray',
			emissive : 0x222222,
			reflectivity : 0.8
		} );
		sphere[i] = new THREE.Mesh( geometry, material );
		sphere[i].position.copy( position );
		scene.add( sphere[i] );
	}

	var radius = 15;
	var geometry = new THREE.ConeGeometry( radius/2, radius, 6 );
	var material = new THREE.MeshBasicMaterial( {
		color: 'rgb(230,185,184)',
		reflectivity : 0.8,
		polygonOffset: true,
		polygonOffsetFactor: 1,
		polygonOffsetUnits: 1
	} );
	var cone = new THREE.Mesh( geometry, material );
	cone.position.set( 0, 0, 0 );
	cone.rotation.set( 0, 0, -Math.PI/2 );
	scene.add( cone );

	var edges = new THREE.EdgesGeometry( geometry );
	var material = new THREE.LineBasicMaterial( {
		color: 0x00000,
		linewidth: 2	// no use
	} );
	var coneEdge = new THREE.LineSegments( edges, material );
	coneEdge.position.set( 0, 0, 0 );
	coneEdge.rotation.set( 0, 0, -Math.PI/2 );
	scene.add( coneEdge );

	var length = radius + 10;
	addAxis(scene, length);

	var center = new THREE.Vector3( 0, 0, 0 );
	var camera = new THREE.PerspectiveCamera();
	var depth = 50;
	var angle_x = 0.5;
	var angle_y = 0.5;
	function updateCamera() {
		camera.position.x = center.x + depth * Math.cos(angle_y) * Math.cos(angle_x);
		camera.position.y = center.y + depth * Math.sin(-angle_x);
		camera.position.z = center.z + depth * Math.sin(angle_y);
		camera.up.set( 0, 0, 1 );
		camera.lookAt( center );
	}
	updateCamera();
	renderer.render(scene, camera);

	canvas.onmousemove = function(event) {
		angle_x = (event.offsetX-canvas.width/2)/(canvas.width/2);
		angle_y = (event.offsetY-canvas.height/2)/(canvas.height/2);
	}

	var t = 0;
	function updatePosition() {
		t += .01;
		t %= Math.PI * 2;
		var θ = t * 20;
		for (let i=0; i<n; ++i) {
			var x = r[i] * Math.cos(θ);
			var y = r[i] * Math.sin(θ);
			sphere[i].position.set( x, y, z[i] );
		}
		var x = radius * Math.cos(θ);
		var y = radius * Math.sin(θ);
		cone.rotation.set( 0, 0, θ - Math.PI/2 );
		coneEdge.rotation.set( 0, 0, θ - Math.PI/2 );
	}

	var length = 40;
	var trajectory = new Array(n);
	for (let i=0; i<n; ++i) {
		trajectory[i] = new Array(length);
		for (let j=0; j<length; ++j)
			trajectory[i][j] = sphere[i].position.clone();
	}
	var line = new Array(n);

	function updateTrajectory() {
		for (let i=0; i<n; ++i) {
			trajectory[i].shift();
			trajectory[i].push(sphere[i].position.clone());

			var geometry = new THREE.BufferGeometry().setFromPoints( trajectory[i] );
			var material = new THREE.LineBasicMaterial( {
				color: 'brown',
				linewidth: 2	// no use
			} );
			scene.remove( line[i] );
			line[i] = new THREE.Line( geometry, material );
			scene.add( line[i] );
		}
	}

	function update(event) {
		updateCamera();
		if (press) updatePosition();
		updateTrajectory();
		renderer.render(scene, camera);
	}

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur  = function(){clearInterval(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = setInterval(update, 20);};

	var press = false;
	canvas.onmousedown = function(e) {press = true;}
	canvas.onmouseup = canvas.onmouseout = function(e) {press = false;}
})();
</script>
</div><div class="canvas" name="OrbitalMotion">
<canvas id="OrbitalMotion" width="200" height="200"></canvas>
<script src="three.min.js"></script>
<script src="axis.js"></script>
<script id="OrbitalMotion.js">
(function(){
	var canvas = document.getElementById('OrbitalMotion');
	var renderer = new THREE.WebGLRenderer({canvas: canvas, alpha: true, preserveDrawingBuffer: true});
	var scene = new THREE.Scene();

	var light = new THREE.PointLight( 0xffffff, 1, 100 );
	light.position.set( 50, 50, 50 );
	scene.add( light );

	var radius = 15;
	var position = new THREE.Vector3( radius, 0, 0 );
	var geometry = new THREE.SphereGeometry( 1 );
	var material = new THREE.MeshPhongMaterial( {
		color : 'gray',
		emissive : 0x222222,
		reflectivity : 0.8,
	} );
	var sphere = new THREE.Mesh( geometry, material );
	sphere.position.copy( position );
	scene.add( sphere );

	var geometry = new THREE.ConeGeometry( radius/2, radius, 6 );
	var material = new THREE.MeshBasicMaterial( {
		color: 'rgb(230,185,184)',
		reflectivity : 0.8,
		polygonOffset: true,
		polygonOffsetFactor: 1,
		polygonOffsetUnits: 1
	} );
	var cone = new THREE.Mesh( geometry, material );
	cone.position.set( 0, 0, 0 );
	cone.rotation.set( 0, 0, -Math.PI/2 );
	scene.add( cone );

	var edges = new THREE.EdgesGeometry( geometry );
	var material = new THREE.LineBasicMaterial( {
		color: 0x00000,
		linewidth: 2	// no use
	} );
	var coneEdge = new THREE.LineSegments( edges, material );
	coneEdge.position.set( 0, 0, 0 );
	coneEdge.rotation.set( 0, 0, -Math.PI/2 );
	scene.add( coneEdge );

	var length = radius + 10;
	addAxis(scene, length);

	var center = new THREE.Vector3( 0, 0, 0 );
	var camera = new THREE.PerspectiveCamera();
	var depth = 50;
	var angle_x = 0.5;
	var angle_y = 0.5;
	function updateCamera() {
		camera.position.x = center.x + depth * Math.cos(angle_y) * Math.cos(angle_x);
		camera.position.y = center.y + depth * Math.sin(-angle_x);
		camera.position.z = center.z + depth * Math.sin(angle_y);
		camera.up.set( 0, 0, 1 );
		camera.lookAt( center );
	}
	updateCamera();
	renderer.render(scene, camera);

	canvas.onmousemove = function(event) {
		angle_x = (event.offsetX-canvas.width/2)/(canvas.width/2);
		angle_y = (event.offsetY-canvas.height/2)/(canvas.height/2);
	}

	var t = 0, i = 0;
	function updatePosition() {
		t += .01;
		t %= Math.PI * 2;
		var θ = t * 20;
		var φ = t;
		var x = radius * Math.cos(θ);
		var y = radius * Math.sin(θ);
		var z = Math.sin(φ) * y;
		var y = Math.cos(φ) * y;
		sphere.position.set( x, y, z );
		cone.rotation.set( φ, 0, θ - Math.PI/2 );
		coneEdge.rotation.set( φ, 0, θ - Math.PI/2 );
	}

	var length = 40;
	var trajectory = new Array(length);
	for (let i=0; i<length; ++i)
		trajectory[i] = sphere.position.clone();
	var line = null;

	function updateTrajectory() {
		trajectory.shift();
		trajectory.push(sphere.position.clone());

		var geometry = new THREE.BufferGeometry().setFromPoints( trajectory );
		var material = new THREE.LineBasicMaterial( {
			color: 'brown',
			linewidth: 2	// no use
		} );
		scene.remove( line );
		line = new THREE.Line( geometry, material );
		scene.add( line );
	}

	function update(event) {
		updateCamera();
		if (press) updatePosition();
		updateTrajectory();
		renderer.render(scene, camera);
	}

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur  = function(){clearInterval(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = setInterval(update, 20);};

	var press = false;
	canvas.onmousedown = function(e) {press = true;}
	canvas.onmouseup = canvas.onmouseout = function(e) {press = false;}
})();
</script>
</div><div class="canvas" name="FluidSimulation1">
<canvas id="FluidSimulation1" width="200" height="200"></canvas>
<script id="FluidSimulation1.js">
(function(){
	var canvas = document.getElementById('FluidSimulation1');
	var ctx    = canvas.getContext('2d');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var mouseStart = [0, 0], mouseEnd = [0, 0];
	canvas.onmousemove = function(e){
		mouseEnd = [e.offsetX, e.offsetY];
	};
	canvas.onmouseover = function(e){
		mouseStart = mouseEnd = [e.offsetX, e.offsetY];
	};

	var button_auto = false;
	canvas.onkeydown = function(e) {
		if (e.keyCode == 65) button_auto = !button_auto;	// a
	}

	var params = {
		N: 500,
		size: 5,
		diameter: 12,
		comp: 1,
		visc: .1,
		pull: 5
	};
	params.grid = canvas.width / params.diameter | 0;

	function Particle() {
		return {
			x: Math.random() * canvas.width,
			y: Math.random() * canvas.height,
			vx: 0,
			vy: 0,
			ax: 0,
			ay: 0
		}
	}

	var particles = [];
	var grids = [];
	for (let i=0; i<params.N; i++) particles.push(Particle());

	function interact() {
		var mx = mouseEnd[0] - mouseStart[0];
		var my = mouseEnd[1] - mouseStart[1];
		var md = Math.sqrt(mx*mx + my*my);
		for (let p of particles) {
			for (let i=0; i<md; i+=1) {
				var x = mouseStart[0] + mx/md * i;
				var y = mouseStart[1] + my/md * i;
				var dx = x - p.x;
				var dy = y - p.y;
				var d  = dx*dx + dy*dy;
				if (d > 0 && d < 20) {
					p.ax += mx/md * params.pull;
					p.ay += my/md * params.pull;
					break;
				}
			}
		}
		mouseStart = mouseEnd;
	}

	function pump() {
		for (let p of particles) {
			if (p.x < 10)  p.ay += .1;
			if (p.x > 190) p.ay -= .1;
			if (p.y < 10)  p.ax -= .1;
			if (p.y > 190) p.ax += .1;
		}
	}

	function pushgrid(p) {
		var x = p.x / params.diameter | 0;
		var y = p.y / params.diameter | 0;
		var k = x * params.grid + y;
		grids[k] = grids[k] || [];
		grids[k].push(p);
	}

	function neighbors(p) {
		var array = new Array();
		var x = p.x / params.diameter | 0;
		var y = p.y / params.diameter | 0;
		var ix = [-1,-1,-1,0,0,0,1,1,1];
		var iy = [-1,0,1,-1,0,1,-1,0,1];
		for (let i=0; i<9; ++i) {
			var k = (x+ix[i]) * params.grid + (y+iy[i]);
			if (grids[k]) array = array.concat(grids[k]);
		}
		return array;
	}

	function force() {
		grids = [];
		for (let p1 of particles)
			pushgrid(p1);

		for (let p1 of particles) {
			p1.ax = p1.ay = 0;
			for (let p2 of neighbors(p1))
				addforce(p1, p2);
		}
	}

	function addforce(p1, p2) {
		var dx = p2.x - p1.x;
		var dy = p2.y - p1.y;
		var d = Math.sqrt(dx*dx + dy*dy);
		if (d < 0.001) d = 0.001;

		var dvx = p2.vx - p1.vx;
		var dvy = p2.vy - p1.vy;
		var dot = (dx*dvx + dy*dvy);

		var diameter = params.diameter;
		if (d >= diameter) return;

		var s = (diameter - d) / diameter;
		s = s * s;
		p1.ax += params.comp * s * -dx/d;
		p1.ay += params.comp * s * -dy/d;
		p1.ax += params.visc * dot/d * dx/d;
		p1.ay += params.visc * dot/d * dy/d;
	}

	function bound() {
		for (let p of particles) {
			var r = params.diameter / 2;
			var x1 = r, x2 = canvas.width - r;
			var y1 = r, y2 = canvas.height - r;
			if      (p.x < x1) p.ax += (x1 - p.x) * params.comp;
			else if (p.x > x2) p.ax -= (p.x - x2) * params.comp;
			if      (p.y < y1) p.ay += (y1 - p.y) * params.comp;
			else if (p.y > y2) p.ay -= (p.y - y2) * params.comp;
		}
	}

	function move() {
		for (let p of particles) {
			p.vx += p.ax;
			p.vy += p.ay;
			p.x  += p.vx;
			p.y  += p.vy;
		}
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.fillStyle = 'blue';
		for (let p of particles)
			ctx.fillRect(p.x - params.size/2, p.y - params.size/2, params.size, params.size);
	}

	function title() {
		ctx.font = "12pt Verdana";
		ctx.textBaseline = "top";
		ctx.textAlign = "center";
		ctx.fillStyle = "gray";
		ctx.fillText("[a] auto", canvas.width/2, 0);
		ctx.fillStyle = "white";
	}

	function update() {
		force();
		interact();
		if (button_auto) pump();
		bound();
		move();
		draw();
		title();
	}

	draw();
})();
</script>
</div><div class="canvas" name="FluidSimulation2">
<canvas id="FluidSimulation2" width="200" height="200"></canvas>
<script id="FluidSimulation2.js">
(function(){
	var canvas = document.getElementById('FluidSimulation2');
	var ctx    = canvas.getContext('2d');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var mouse = [0, 0];
	var drag = false;
	canvas.onmousedown = function(e){mouse = [e.offsetX, e.offsetY]; drag = true;};
	canvas.onmousemove = function(e){mouse = [e.offsetX, e.offsetY];};
	canvas.onmouseup   = function(e){drag = false;}
	canvas.onmouseout  = function(e){drag = false;}

	var button_water = false;
	canvas.onkeydown = function(e) {
		if (e.keyCode == 88) button_water = !button_water;	// x
	}

	var params = {
		N: 200,
		size: 5,
		size2: 12,
		diameter: 12,
		comp: 1,
		visc: .1,
		grav: .5,
		pull: .01
	};
	params.grid = Math.ceil(canvas.height / params.diameter);

	function Particle() {
		return {
			x: Math.random() * canvas.width,
			y: Math.random() * canvas.height,
			vx: 0,
			vy: 0,
			ax: 0,
			ay: 0
		}
	}

	var particles = [];
	var grids = [];
	for (let i=0; i<params.N; i++) particles.push(Particle());

	function pushgrid(p) {
		var x = p.x / params.diameter | 0;
		var y = p.y / params.diameter | 0;
		var k = x * params.grid + y;
		grids[k] = grids[k] || [];
		grids[k].push(p);
	}

	function neighbors(p) {
		var array = new Array();
		var x = p.x / params.diameter | 0;
		var y = p.y / params.diameter | 0;
		var ix = [-1,-1,-1,0,0,0,1,1,1];
		var iy = [-1,0,1,-1,0,1,-1,0,1];
		for (let i=0; i<9; ++i) {
			var k = (x+ix[i]) * params.grid + (y+iy[i]);
			if (grids[k]) array = array.concat(grids[k]);
		}
		return array;
	}

	function interact() {
		if (drag) {
			for (let p of particles) {
				var dx = mouse[0] - p.x;
				var dy = mouse[1] - p.y;
				var d  = dx*dx + dy*dy;
				p.ax += dx * params.pull;
				p.ay += dy * params.pull;
			}
		}
	}

	function force() {
		grids = [];
		for (let p1 of particles)
			pushgrid(p1);

		for (let p1 of particles) {
			p1.ax = p1.ay = 0;
			for (let p2 of neighbors(p1))
				addforce(p1, p2);
		}
	}

	function addforce(p1, p2) {
		var dx = p2.x - p1.x;
		var dy = p2.y - p1.y;
		var d = Math.sqrt(dx*dx + dy*dy);
		if (d < 0.001) d = 0.001;

		var dvx = p2.vx - p1.vx;
		var dvy = p2.vy - p1.vy;
		var dot = (dx*dvx + dy*dvy);

		var diameter = params.diameter;
		if (d >= diameter) return;

		var s = (diameter - d) / diameter;
		s = s * s;
		p1.ax += params.comp * s * -dx/d;
		p1.ay += params.comp * s * -dy/d;
		p1.ax += params.visc * dot/d * dx/d;
		p1.ay += params.visc * dot/d * dy/d;
	}

	function bound() {
		for (let p of particles) {
			var r = params.diameter / 2;
			var x1 = r, x2 = canvas.width - r;
			var y1 = r, y2 = canvas.height - r;
			if      (p.x < x1) p.ax += (x1 - p.x) * params.comp;
			else if (p.x > x2) p.ax -= (p.x - x2) * params.comp;
			if      (p.y < y1) p.ay += (y1 - p.y) * params.comp;
			else if (p.y > y2) p.ay -= (p.y - y2) * params.comp;
		}
	}

	function move() {
		for (let p of particles) {
			p.vx += p.ax;
			p.vy += p.ay + params.grav;
			p.x  += p.vx;
			p.y  += p.vy;
		}
	}

	function draw() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.fillStyle = 'blue';
		for (let p of particles)
			ctx.fillRect(p.x - params.size/2, p.y - params.size/2, params.size, params.size);
	}

	var node2 = (function(){
		var size = params.size2;
		var canvas = document.createElement("canvas");
		canvas.width = canvas.height = size * 2;
		var ctx    = canvas.getContext('2d');
		var grad = ctx.createRadialGradient(size, size, 1, size, size, size);
		grad.addColorStop(0, 'hsla(240, 80%, 60%, 1)');
		grad.addColorStop(1, 'hsla(240, 80%, 60%, 0)');
		ctx.fillStyle = grad;
		ctx.beginPath();
		ctx.arc(size, size, size, 0, Math.PI * 2, true);
		ctx.fill();
		return canvas;
	})();

	function draw2() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		for (let p of particles)
			ctx.drawImage(node2, p.x-params.size2, p.y-params.size2);

		var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
		var pix       = imageData.data;
		var threshold = 180;
		for (let i = 0; i < pix.length; i += 4)
			pix[i + 3] = (pix[i + 3] < threshold) ? 0 : 255;
		ctx.putImageData(imageData, 0, 0);
	}

	function title() {
		ctx.font = "12pt Verdana";
		ctx.textBaseline = "top";
		ctx.textAlign = "center";
		ctx.fillStyle = "gray";
		ctx.fillText("[x] particle/water", canvas.width/2, 0);
		ctx.fillStyle = "white";
	}

	function update() {
		force();
		interact();
		bound();
		move();
		button_water ? draw2() : draw();
		title();
	}

	draw();
})();
</script>
</div><div class="canvas" name="SPH">
<canvas id="SPH" width="200" height="200"></canvas>
<script src="three.min.js"></script>
<script id="SPH.js">
// https://github.com/novalain/SPH-Fluid-WebGL
(function(){
	var canvas = document.getElementById('SPH');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var PAR = {
		N: 100,
		width : 75,
		Z_DEPTH: 1000,
		mass: 1,
		viscosity: 1,
		stiffness: 1,
		gravity: 9.8 / 100,
		h: 16,
		dt: 1
	};
	PAR.radius  = PAR.h / 2;
	PAR.density = PAR.mass / Math.pow(PAR.h, 3);
	PAR.h6pi    = Math.pow(PAR.h, 6) * Math.PI;
	PAR.grid    = Math.ceil(PAR.width / PAR.h);

	var particles = [];

	for (let i = 0; i < PAR.N; i++) {
		particles.push({
			position:       new THREE.Vector3(0, 0, 0),
			velocity:       new THREE.Vector3(0, 0, 0),
			density:        0,
			pressure:       0,
			gravityForce:   new THREE.Vector3(0, 0, 0),
			pressureForce:  new THREE.Vector3(0, 0, 0),
			viscosityForce: new THREE.Vector3(0, 0, 0),
		});
	}

	reset();
//	function reset() {
//		var c = 0, i = 0, j = 0;
//		for (let p of particles) {
//			if (++c % 10 === 0) {i++; j = 0;}
//			j++;
//			p.position.set(
//				i * PAR.h / 2,
//				j * PAR.h / 2 + 80,
//				j * PAR.h / 2
//			);
//		}
//	}
	function reset() {
		for (let p of particles) {
			p.position.set(
				PAR.h + Math.random() * (PAR.width - PAR.h * 2),
				PAR.h + Math.random() * (PAR.width - PAR.h * 2) + 80,
				PAR.h + Math.random() * (PAR.width - PAR.h * 2)
			);
		}
	}

	var grids = [];

	function pushgrid(p) {
		var x = p.position.x / PAR.h | 0;
		var y = p.position.y / PAR.h | 0;
		var z = p.position.z / PAR.h | 0;
		var k = x + y * PAR.grid + z * PAR.grid * PAR.grid;
		grids[k] = grids[k] || [];
		grids[k].push(p);
	}

	function neighbors(p) {
		var array = [];
		var x = p.position.x / PAR.h | 0;
		var y = p.position.y / PAR.h | 0;
		var z = p.position.z / PAR.h | 0;
		var ix= [-1,-1,-1,0,0,0,1,1,1,-1,-1,-1,0,0,0,1,1,1,-1,-1,-1,0,0,0,1,1,1];
		var iy= [-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1];
		var iz= [-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1];
		for (let i=0; i<27; ++i) {
			var k = (x+ix[i]) + (y+iy[i]) * PAR.grid + (z+iz[i]) * PAR.grid * PAR.grid;
			if (grids[k]) array = array.concat(grids[k]);
		}
		return array;
	}

	function force() {
		grids = [];
		for (let p1 of particles) pushgrid(p1);

		// density & pressure
		for (let p1 of particles) {
			p1.density = 0;
			for (let p2 of neighbors(p1)) {
				let d = p1.position.distanceTo(p2.position);
				if (d < PAR.h) p1.density += Math.pow(PAR.h - d, 3);
			}
			p1.density *= PAR.mass * 15 / PAR.h6pi;
			p1.pressure = p1.density - PAR.density;
		}

		// force
		for (let p1 of particles) {
			p1.gravityForce.set(0, -PAR.mass * PAR.gravity, 0);
			p1.pressureForce.set(0, 0, 0);
			p1.viscosityForce.set(0, 0, 0);

			for (let p2 of neighbors(p1)) {
				if (p1 === p2) continue;

				var position = new THREE.Vector3().subVectors(p2.position, p1.position);
				var velocity = new THREE.Vector3().subVectors(p2.velocity, p1.velocity);

				var d = position.length();
				if (d >= PAR.h) continue;

				var w_pressure  = -PAR.mass * PAR.mass * PAR.stiffness
								* 45 / PAR.h6pi * (PAR.h - d) * (PAR.h - d)
								* (p1.pressure / p1.density / p1.density
								 + p2.pressure / p2.density / p2.density);
				var w_viscosity = PAR.mass * PAR.mass * PAR.viscosity
								* 90 / PAR.h6pi * (PAR.h - d)
								/ p2.density;

				p1.gravityForce.addScaledVector(position, w_pressure);
				p1.viscosityForce.addScaledVector(velocity, w_viscosity);
			}
		}
	}

	function move() {
		var position = new THREE.Vector3(0, 0, 0);
		var velocity = new THREE.Vector3(0, 0, 0);

		for (let p of particles) {
			position.copy(p.gravityForce);
			position.add(p.viscosityForce);
			position.add(p.pressureForce);
			position.multiplyScalar(PAR.dt / PAR.mass);
			position.add(p.velocity);
			position.multiplyScalar(PAR.dt);
			position.add(p.position);

			velocity.subVectors(position, p.position);
			velocity.multiplyScalar(1 / PAR.dt);

			p.position.copy(position);
			p.velocity.copy(velocity);
			bound(p);
		}
	}

	function bound(p) {
		if (p.position.x < PAR.radius) {
			p.velocity.x = -0.8 * p.velocity.x;
			p.position.x = PAR.radius;
		} else if (p.position.x > PAR.width - PAR.radius) {
			p.velocity.x = -0.8 * p.velocity.x;
			p.position.x = PAR.width - PAR.radius;
		}

		if (p.position.y < PAR.radius) {
			p.velocity.y = -0.8 * p.velocity.y;
			p.position.y = PAR.radius;
		} else if (p.position.y > PAR.width - PAR.radius) {
			//p.velocity.y = -0.8 * p.velocity.y;
			//p.position.y = PAR.width - PAR.radius;
		}

		if (p.position.z < PAR.radius) {
			p.velocity.z = -0.8 * p.velocity.z;
			p.position.z = PAR.radius;
		} else if (p.position.z > PAR.width - PAR.radius) {
			p.velocity.z = -0.8 * p.velocity.z;
			p.position.z = PAR.width - PAR.radius;
		}
	}

	var renderer = new THREE.WebGLRenderer({canvas: canvas});

	var meshes = [];
	var scene = new THREE.Scene();
	var geometry = new THREE.SphereGeometry( PAR.radius );
	var material = new THREE.MeshLambertMaterial({ color: 0x2FA1D6 });
	for (let p of particles) {
		var mesh = new THREE.Mesh(geometry, material);
		mesh.position.copy(p.position);
		scene.add(mesh);
		meshes.push(mesh);
	}

	var light = new THREE.AmbientLight( 0x333333 );
	scene.add( light );
	var light = new THREE.DirectionalLight();
	light.position.set(0, 1, 1);
	scene.add( light );
	var light = new THREE.DirectionalLight();
	light.position.set(0, 1, -1);
	scene.add( light );

	var camera = new THREE.PerspectiveCamera( 10, 1, 1, 10000 );
	camera.position.set(PAR.width / 2, PAR.width * 8, PAR.Z_DEPTH);
	var lookat = new THREE.Vector3(PAR.width / 2, PAR.width, PAR.width / 2);
	camera.lookAt( lookat );
	var angle = 0;
	canvas.onmousemove = function(e) {
		angle = (e.offsetX/canvas.width-.5)*5;
		camera.position.x = lookat.x + PAR.Z_DEPTH * Math.cos( angle );
		camera.position.z = lookat.z + PAR.Z_DEPTH * Math.sin( angle );
		camera.lookAt( lookat );
	}
	canvas.onmousedown = reset;

	function update() {
		force();
		move();

		for (let i = 0; i < particles.length; i++)
			meshes[i].position.copy(particles[i].position);

		renderer.render( scene, camera );
	}

	update();
})();
</script>
</div><div class="canvas" name="FluidSimulation3">
<canvas id="FluidSimulation3" width="200" height="200"></canvas>
<script id="FluidSimulation3.js">
// https://www3.nd.edu/~gtryggva/CFd-Course2017/MACcode.m
(function() {
	var canvas    = document.getElementById("FluidSimulation3");
	var ctx       = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var N         = 40;
	var u         = new Float32Array2D(N+1, N+2);
	var u0        = new Float32Array2D(N+1, N+2);
	var v         = new Float32Array2D(N+2, N+1);
	var v0        = new Float32Array2D(N+2, N+1);
	var p         = new Float32Array2D(N+2, N+2);
	var c         = new Float32Array2D(N+2, N+2);
	var div       = new Float32Array2D(N+2, N+2);

	function Float32Array2D(n, m) {
		var a = new Array(n);
		for (let i=0; i<n; ++i)
			a[i] = new Float32Array(m).fill(0);
		return a;
	}

	function update() {
		// mouse control
		interact(u, v, d);

		// velocity field
		boundary(u, v);
		interior(u, v);			[u, u0] = [u0, u]; [v, v0] = [v0, v];
		project(u, v);

		// visualization
		boundary_grids(d);
		flow_grids(u, v, d);	[d, d0] = [d0, d];
		decay_grids();
		flow_particles(u, v);
		button_mode ? draw_grids() : draw_particles();
//		draw_velocity_field(u, v);
		title();
	}

	// set boundary velocity
	function boundary(u, v) {
		var un, us, ve, vw;
		button_auto ? (un=ve=-1,us=vw=+1) : un=us=ve=vw=0;
		for (let i=1; i<=N; i++) u[i][0]   = 2*un-u[i][1];
		for (let i=1; i<=N; i++) u[i][N+1] = 2*us-u[i][N];
		for (let j=1; j<=N; j++) v[0][j]   = 2*vw-v[1][j];
		for (let j=1; j<=N; j++) v[N+1][j] = 2*ve-v[N][j];
	}

	// set interior velocity
	const h  = 1/N;
	const dt = 0.001;
	const nu = 0.1;		// kinematic viscosity
	function interior(u, v) {
		// move u-velocity
		for (let i=1; i<=N-1; i++) for (let j=1; j<=N; j++) {
			var laplace = u[i+1][j]+u[i-1][j]+u[i][j+1]+u[i][j-1]-4*u[i][j];
			var uu_x = Math.pow(u[i+1][j]+u[i][j], 2)
			         - Math.pow(u[i][j]+u[i-1][j], 2);
			var uv_y = (u[i][j+1]+u[i][j])*(v[i+1][j]+v[i][j])
			         - (u[i][j]+u[i][j-1])*(v[i+1][j-1]+v[i][j-1]);
			u0[i][j] = u[i][j] + dt*(-(uu_x+uv_y)/4/h + nu*laplace/h/h);
		}
//		[u, u0] = [u0, u];	// swap outside

		// move v-velocity
		for (let i=1; i<=N; i++) for (let j=1; j<=N-1; j++) {
			var laplace = v[i+1][j]+v[i-1][j]+v[i][j+1]+v[i][j-1]-4*v[i][j];
			var vv_y = Math.pow(v[i][j+1]+v[i][j],2)
			         - Math.pow(v[i][j]+v[i][j-1],2);
			var uv_x = (u[i][j+1]+u[i][j])*(v[i+1][j]+v[i][j])
			         - (u[i-1][j+1]+u[i-1][j])*(v[i][j]+v[i-1][j]);
			v0[i][j] = v[i][j] + dt*(-(uv_x+vv_y)/4/h + nu*laplace/h/h);
		}
//		[v, v0] = [v0, v];	// swap outside
	}

	// make div(u,v) = 0
	function project(u, v) {
		// get divergence
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			div[i][j] = u[i][j]-u[i-1][j]+v[i][j]-v[i][j-1];

		// solve for pressure
		solve(p, div);

		// remove divergence field
		for (let i=1; i<=N-1; ++i) for (let j=1; j<=N; ++j)
			u[i][j] -= (dt/h)*(p[i+1][j]-p[i][j]);
		for (let i=1; i<=N; ++i) for (let j=1; j<=N-1; ++j)
			v[i][j] -= (dt/h)*(p[i][j+1]-p[i][j]);
	}

	neighbor();
	function neighbor() {
		for (let i=2; i<=N-1; i++)
			for (let j=2; j<=N-1; j++)
				c[i][j] = 4;
		for (let i=2; i<=N-1; i++)
			c[1][i] = c[N][i] = c[i][1] = c[i][N] = 3;
		c[1][1] = c[1][N] = c[N][1] = c[N][N] = 2;
	}

	// successive over-relaxation
	const beta = 1.2;
	const iteration = 100;
	function solve(p, div) {
		for (let k=0; k<iteration; ++k)
			for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j) {
				var sum = p[i+1][j]+p[i-1][j]+p[i][j+1]+p[i][j-1];
				p[i][j] = beta*(sum-(h/dt)*div[i][j])/c[i][j]
						+ (1-beta)*p[i][j];
			}
	}

	// preconditioned conjugate gradient
	var M         = new Float32Array2D(N+2, N+2);
	var r         = new Float32Array2D(N+2, N+2);
	var s         = new Float32Array2D(N+2, N+2);
	var z         = new Float32Array2D(N+2, N+2);
	var q         = new Float32Array2D(N+2, N+2);

	function solve(x, b) {
		fill(x, 0);							// x = 0
		if (dot(b, b) < 1e-6) return;		// b == 0
		copy(r, b);							// r = b
		negate(r);							// r = -b
		buildPreconditioner(M);
		applyPreconditioner(M, r, z);		// z = f(r)
		var a = dot(z, r);					// a = z . r

		for (let k=0; k<N*N; k++) {
			copy(s, z);						// s = z
			compute_Ax(s, z);				// z = A(s)
			var alpha = a / dot(z, s);
			add(x, s, +alpha);				// x += alpha * s
			add(r, z, -alpha);				// r -= alpha * z
			var e = dot(r, r);				// e = r . r
			if (e/(N*N) < 1e-6) break;
			applyPreconditioner(M, r, z);	// z = f(r)
			var anew = dot(z, r);			// anew = z . r
			var beta = anew / a;
			add(z, s, +beta);				// z += beta * s
			a = anew;
		}

		// r = b - Ax
//		copy(r, b);
//		compute_Ax(x, z);
//		add(r, z, -1);
//		var error = Math.sqrt(dot(r, r));
//		console.log(error);
	}

	// modified incomplete Cholesky precondioner
	const tau = 0.97;
	function buildPreconditioner(M) {
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j) {
			var ai = M[i-1][j] * M[i-1][j];
			var aj = M[i][j-1] * M[i][j-1];
			var e  = c[i][j] - (1 - tau) * (ai + aj);
			M[i][j] = 1 / Math.sqrt(e + 1e-6);
		}
	}

	function applyPreconditioner(M, r, z) {
		// Lq = r
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j) {
			var ai = M[i-1][j] * q[i-1][j];
			var aj = M[i][j-1] * q[i][j-1];
			q[i][j] = (r[i][j] + ai + aj) * M[i][j];
		}

		// Lᵀz = q
		for (let i=N; i>=1; --i) for (let j=N; j>=1; --j) {
			var ai = M[i][j] * z[i+1][j];
			var aj = M[i][j] * z[i][j+1];
			z[i][j] = (q[i][j] + ai + aj) * M[i][j];
		}
	}

	// y = Ax
	function compute_Ax(x, y) {
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j) {
			var sum = x[i+1][j]+x[i-1][j]+x[i][j+1]+x[i][j-1];
			y[i][j] = (c[i][j] * x[i][j] - sum) * (dt/h);
		}
	}

	// x = k
	function fill(x, k) {
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			x[i][j] = k;
	}

	// x = y
	function copy(x, y) {
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			x[i][j] = y[i][j];
	}

	// x += k y
	function add(x, y, k) {
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			x[i][j] += k * y[i][j];
	}

	// z = x . y
	function dot(x, y) {
		var z = 0;
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			z += x[i][j] * y[i][j];
		return z;
	}

	// x = -x
	function negate(x) {
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			x[i][j] = -x[i][j];
	}

	// stream
	function draw_velocity_field(u, v) {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		for (let i=1; i<=N; ++i) {
			for (let j=1; j<=N; ++j) {
				var c = 0;
				var x = i - 0.5;
				var y = j - 0.5;
				ctx.beginPath();
				ctx.moveTo(x * width, y * width);
				for (let k=0; k<5; ++k) {
					var cu = interpolate(u, x, y + 0.5);
					var cv = interpolate(v, x + 0.5, y);
					var l = Math.sqrt(cu * cu + cv * cv);
					if (k == 0) c = (l * 255) | 0;
					if (l != 0) {cu /= l; cv /= l;}
					x -= cu;
					y -= cv;
					ctx.lineTo(x * width, y * width);
					if (!(x >= 0 && x < N && y >= 0 && y < N)) break;
				}
				ctx.lineWidth = 1;
				ctx.strokeStyle = 'rgba(' + c + ',' + c + ',' + c + ', 0.5)';
				ctx.stroke();
			}
		}
	}

	// arrow
	function draw_velocity_field(u, v) {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		for (let i=1; i<=N; ++i)
			for (let j=1; j<=N; ++j) {
				const slope = Math.PI / 18;
				const length = 8;
				var cu = (u[i-1][j] + u[i][j]) / 2;
				var cv = (v[i][j-1] + v[i][j]) / 2;
				var angle = Math.atan2(cv, cu);
				var l = Math.sqrt(cu * cu + cv * cv);
				var c = (l * 255) | 0;
				if (l != 0) {cu /= l; cv /= l;}
				ctx.save();
				ctx.translate((i-0.5)*width, (j-0.5)*width);
				ctx.translate(cu, cv);
				ctx.beginPath();
				ctx.moveTo(0, 0);
				ctx.lineTo(-length * Math.cos(angle-slope),
						   -length * Math.sin(angle-slope));
				ctx.lineTo(-length * Math.cos(angle+slope),
						   -length * Math.sin(angle+slope));
				ctx.closePath();
				ctx.fillStyle = 'rgb(' + c + ',' + c + ',' + c + ')';
				ctx.fill();
				ctx.restore();
			}
	}

	var d         = new Float32Array2D(N+2, N+2);
	var d0        = new Float32Array2D(N+2, N+2);

	function boundary_grids(d) {
		// boundary density
		for (let i=1; i<=N; i++) d[i][0]   = d[i][1];
		for (let i=1; i<=N; i++) d[i][N+1] = d[i][N];
		for (let j=1; j<=N; j++) d[0][j]   = d[1][j];
		for (let j=1; j<=N; j++) d[N+1][j] = d[N][j];
		d[0][0] = d[1][1]; d[0][N+1] = d[0][N];
		d[N][0] = d[N][1]; d[N+1][N+1] = d[N][N];
	}

	const speed = 1000;
	function flow_grids(u, v, d) {
		// semi-lagrangian
		for (let i=1; i<=N; i++) {
			for (let j=1; j<=N; j++) {
				var cu = (u[i-1][j] + u[i][j]) / 2;
				var cv = (v[i][j-1] + v[i][j]) / 2;
				var x = i - 0.5 - cu * dt * speed;
				var y = j - 0.5 - cv * dt * speed;
				if (x < 0) x = 0; if (x > N) x = N;
				if (y < 0) y = 0; if (y > N) y = N;
				d0[i][j] = interpolate(d, x + 0.5, y + 0.5);
			}
		}
//		[d, d0] = [d0, d];
	}

	function interpolate(a, x, y) {
		var x0 = x | 0;  var x1 = x0 + 1;
		var y0 = y | 0;  var y1 = y0 + 1;
		var s1 = x - x0; var s0 = 1 - s1;
		var t1 = y - y0; var t0 = 1 - t1;
		return s0 * (t0 * a[x0][y0] + t1 * a[x0][y1])
			 + s1 * (t0 * a[x1][y0] + t1 * a[x1][y1]);
	}

	const rate = 0.995;
	function decay_grids() {
		for (let i=1; i<=N; i++)
			for (let j=1; j<=N; j++)
				d[i][j] *= rate;
	}

	const width = (canvas.width / N) | 0;
	function draw_grids() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		for (let i=1; i<=N; ++i) {
			for (let j=1; j<=N; ++j) {
				ctx.fillStyle = 'rgba(0,128,255,' + d[i][j] + ')';
				ctx.fillRect((i-1) * width, (j-1) * width, width, width);
			}
		}
	}

	const P = 500;
	var particles = [];

	init_particles();
	function init_particles() {
		for (let i=0; i<P; i++) {
			particles.push({
				x: Math.random() * N,
				y: Math.random() * N,
			});
		}
	}

	function flow_particles(u, v) {
		for (let p of particles) {
			var pu = interpolate(u, p.x, p.y + 0.5);
			var pv = interpolate(v, p.x + 0.5, p.y);
			p.x += pu * dt * speed;
			p.y += pv * dt * speed;
			if (!(p.x >= 0 && p.x < N && p.y >= 0 && p.y < N)) {
				p.x = Math.random() * N;
				p.y = Math.random() * N;
			}
		}
	}

	const size = 5;
	function draw_particles() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		ctx.fillStyle = 'blue';
		for (let p of particles)
			ctx.fillRect(p.x*width - size/2, p.y*width - size/2, size, size);
	}

	var mouseStart = [], mouseEnd = [];
	canvas.onmousemove = function(e){
		mouseEnd = [e.offsetX, e.offsetY];
	};
	canvas.onmouseover = function(e){
		mouseStart = mouseEnd = [e.offsetX, e.offsetY];
	};

	const pull = 2;
	function interact(u, v, d) {
		var mx = mouseEnd[0] - mouseStart[0];
		var my = mouseEnd[1] - mouseStart[1];
		var md = Math.sqrt(mx*mx + my*my);
		var oldx = -1, oldy = -1;
		for (let i=0; i<md; i++) {
			var x = ((mouseStart[0] + mx/md * i) / width | 0) + 1;
			var y = ((mouseStart[1] + my/md * i) / width | 0) + 1;
			if (x == oldx && y == oldy) continue;
			oldx = x; oldy = y;
			if (x >= 1 && x <= N-1 && y >= 1 && y <= N) u[x][y] += mx/md * pull;
			if (x >= 1 && x <= N && y >= 1 && y <= N-1) v[x][y] += my/md * pull;
			if (x >= 1 && x <= N && y >= 1 && y <= N)   d[x][y] += 1;
		}
		mouseStart = mouseEnd;
	}

	var button_auto = false;
	var button_mode = false;
	canvas.onkeydown = function(e) {
		if (e.keyCode == 65) button_auto = !button_auto;	// a
		if (e.keyCode == 88) button_mode = !button_mode;	// x
	}

	function title() {
		ctx.font = "12pt verdana";
		ctx.textBaseline = "top";
		ctx.textAlign = "center";
		ctx.fillStyle = "gray";
		ctx.fillText("[a] auto [x] particle/grid", canvas.width/2, 0);
		ctx.fillStyle = "white";
	}

	draw_particles();
})();
</script>
</div><div class="canvas" name="FluidSimulation4">
<canvas id="FluidSimulation4" width="200" height="200"></canvas>
<script id="TinyQueue.js">
// https://github.com/mourner/tinyqueue
var TinyQueue = function TinyQueue(data, compare) {
	if ( data === void 0 ) data = [];
	if ( compare === void 0 ) compare = function(a,b) {return a-b;}

	this.data = data;
	this.length = this.data.length;
	this.compare = compare;

	if (this.length > 0) {
		for (var i = (this.length >> 1) - 1; i >= 0; i--) { this._down(i); }
	}
};

TinyQueue.prototype.push = function push (item) {
	this.data.push(item);
	this.length++;
	this._up(this.length - 1);
};

TinyQueue.prototype.pop = function pop () {
	if (this.length === 0) { return void 0; }

	var top = this.data[0];
	this.length--;

	if (this.length > 0) {
		this.data[0] = this.data[this.length];
		this._down(0);
	}
	this.data.pop();

	return top;
};

TinyQueue.prototype.peek = function peek () {
	return this.data[0];
};

TinyQueue.prototype._up = function _up (pos) {
	var data = this.data;
	var compare = this.compare;
	var item = data[pos];

	while (pos > 0) {
		var parent = (pos - 1) >> 1;
		var current = data[parent];
		if (compare(item, current) >= 0) { break; }
		data[pos] = current;
		pos = parent;
	}

	data[pos] = item;
};

TinyQueue.prototype._down = function _down (pos) {
	var data = this.data;
	var compare = this.compare;
	var halfLength = this.length >> 1;
	var item = data[pos];

	while (pos < halfLength) {
		var left = (pos << 1) + 1;
		var right = left + 1;
		var best = data[left];

		if (right < this.length && compare(data[right], best) < 0) {
			left = right;
			best = data[right];
		}
		if (compare(best, item) >= 0) { break; }

		data[pos] = best;
		pos = left;
	}

	data[pos] = item;
};
</script>
<script id="FluidSimulation4.js">
(function() {
	var canvas    = document.getElementById("FluidSimulation4");
	var ctx       = canvas.getContext("2d");

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	var mouse = [0, 0];
	var drag = false;
	canvas.onmousedown = function(e){mouse = [e.offsetX, e.offsetY]; drag = true;};
	canvas.onmousemove = function(e){mouse = [e.offsetX, e.offsetY];};
	canvas.onmouseup   = function(e){drag = false;}
	canvas.onmouseout  = function(e){drag = false;}

	var N         = 40;
	var u         = new Float32Array2D(N+1, N+2);
	var u0        = new Float32Array2D(N+1, N+2);
	var v         = new Float32Array2D(N+2, N+1);
	var v0        = new Float32Array2D(N+2, N+1);
	var d         = new Float32Array2D(N+2, N+2);
	var d0        = new Float32Array2D(N+2, N+2);
	var p         = new Float32Array2D(N+2, N+2);
	var c         = new Float32Array2D(N+2, N+2);
	var div       = new Float32Array2D(N+2, N+2);

	function Float32Array2D(n, m) {
		var a = new Array(n);
		for (let i=0; i<n; ++i)
			a[i] = new Float32Array(m).fill(0);
		return a;
	}

	function fill(a, v) {for (let i=0; i<a.length; ++i) a[i].fill(v);}
	function copy(a, b) {for (let i=0; i<a.length; ++i) a[i].set(b[i]);}
	function swap(a, b) {for (let i=0; i<a.length; ++i) [a[i], b[i]] = [b[i], a[i]];}

	function update(){
		// velocity field
		redistance(d);		// air/water distance 
		air(u, v, d);		// air velocity
		boundary(u, v, d);	// wall velocity
		water(u, v, d);		// water velocity
		project(u, v, d);

		// visualization
		draw_water(d);
//		draw_surface();
		title();
	}

	function water(u, v, d) {
		diffuse(u, v, d);
		flow(u, v, d);
		gravity(u, v, d);
		interact(u, v, d);
	}

	neighbor();
	function neighbor() {
		for (let i=2; i<=N-1; i++)
			for (let j=2; j<=N-1; j++)
				c[i][j] = 4;
		for (let i=2; i<=N-1; i++)
			c[1][i] = c[N][i] = c[i][1] = c[i][N] = 3;
		c[1][1] = c[1][N] = c[N][1] = c[N][N] = 2;
	}

	const dir8    = [[-1,-1],[-1,0],[-1,1],[0,1],[1,1],[1,0],[1,-1],[0,-1]];
	var posx      = new Float32Array2D(N+2, N+2);
	var posy      = new Float32Array2D(N+2, N+2);
	var visit     = new Float32Array2D(N+2, N+2);
	var estimate  = new Float32Array2D(N+2, N+2);

	function reset() {
		fill(u, 0);
		fill(v, 0);
		init_distance(d);
	}

	init_distance(d);
	function init_distance(d) {
		for (let i=1; i<=N; i++)
			for (let j=1; j<=N; j++)
				d[i][j] = +1;

		for (let i=5; i<=N-5; i++)
			for (let j=10; j<=N-10; j++)
				d[i][j] = -1;

		for (let i=1; i<=N; i++) d[i][0]   = +0.5;
		for (let i=1; i<=N; i++) d[i][N+1] = +0.5;
		for (let j=1; j<=N; j++) d[0][j]   = +0.5;
		for (let j=1; j<=N; j++) d[N+1][j] = +0.5;
		d[0][0] = d[0][N+1]   = Math.sqrt(2) / 2;
		d[N][0] = d[N+1][N+1] = Math.sqrt(2) / 2;
	}

	function estimate_distance(d, i, j) {
		var sign = (d[i][j] > 0 ? +1 : -1);
		var mind = 1e9;
		for (let dir of dir8) {
			var ni = i + dir[0];
			var nj = j + dir[1];
			if (visit[ni][nj]) {
				var dist = Math.hypot(posx[ni][nj] - i, posy[ni][nj] - j);
				if (dist < mind) {
					mind = dist;
					posx[i][j] = posx[ni][nj];
					posy[i][j] = posy[ni][nj];
					d[i][j] = sign * dist;
				}
			}
		}
		return mind < 1e9;
	}

	function redistance(d) {
		function cmp(a, b) {return d[a[0]][a[1]] - d[b[0]][b[1]];}
		var queue = new TinyQueue([], cmp);
		fill(visit, false);
		fill(estimate, false);

		// surface distance
		for (let i=1; i<=N; i++) for (let j=1; j<=N; j++) {
			// find zero contour
			var pos = new Array(8);
			for (let k=0; k<8; ++k) {
				var ni = i + dir8[k][0];
				var nj = j + dir8[k][1];
				if (!(ni>=1 && ni<=N && nj>=1 && nj<=N)) continue;
				var d1 = d[i][j];
				var d2 = d[ni][nj];
				if (d1 * d2 < 0) {
					var w = d1 / (d1 - d2);
					pos[k] = [(1-w)*i + w*ni, (1-w)*j + w*nj];
				}
			}

			// distance to zero contour: two neighbors
			var mind = 1e9;
			for (let k=0; k<8; ++k) for (let l=1; l<=2; ++l) {
				if (!pos[k]) continue;
				if (!pos[(k+l)%8]) continue;
				var [x1,y1] = pos[k];
				var [x2,y2] = pos[(k+l)%8];

				// project point (i,j) onto segment x1->x2
				var s = ((i-x1)*(x2-x1) + (j-y1)*(y2-y1))
				      / ((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
				s = Math.min(Math.max(s, 0), 1);
				var x = x1 + s * (x2-x1);
				var y = y1 + s * (y2-y1);

				var dist = Math.hypot(i-x, j-y);
				if (dist < mind) {
					mind = dist;
					posx[i][j] = x;
					posy[i][j] = y;
					dist = Math.max(dist, 1e-6);	// prevent zero
					d[i][j] = (d[i][j] > 0 ? +1 : -1) * dist;
				}
			}

			// distance to zero contour: one neighbor
			for (let k=0; k<8; ++k) {
				if (pos[k] == null) continue;
				var dist = Math.hypot(pos[k][0] - i, pos[k][1] - j);
				if (dist < mind) {
					mind = dist;
					posx[i][j] = pos[k][0];
					posy[i][j] = pos[k][1];
					dist = Math.max(dist, 1e-6);	// prevent zero
					d[i][j] = (d[i][j] > 0 ? +1 : -1) * dist;
				}
			}

			if (mind < 1e9) {
				queue.push([i,j]);
				visit[i][j] = true;
			}
		}

		// air distance + water distance
		while (queue.length > 0) {
			var [i,j] = queue.pop();
			visit[i][j] = true;

			for (let dir of dir8) {
				var ni = i + dir[0];
				var nj = j + dir[1];
				if (!(ni>=1 && ni<=N && nj>=1 && nj<=N)) continue;
				if (!estimate[ni][nj] && !visit[ni][nj]) {
					if (estimate_distance(d, ni, nj)) {
						estimate[ni][nj] = true;
						queue.push([ni,nj]);
					}
				}
			}
		}
	}

	var uc         = new Float32Array2D(N+2, N+2);
	var vc         = new Float32Array2D(N+2, N+2);

	function air(u, v, d) {
		// from edge to center
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			uc[i][j] = (u[i-1][j] + u[i][j]) / 2
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			vc[i][j] = (v[i][j-1] + v[i][j]) / 2

		// bfs
		fill(visit, false);

		// push air to queue
		var queue = [];
		for (let i=1; i<=N; i++)
			for (let j=1; j<=N; j++)
				if (d[i][j] < 0)
					visit[i][j] = true;
				else
					queue.push([i,j]);

		// sort by distance
		function cmp(a, b) {return d[a[0]][a[1]] - d[b[0]][b[1]];}
		queue.sort(cmp);

		// estimate velocity: average velocity of neighbors
		while (queue.length > 0) {
			[i,j] = queue.shift();
			visit[i][j] = true;

			var usum = 0, vsum = 0, csum = 0;
			for (let dir of dir8) {
				var ni = i + dir[0];
				var nj = j + dir[1];
				if (visit[ni][nj]) {
					usum += uc[ni][nj];
					vsum += vc[ni][nj];
					csum ++;
				}
			}

			if (csum > 0) {
				uc[i][j] = usum / csum;
				vc[i][j] = vsum / csum;
			} else {
				queue.push([i,j]);
			}
		}

		// from center to edge
		for (let i=1; i<=N-1; ++i) for (let j=1; j<=N; ++j)
			u[i][j] = (uc[i+1][j] + uc[i][j]) / 2;
		for (let i=1; i<=N; ++i) for (let j=1; j<=N-1; ++j)
			v[i][j] = (vc[i][j+1] + vc[i][j]) / 2;
	}

	var un = 0, us = 0, ve = 0, vw = 0;
	function boundary(u, v, d) {
		for (let i=1; i<=N; i++) u[i][0]   = 2*un-u[i][1];
		for (let i=1; i<=N; i++) u[i][N+1] = 2*us-u[i][N];
		for (let j=1; j<=N; j++) v[0][j]   = 2*vw-v[1][j];
		for (let j=1; j<=N; j++) v[N+1][j] = 2*ve-v[N][j];
	}

	const h  = 1;
	const dt = 1;
	const nu = 0.1;	// kinematic viscosity
	const diffusion = dt*nu/h/h;
	function diffuse(u, v, d) {
		copy(u0, u); copy(v0, v);
		for (let k=0; k<100; k++) {
			for (let i=1; i<=N-1; i++) for (let j=1; j<=N; j++) 
				u[i][j] = (u0[i][j] + diffusion * (u[i-1][j] + u[i+1][j] + u[i][j-1] + u[i][j+1])) / (1 + 4 * diffusion);
			for (let i=1; i<=N; i++) for (let j=1; j<=N-1; j++)
				v[i][j] = (v0[i][j] + diffusion * (v[i-1][j] + v[i+1][j] + v[i][j-1] + v[i][j+1])) / (1 + 4 * diffusion);
		}
	}

	function flow(u, v, d) {
		for (let i=1; i<=N-1; i++) {
			for (let j=1; j<=N; j++) {
				var x = i - u[i][j] * dt;
				var y = j - 0.5 - (v[i][j-1]+v[i][j]+v[i+1][j-1]+v[i+1][j])/4 * dt;
				if (x < 0) x = 0; if (x >= N) x = N - 1e-6;
				if (y < 0) y = 0; if (y > N) y = N;
				u0[i][j] = interpolate(u, x, y + 0.5);
			}
		}

		for (let i=1; i<=N; i++) {
			for (let j=1; j<=N-1; j++) {
				var x = i - 0.5 - (u[i-1][j]+u[i-1][j+1]+u[i][j]+u[i][j+1])/4 * dt;
				var y = j - v[i][j] * dt;
				if (x < 0) x = 0; if (x > N) x = N;
				if (y < 0) y = 0; if (y >= N) y = N - 1e-6;
				v0[i][j] = interpolate(v, x + 0.5, y);
			}
		}

		for (let i=1; i<=N; i++) {
			for (let j=1; j<=N; j++) {
				var x = i - 0.5 - (u[i-1][j] + u[i][j]) / 2 * dt;
				var y = j - 0.5 - (v[i][j-1] + v[i][j]) / 2 * dt;
				if (x < 0) x = 0; if (x > N) x = N;
				if (y < 0) y = 0; if (y > N) y = N;
				d0[i][j] = interpolate(d, x + 0.5, y + 0.5);
			}
		}

		swap(u0, u); swap(v0, v); swap(d0, d);
	}

	function interpolate(a, x, y) {
		var x0 = x | 0;  var x1 = x0 + 1;
		var y0 = y | 0;  var y1 = y0 + 1;
		var s1 = x - x0; var s0 = 1 - s1;
		var t1 = y - y0; var t0 = 1 - t1;
		return s0 * (t0 * a[x0][y0] + t1 * a[x0][y1])
			 + s1 * (t0 * a[x1][y0] + t1 * a[x1][y1]);
	}

	const g = 0.3;
	function gravity(u, v, d) {
		for (let i=1; i<=N; ++i) for (let j=1; j<=N-1; ++j)
			if (d[i][j] < 0 || d[i][j+1] < 0)
				v[i][j] += g * dt;
	}

	function interact(u, v, d) {
		if (!drag) return;
		for (let i=1; i<=N-1; ++i) for (let j=1; j<=N; ++j) {
			var dx = mouse[0] - i * width;
			var dy = mouse[1] - (j - 0.5) * width;
			var l = Math.sqrt(dx*dx + dy*dy);
			if (l > 0) dx /= l;
			if (d[i+1][j] < 0 || d[i][j] < 0)
				u[i][j] += dx * 0.5 * dt;
		}
		for (let i=1; i<=N; ++i) for (let j=1; j<=N-1; ++j) {
			var dx = mouse[0] - (i - 0.5) * width;
			var dy = mouse[1] - j * width;
			var l = Math.sqrt(dx*dx + dy*dy);
			if (l > 0) dy /= l;
			if (d[i][j] < 0 || d[i][j+1] < 0)
				v[i][j] += dy * 0.5 * dt;
		}
	}

	// make div(u,v) = 0
	function project(u, v) {
		// get divergence
		fill(div, 0);
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			if (d[i][j] < 0)
				div[i][j] = u[i][j]-u[i-1][j]+v[i][j]-v[i][j-1];

		// solve for pressure
//		solve(p, div);
		pcg(p, div);

		// remove divergence field
		for (let i=1; i<=N-1; ++i) for (let j=1; j<=N; ++j)
			u[i][j] -= (dt/h)*(p[i+1][j]-p[i][j]);
		for (let i=1; i<=N; ++i) for (let j=1; j<=N-1; ++j)
			v[i][j] -= (dt/h)*(p[i][j+1]-p[i][j]);
	}

	// successive over-relaxation
	const beta = 1.2;
	const iteration = 100;
	function solve(p, div) {
		fill(p, 0);		// wall/air pressure is zero
		for (let k=0; k<iteration; ++k) {
			for (let i=1; i<=N; ++i)
				for (let j=1; j<=N; ++j)
					if (d[i][j] < 0) {
						var sum = p[i+1][j]+p[i-1][j]+p[i][j+1]+p[i][j-1];
						p[i][j] = beta*(sum-(h/dt)*div[i][j])/c[i][j]
								+ (1-beta)*p[i][j];
					}
		}
	}

	var M         = new Float32Array2D(N+2, N+2);
	var r         = new Float32Array2D(N+2, N+2);
	var s         = new Float32Array2D(N+2, N+2);
	var z         = new Float32Array2D(N+2, N+2);
	var q         = new Float32Array2D(N+2, N+2);

	function pcg(x, b) {
		fill(p, 0);							// x = 0
		if (dot(b, b) < 1e-6) return;		// b == 0
		copy(r, b);							// r = b
		negate(r);							// r = -b
		buildPreconditioner(M);
		applyPreconditioner(M, r, z);		// z = f(r)
		var a = dot(z, r);					// a = z . r

		for (let k=0; k<N*N; k++) {
			copy(s, z);						// s = z
			compute_Ax(s, z);				// z = A(s)
			var alpha = a / dot(z, s);
			add(x, s, +alpha);				// x += alpha * s
			add(r, z, -alpha);				// r -= alpha * z
			var e = dot(r, r);				// e = r . r
			if (e/(N*N) < 1e-6) break;
			applyPreconditioner(M, r, z);	// z = f(r)
			var anew = dot(z, r);			// anew = z . r
			var beta = anew / a;
			add(z, s, +beta);				// z += beta * s
			a = anew;
		}

		// r = b - Ax
//		copy(r, b);
//		compute_Ax(x, z);
//		add(r, z, -1);
//		var error = Math.sqrt(dot(r, r));
//		console.log(error);
	}

	function sgn(i, j) {
		return (i>=1 && i<=N && j>=1 && j<=N) && d[i][j] < 0;
	}

	// modified incomplete Cholesky precondioner
	const tau = 0.97;
	function buildPreconditioner(M) {
		fill(M, 0);
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j) {
			if (d[i][j] < 0) {
				var ai = sgn(i-1,j) * M[i-1][j] * M[i-1][j];
				var aj = sgn(i,j-1) * M[i][j-1] * M[i][j-1];
				var e  = c[i][j] - (1 - tau) * (ai + aj);
				M[i][j] = 1 / Math.sqrt(e + 1e-6);
			}
		}
	}

	function applyPreconditioner(M, r, z) {
		// Lq = r
		fill(q, 0);
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j) {
			if (d[i][j] < 0) {
				var left   = sgn(i-1,j) * M[i-1][j] * q[i-1][j];
				var bottom = sgn(i,j-1) * M[i][j-1] * q[i][j-1];
				q[i][j] = (r[i][j] + left + bottom) * M[i][j];
			}
		}

		// L^T z = q
		fill(z, 0);
		for (let i=N; i>=1; --i) for (let j=N; j>=1; --j) {
			if (d[i][j] < 0) {
				var right = sgn(i+1,j) * M[i][j] * z[i+1][j];
				var top   = sgn(i,j+1) * M[i][j] * z[i][j+1];
				z[i][j] = (q[i][j] + right + top) * M[i][j];
			}
		}
	}

	// y = Ax
	function compute_Ax(x, y) {
		fill(y, 0);
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j) {
			if (d[i][j] < 0) {
				var sum = x[i+1][j]+x[i-1][j]+x[i][j+1]+x[i][j-1];
				y[i][j] = (c[i][j] * x[i][j] - sum) * (dt/h);
			}
		}
	}

	// x += k y
	function add(x, y, k) {
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			x[i][j] += k * y[i][j];
	}

	// z = x . y
	function dot(x, y) {
		var z = 0;
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			z += x[i][j] * y[i][j];
		return z;
	}

	// x = -x
	function negate(x) {
		for (let i=1; i<=N; ++i) for (let j=1; j<=N; ++j)
			x[i][j] = -x[i][j];
	}

	const width = (canvas.width / N) | 0;
	function draw_water(d) {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		for (var i=1; i<=N; ++i) for (var j=1; j<=N; ++j) {
			if (d[i][j] > 0) continue;
			var bw = Math.trunc(d[i][j] * 40);
			var c = d[i][j] < 0 ? [0, 0, 127] : [bw, bw, bw];
//			var c = d[i][j] < 0 ? [0, 0, -bw] : [bw, bw, bw];
			ctx.fillStyle = 'rgba(' + c[0] + ',' + c[1] + ',' + c[2] + ', 0.9)';
			ctx.fillRect((i-1) * width, (j-1) * width, width, width);
		}
	}

	function draw_surface() {
		var scale = canvas.width / N;
		ctx.fillStyle = "rgb(127,0,0)";
		for (var i=1; i<=N; ++i) for (var j=1; j<=N; ++j) {
			if (d[i][j] < 0) {
				ctx.beginPath();
				ctx.arc(posx[i][j]*scale, posy[i][j]*scale, 2, 0, 2*Math.PI, true);
				ctx.closePath();
				ctx.fill();
			}
		}
	}

	canvas.onkeydown = function(e) {
		if (e.keyCode == 90) reset();	// z
	}

	function title() {
		ctx.font = "12pt Verdana";
		ctx.textBaseline = "top";
		ctx.textAlign = "center";
		ctx.fillStyle = "gray";
		ctx.fillText("[z] reset", canvas.width/2, 0);
	}

	draw_water(d);
})();
</script>
</div><div class="canvas" name="SoftBodySimulation1">
<canvas id="SoftBodySimulation1" width="200" height="200"></canvas>
<script id="SoftBodySimulation1.js">
// https://github.com/dissimulate/jelly
(function() {
	var canvas = document.getElementById('SoftBodySimulation1');
	var ctx = canvas.getContext('2d');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	const ITERATIONS = 2;
	const SPACING    = 14;		// h
	const GRAVITY    = 0.05;	// g
	const SPEED      = 1;		// dt

	function clamp(val, min, max) {
		return Math.min(Math.max(val, min), max);
	};

	class Vector {
		constructor (x = 0, y = 0) {
			this.x = x;
			this.y = y;
		}

		add (v) {
			this.x += v.x;
			this.y += v.y;
			return this;
		}

		sub (v) {
			this.x -= v.x;
			this.y -= v.y;
			return this;
		}

		scale (k) {
			this.x *= k;
			this.y *= k;
			return this;
		}

		static add (v1, v2) {
			return new Vector(v1.x + v2.x, v1.y + v2.y);
		}

		static sub (v1, v2) {
			return new Vector(v1.x - v2.x, v1.y - v2.y);
		}

		static scale (v, k) {
			return new Vector(v.x * k, v.y * k);
		}

		static dist (v1, v2) {
			return Math.hypot(v1.x - v2.x, v1.y - v2.y);
		}

		static rot (v, t) {
			var cos_t = Math.cos(t);
			var sin_t = Math.sin(t);
			return new Vector(
				v.x * cos_t - v.y * sin_t,
				v.x * sin_t + v.y * cos_t 
			);
		}
	}

	class Particle {
		constructor (x, y) {
			this.position = new Vector(x, y);
			this.velocity = new Vector();
			this.force    = new Vector();
		}

		update () {
			this.velocity.add(Vector.scale(this.force, SPEED));
			this.position.add(Vector.scale(this.velocity, SPEED));
			this.force = new Vector(0, GRAVITY / ITERATIONS);
		}

		wall () {
			const padding = 5;
			const damping = 0.6;

			var x = padding - this.position.x;
			if (x > 0) {
				this.force.add(new Vector(x, 0));
				this.velocity.y *= damping;
			}

			var x = canvas.width - padding - this.position.x;
			if (x < 0) {
				this.force.add(new Vector(x, 0));
				this.velocity.y *= damping;
			}

			var y = padding - this.position.y
			if (y > 0) {
				this.force.add(new Vector(0, y));
				this.velocity.x *= damping;
			}

			var y = canvas.height - padding - this.position.y;
			if (y < 0) {
				this.force.add(new Vector(0, y));
				this.velocity.x *= damping;
			}
		}

		interact () {
			const range = SPACING * 3;
			const pull  = 0.002;

			if (!press) {this.fixed = false; return;}
			if (!this.fixed) {	// first-time initialization
				if (Vector.dist(this.position, mouse) < range) { 
					this.fixed = true;
					this.offset = Vector.sub(this.position, mouse);
					this.velocity.scale(0);
					this.force.scale(0);
				}
			}
			if (!this.fixed) return;

			var target = Vector.add(mouse, this.offset);
			var diff   = Vector.sub(target, this.position);
			diff.scale(pull);
			this.force.add(diff.scale(SPEED));
			this.velocity.scale(0.99);
		}
	}

	class Constraint {
		constructor (a, b) {
			this.a = a;
			this.b = b;
			this.spring_length = Vector.dist(a.position, b.position); 
		}

		resolve () {
			let {a, b, spring_length} = this;

			var posAB = Vector.sub(b.position, a.position);
			var velAB = Vector.sub(b.velocity, a.velocity);

			const ks = 0.01;	// spring constant
			const kd = 0.01;	// drag constant
			var length = Vector.dist(a.position, b.position);
			var force  = ks * (length - spring_length);
			var spring = Vector.scale(posAB, force);
			var drag   = Vector.scale(velAB, kd);
			var f      = Vector.add(spring, drag);
			a.force.add(f);
			b.force.sub(f);
		}
	}

	var particles = [];
	var constraints = [];

	class Square {
		constructor (width, height) {
			const xbase = (canvas.width  - (width-1)  * SPACING) >> 1;
			const ybase = (canvas.height - (height-1) * SPACING) >> 1;

			particles = [];
			for (let i=0; i<height; ++i)
				for (let j=0; j<width; ++j) {
					var index = i * width + j;
					var x = xbase + i * SPACING;
					var y = ybase + j * SPACING;
					particles.push( new Particle(x, y) );
				}

			constraints = [];
			for (let i=0; i<height; ++i)
				for (let j=0; j<width; ++j) {
					var index = i * width + j;

					if (j > 0) {
						constraints.push( new Constraint(
							particles[index - 1],
							particles[index]
						));
					}

					if (i > 0) {
						constraints.push( new Constraint(
							particles[index - width],
							particles[index]
						));
					}

					if (i > 0 && j > 0) {
						constraints.push( new Constraint(
							particles[index - width],
							particles[index - 1]
						));
						constraints.push( new Constraint(
							particles[index - 1 - width],
							particles[index]
						));
					}

					if (j > 1) {
						constraints.push( new Constraint(
							particles[index - 2],
							particles[index]
						));
					}

					if (i > 1) {
						constraints.push( new Constraint(
							particles[index - width*2],
							particles[index]
						));
					}

					if (i > 1 && j > 1) {
						constraints.push( new Constraint(
							particles[index - width*2],
							particles[index - 2]
						));
						constraints.push( new Constraint(
							particles[index - 2 - width*2],
							particles[index]
						));
					}
				}
		}

		draw(ctx) {
			ctx.lineWidth   = 2;
			ctx.strokeStyle = 'darkgreen';
			ctx.beginPath();
			for (let c of constraints) {
				ctx.moveTo(c.a.position.x, c.a.position.y);
				ctx.lineTo(c.b.position.x, c.b.position.y);
			}
			ctx.stroke();
		}
	}

	var square = new Square(8, 8);
	update();

	function update() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);

		for (let i = ITERATIONS; i--;) {
			for (let constraint of constraints) {
				constraint.resolve();
			}

			for (let particle of particles) {
				particle.interact();
				particle.wall();
				particle.update();
			}
		}

		square.draw(ctx);
	};

	var press = false;
	canvas.onmousedown = function() {press = true;};
	canvas.onmouseup = function() {press = false;};
	canvas.onmouseenter = function(e) {press = (e.buttons == 1);}

	var mouse = new Vector(0, 0);
	canvas.onmousemove = function(e) {
		mouse.x = e.offsetX;
		mouse.y = e.offsetY;
	}
})();
</script>
</div><div class="canvas" name="SoftBodySimulation2">
<canvas id="SoftBodySimulation2" width="200" height="200"></canvas>
<script id="SoftBodySimulation2.js">
// https://github.com/dissimulate/jelly
(function() {
	var canvas = document.getElementById('SoftBodySimulation2');
	var ctx = canvas.getContext('2d');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	const ITERATIONS = 14;
	const SPACING    = 14;		// h
	const GRAVITY    = 0.05;	// g
	const SPEED      = 1;		// dt

	function clamp(val, min, max) {
		return Math.min(Math.max(val, min), max);
	};

	class Vector {
		constructor (x = 0, y = 0) {
			this.x = x;
			this.y = y;
		}

		add (v) {
			this.x += v.x;
			this.y += v.y;
			return this;
		}

		sub (v) {
			this.x -= v.x;
			this.y -= v.y;
			return this;
		}

		scale (k) {
			this.x *= k;
			this.y *= k;
			return this;
		}

		static add (v1, v2) {
			return new Vector(v1.x + v2.x, v1.y + v2.y);
		}

		static sub (v1, v2) {
			return new Vector(v1.x - v2.x, v1.y - v2.y);
		}

		static scale (v, k) {
			return new Vector(v.x * k, v.y * k);
		}

		static dist (v1, v2) {
			return Math.hypot(v1.x - v2.x, v1.y - v2.y);
		}

		static rot (v, t) {
			var cos_t = Math.cos(t);
			var sin_t = Math.sin(t);
			return new Vector(
				v.x * cos_t - v.y * sin_t,
				v.x * sin_t + v.y * cos_t 
			);
		}
	}

	class Particle {
		constructor (x, y, w) {
			// linear motion
			this.position = new Vector(x, y);
			this.velocity = new Vector();
			this.force    = new Vector();

			// angular motion
			this.angle  = 0;
			this.w      = w;
			this.torque = 0;
		}

		update () {
			// linear motion
			this.velocity.add(Vector.scale(this.force, SPEED));
			this.position.add(Vector.scale(this.velocity, SPEED));
			this.force = new Vector(0, GRAVITY / ITERATIONS);

			// angular motion
			const inertia = ((SPACING / 2) ** 2) / 2;
			this.w += this.torque / inertia * SPEED;
			const qPI = Math.PI / 4;
			this.w = clamp(this.w, -qPI, qPI);
			this.angle += this.w * SPEED;
			this.torque = 0;
		}

		wall () {
			const padding = 5;
			const damping = 0.6;

			var x = padding - this.position.x;
			if (x > 0) {
				this.force.add(new Vector(x, 0));
				this.velocity.y *= damping;
			}

			var x = canvas.width - padding - this.position.x;
			if (x < 0) {
				this.force.add(new Vector(x, 0));
				this.velocity.y *= damping;
			}

			var y = padding - this.position.y
			if (y > 0) {
				this.force.add(new Vector(0, y));
				this.velocity.x *= damping;
			}

			var y = canvas.height - padding - this.position.y;
			if (y < 0) {
				this.force.add(new Vector(0, y));
				this.velocity.x *= damping;
			}
		}

		interact () {
			const range = SPACING * 3;
			const pull  = 0.001;

			if (!press) {this.fixed = false; return;}
			if (!this.fixed) {	// first-time initialization
				if (Vector.dist(this.position, mouse) < range) { 
					this.fixed = true;
					this.offset = Vector.sub(this.position, mouse);
					this.velocity.scale(0);
					this.force.scale(0);
				}
			}
			if (!this.fixed) return;

			var target = Vector.add(mouse, this.offset);
			var diff   = Vector.sub(target, this.position);
			diff.scale(pull);
			this.force.add(diff.scale(SPEED));
			this.velocity.scale(0.99);
		}

		vat (target) {
			// radius vector
			var r = Vector.sub(target, this.position);
			// tangent velocity
			var vt = new Vector(-r.y, r.x).scale(this.w);
			// linear + angular
			return Vector.add(this.velocity, vt);
		}

		tat (target, f) {
			var arm = Vector.sub(target, this.position);
			var torque = f.y * arm.x - f.x * arm.y;
			return torque;
		}
	}

	class Constraint {
		constructor (a, b) {
			this.a = a;
			this.b = b;
			this.dir = Vector.sub(b.position, a.position).scale(0.5);
		}

		resolve () {
			let {a, b, dir} = this;

			var dirA = Vector.rot(dir, a.angle);
			var dirB = Vector.rot(Vector.scale(dir, -1), b.angle);

			var refA = Vector.add(a.position, dirA);
			var refB = Vector.add(b.position, dirB);

			var diff = Vector.sub(refB, refA);
			var mid  = Vector.add(refA, Vector.scale(diff, 0.5));

			var velA = a.vat(mid);
			var velB = b.vat(mid);
			var velAB = Vector.sub(velB, velA);

			// linear spring (diff)
			// linear drag (velAB)
			const fk = 0.04;
			const tk = 0.02;
			var f = Vector.add(diff, velAB).scale(fk);
			var t = Vector.add(diff, velAB).scale(tk);
			a.force.add(f);
			b.force.sub(f);
			a.torque += a.tat(mid, t);
			b.torque -= b.tat(mid, t);

			// angular spring
			const k = 1;
			var diff = b.angle - a.angle;
			diff = clamp(diff, -Math.PI, Math.PI);
			a.torque += diff * k;
			b.torque -= diff * k;
		}
	}

	var particles = [];
	var constraints = [];

	class Square {
		constructor (width, height) {
			const xbase = (canvas.width  - width  * SPACING) >> 1;
			const ybase = (canvas.height - height * SPACING) >> 1;

			particles = [];
			for (let i=0; i<height; ++i)
				for (let j=0; j<width; ++j) {
					var index = i * width + j;
					var x = xbase + i * SPACING;
					var y = ybase + j * SPACING;
					var w = -0.5 + Math.random();
					particles.push( new Particle(x, y, w) );
				}

			constraints = [];
			for (let i=0; i<height; ++i)
				for (let j=0; j<width; ++j) {
					var index = i * width + j;

					if (j > 0) {
						constraints.push( new Constraint(
							particles[index - 1],
							particles[index]
						));
					}

					if (i > 0) {
						constraints.push( new Constraint(
							particles[index - width],
							particles[index]
					));
				}
			}
		}

		draw(ctx) {
			ctx.lineWidth   = 2;
			ctx.strokeStyle = 'darkgreen';
			ctx.beginPath();
			for (let c of constraints) {
				ctx.moveTo(c.a.position.x, c.a.position.y);
				ctx.lineTo(c.b.position.x, c.b.position.y);
			}
			ctx.stroke();
		}
	}

	var square = new Square(8, 8);
	update();

	function update() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);

		for (let i = ITERATIONS; i--;) {
			for (let constraint of constraints) {
				constraint.resolve();
			}

			for (let particle of particles) {
				particle.interact();
				particle.wall();
				particle.update();
			}
		}

		square.draw(ctx);
	};

	var press = false;
	canvas.onmousedown = function() {press = true;};
	canvas.onmouseup = function() {press = false;};
	canvas.onmouseenter = function(e) {press = (e.buttons == 1);}

	var mouse = new Vector(0, 0);
	canvas.onmousemove = function(e) {
		mouse.x = e.offsetX;
		mouse.y = e.offsetY;
	}
})();
</script>
</div><div class="canvas" name="FastLSM">
<canvas id="FastLSM" width="200" height="200"></canvas>
<script id="FastLSM.js">
(function() {
	var canvas = document.getElementById('FastLSM');
	var ctx = canvas.getContext('2d');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	const ITERATIONS = 14;
	const RANGE      = 50;		// r
	const SPACING    = 14;		// h
	const GRAVITY    = 0.05;	// g
	const SPEED      = 1;		// dt

	class Vector {
		constructor (x = 0, y = 0) {
			this.x = x;
			this.y = y;
		}

		copy () {
			return new Vector(this.x, this.y);
		}

		add (v) {
			this.x += v.x;
			this.y += v.y;
			return this;
		}

		sub (v) {
			this.x -= v.x;
			this.y -= v.y;
			return this;
		}

		scale (k) {
			this.x *= k;
			this.y *= k;
			return this;
		}

		transform(m) {
			var x = m[0] * this.x + m[1] * this.y;
			var y = m[2] * this.x + m[3] * this.y;
			this.x = x;
			this.y = y;
			return this;
		}

		static add (v1, v2) {
			return new Vector(v1.x + v2.x, v1.y + v2.y);
		}

		static sub (v1, v2) {
			return new Vector(v1.x - v2.x, v1.y - v2.y);
		}

		static scale (v, k) {
			return new Vector(v.x * k, v.y * k);
		}

		static dist (v1, v2) {
			return Math.hypot(v1.x - v2.x, v1.y - v2.y);
		}

		static dot (v1, v2) {
			return v1.x * v2.x + v1.y * v2.y;
		}
	}

	var points = [
		[4,5  ],[16,4 ],[9,8  ],[3,16 ],[8,14 ],
		[17,11],[23,9 ],[8,19 ],[16,16],[23,14],
		[5,24 ],[13,25],[19,22],[26,21],[29,16],
		[13,30],[22,31],[28,27],[32,23],[38,19],
		[30,32],[37,28]
	];

	scale(points, 5, -5);
	translate(points, 0, 200);
	points = array2vector(points);

	function scale(p, s, t) {
		for (var i=0; i<p.length; ++i) {
			p[i][0] *= s;
			p[i][1] *= t;
		}
	}
	
	function translate(p, x, y) {
		for (var i=0; i<p.length; ++i) {
			p[i][0] += x;
			p[i][1] += y;
		}
	}

	function array2vector(arrays) {
		var vectors = [];
		for (let a of arrays)
			vectors.push( new Vector(a[0], a[1]) );
		return vectors;
	}

	var edges = [
		[0,2],[1,2],[2,5],[5,6],[8,9],
		[9,6],[4,8],[2,4],[3,4],[3,7],
		[3,10],[0,3],[10,15],[7,11],[11,12],
		[12,13],[13,14],[6,14],[8,12],[9,13],
		[12,16],[14,18],[14,19],[11,15],[15,16],
		[16,17],[17,18],[18,19],[19,21],[20,21],
		[16,20],[17,20],[18,21],[1,6],[0,1]
	];

	class Matrix {
		static basis(v0, v1) {
			return [v0[0], v1[0], v0[1], v1[1]];
		}

		static transpose(m) {
			return [m[0], m[2], m[1], m[3]];
		}

		static det(m) {
			return m[0]*m[3] - m[1]*m[2];
		}

		static inv(m) {
			var d = Matrix.det(m);
			return [m[3]/d, -m[1]/d, -m[2]/d, m[0]/d];
		}

		static mul(a, b) {
			return [a[0]*b[0]+a[1]*b[2], a[0]*b[1]+a[1]*b[3],
					a[2]*b[0]+a[3]*b[2], a[2]*b[1]+a[3]*b[3]];
		}

		static polar(m) {
			var sign = Math.sign(Matrix.det(m));
			var r = new Array(4);
			r[0] = m[0] + m[3] * sign;
			r[1] = m[1] - m[2] * sign;
			r[2] = m[2] - m[1] * sign;
			r[3] = m[3] + m[0] * sign;
			var l = Math.hypot(r[0],r[2]);
			r[0] /= l; r[1] /= l;
			r[2] /= l; r[3] /= l;
			if (Matrix.det(r) < 0) r[0] = -r[0], r[2] = -r[2];
			var s = Matrix.mul(Matrix.transpose(r), m);
			return [r, s];
		}
	}

	class Particle {
		constructor (x, y) {
			this.position = new Vector(x, y);
			this.velocity = new Vector();
			this.force    = new Vector();
		}

		update () {
			this.velocity.add(Vector.scale(this.force, SPEED));
			this.position.add(Vector.scale(this.velocity, SPEED));
			this.force = new Vector(0, GRAVITY / ITERATIONS);
		}

		wall () {
			const padding = 5;
			const damping = 0.6;

			var x = padding - this.position.x;
			if (x > 0) {
				this.force.add(new Vector(x, 0).scale(0.1));
				this.velocity.y *= damping;
			}

			var x = canvas.width - padding - this.position.x;
			if (x < 0) {
				this.force.add(new Vector(x, 0));
				this.velocity.y *= damping;
			}

			var y = padding - this.position.y
			if (y > 0) {
				this.force.add(new Vector(0, y));
				this.velocity.x *= damping;
			}

			var y = canvas.height - padding - this.position.y;
			if (y < 0) {
				this.force.add(new Vector(0, y));
				this.velocity.x *= damping;
			}
		}

		interact () {
			const range = SPACING * 3;
			const pull  = 0.001;

			if (!press) {this.fixed = false; return;}
			if (!this.fixed) {	// first-time initialization
				if (Vector.dist(this.position, mouse) < range) { 
					this.fixed = true;
					this.offset = Vector.sub(this.position, mouse);
					this.velocity.scale(0);
					this.force.scale(0);
				}
			}
			if (!this.fixed) return;

			var target = Vector.add(mouse, this.offset);
			var diff   = Vector.sub(target, this.position);
			diff.scale(pull);
			this.force.add(diff.scale(SPEED));
			this.velocity.scale(0.99);
		}
	}

	var particles     = [];
	var adj           = [];
	var new_positions = [];

	class PointCloud {
		constructor (points, edges) {
			particles = new Array(points.length);
			for (let i = 0; i < points.length; i++)
				particles[i] = new Particle(points[i].x, points[i].y);

			adj = new Array(points.length);
			for (let i = 0; i < points.length; i++) {
				adj[i] = [];
				for (let j = 0; j < points.length; j++)
					if (Vector.dist(points[i], points[j]) <= RANGE)
						adj[i].push(j);
			}

			new_positions = new Array(points.length);
			for (let i = 0; i < points.length; i++)
				new_positions[i] = new Vector(0, 0);
		}

		resolve() {
			for (let i = 0; i < points.length; i++) {
				new_positions[i].x = 0;
				new_positions[i].y = 0;
			}

			for (let i = 0; i < points.length; i++)
				this.resolve_particle(i);

			for (let i = 0; i < points.length; i++)
				new_positions[i].scale(1 / adj[i].length);

			// new velocity by Verlet method
			const ks = 0.02;
			const kd = 0.01;
			for (let i = 0; i < points.length; i++) {
				// spring
				var diff = Vector.sub(new_positions[i], particles[i].position);
				var f = Vector.scale(diff, ks);
				particles[i].force.add(f);
				// damping
				var vd = Vector.dot(particles[i].velocity, diff);
				var dd = Vector.dot(diff, diff);
				var proj = (dd < 1e-6 ? 0 : vd/dd);
				var f = Vector.scale(diff, proj * kd);
				particles[i].force.sub(f);
			}
		}

		resolve_particle (i) {
			var neighbors = adj[i];

			// mean (center of mass)
			var m1 = new Vector(0, 0);
			var m2 = new Vector(0, 0);
			for (let j of neighbors) {
				m1.add(points[j]);
				m2.add(particles[j].position);
			}
			m1.scale(1/neighbors.length);
			m2.scale(1/neighbors.length);

			// covariance matrix
			var cxx = 0, cxy = 0, cyy = 0;
			for (let j of neighbors) {
				cxx += (points[j].x - m1.x) * (particles[j].position.x - m2.x);
				cxy += (points[j].x - m1.x) * (particles[j].position.y - m2.y);
				cyy += (points[j].y - m1.y) * (particles[j].position.y - m2.y);
			}

			// new position by optimal rigid transformation
			var cov = [cxx, cxy, cxy, cyy];
			var [r, s] = Matrix.polar(cov);
			for (let j of neighbors) {
				var p = points[i].copy().sub(m1).transform(r).add(m2);
				new_positions[i].add(p);
			}
		}

		draw(ctx) {
			ctx.lineWidth   = 2;
			ctx.strokeStyle = 'darkgreen';
			ctx.beginPath();
			for (let i = 0; i < points.length; i++)
				for (let j of adj[i])
					if (i < j) {
						var p = particles[i].position;
						var q = particles[j].position;
						ctx.moveTo(p.x, p.y);
						ctx.lineTo(q.x, q.y);
					}
			ctx.stroke();
/*
			ctx.font = "12pt Verdana";
			ctx.textBaseline = "middle";
			ctx.textAlign = "center";
			for (let i = 0; i < points.length; i++) {
				var p = particles[i].position;
				ctx.fillStyle = (particles[i].fixed ? "gold" : "gray");
				ctx.fillText(`${i}`, p.x, p.y);
			}
*/
		}
	}

	var pointcloud = new PointCloud(points, edges);

	function reset() {
		pointcloud = new PointCloud(points, edges);
	}

	update();
	function update() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		title();

		for (let i = ITERATIONS; i--;) {
			pointcloud.resolve();

			for (let particle of particles) {
				particle.interact();
				particle.wall();
				particle.update();
			}
		}

		pointcloud.draw(ctx);
	};

	var press = false;
	canvas.onmousedown = function() {press = true;};
	canvas.onmouseup = function() {press = false;};
	canvas.onmouseenter = function(e) {press = (e.buttons == 1);}

	var mouse = new Vector(0, 0);
	canvas.onmousemove = function(e) {
		mouse.x = e.offsetX;
		mouse.y = e.offsetY;
	}

	canvas.onkeydown = function(e) {
		if (e.keyCode == 90) reset();	// z
	}

	function title() {
		ctx.font = "12pt Verdana";
		ctx.textBaseline = "top";
		ctx.textAlign = "center";
		ctx.fillStyle = "gray";
		ctx.fillText("[z] reset", canvas.width/2, 0);
	}
})();
</script>
</div><div class="canvas" name="MPM">
<canvas id="MPM" width="200" height="200"></canvas>
<script id="MPM.js">
// https://github.com/Elias-gu/MPM2D
(function() {
	var canvas = document.getElementById('MPM');
	var ctx = canvas.getContext('2d');

	var id = 0;
	canvas.tabIndex = 0;
	canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
	canvas.onfocus = function(){if (!id) id = requestAnimationFrame(frame, canvas);};
	function frame() {update(); id = requestAnimationFrame(frame, canvas);}

	// Vector [v0, v1]
	function assto(v, w) {v[0] = w[0]; v[1] = w[1];}
	function addto(v, w) {v[0] += w[0]; v[1] += w[1];}
	function subto(v, w) {v[0] -= w[0]; v[1] -= w[1];}
	function multo(v, k) {v[0] *= k; v[1] *= k;}
	function add(v, w) {return [v[0] + w[0], v[1] + w[1]];}
	function sub(v, w) {return [v[0] - w[0], v[1] - w[1]];}
	function mul(v, k) {return [v[0] * k, v[1] * k];}
	function addmul(v, w, k) {return [v[0] + w[0] * k, v[1] + w[1] * k];}
	function submul(v, w, k) {return [v[0] - w[0] * k, v[1] - w[1] * k];}
	function dot(v, w) {return v[0] * w[0] + v[1] * w[1];}
	function norm(v) {return Math.hypot(v[0], v[1]);}
	function dist(v, w) {return Math.hypot(v[0] - w[0], v[1] - w[1]);}

	// Matrix [m0  m1]
	//        [m2  m3]
	function assmmto(a, b) {a[0] = b[0]; a[1] = b[1]; a[2] = b[2]; a[3] = b[3];}
	function addmmto(a, b) {a[0] += b[0]; a[1] += b[1]; a[2] += b[2]; a[3] += b[3];}
	function addmm(a, b) {return [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]];}
	function submm(a, b) {return [a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]];}
	function mulms(m, k) {return [m[0] * k, m[1] * k, m[2] * k, m[3] * k];}
	function mulmv(m, v) {return [m[0]*v[0]+m[1]*v[1], m[2]*v[0]+m[3]*v[1]];}
	function mulmm(a, b) {return [a[0]*b[0]+a[1]*b[2], a[0]*b[1]+a[1]*b[3], a[2]*b[0]+a[3]*b[2], a[2]*b[1]+a[3]*b[3]];}
	function basis(v, w) {return [v[0], w[0], v[1], w[1]];}
	function outer(v, w) {return [v[0] * w[0], v[0] * w[1], v[1] * w[0], v[1] * w[1]];}
	function transpose(m) {return [m[0], m[2], m[1], m[3]];}
	function det(m) {return m[0]*m[3] - m[1]*m[2];}
	function inv(m) {var d = det(m); return [m[3]/d, -m[1]/d, -m[2]/d, m[0]/d];}
	function polar(m) {
		var t = Math.atan2(m[2] - m[1], m[0] + m[3]);
		var c = Math.cos(t), s = Math.sin(t);
		var R = [c, -s, s, c];
		var S = mulmm(transpose(R), m);
		return [R, S];
	}
	function polar(m) {
		var sign = Math.sign(det(m));
		var r = new Array(4);
		r[0] = m[0] + m[3] * sign;
		r[1] = m[1] - m[2] * sign;
		r[2] = m[2] - m[1] * sign;
		r[3] = m[3] + m[0] * sign;
		var l = Math.hypot(r[0],r[2]);
		r[0] /= l; r[1] /= l;
		r[2] /= l; r[3] /= l;
		if (det(r) < 0) r[0] = -r[0], r[2] = -r[2];
		var s = mulmm(transpose(r), m);
		return [r, s];
	}

	const N  = 20;		// grid number (per dimension)
	const K  = 2;		// particle number per grid (per dimension)
	const h  = 1;		// grid width Δx = Δy = h (2D)
	const dV = h * h;	// volume Δx * Δy (2D)
	const dt = 1;		// time step Δt
	const gr = 0.01;	// gravity
	const μₖ = 0.1;		// kinetic friction
	const E  = 1;		// Young's modulus
	const ν  = 0.1;		// Poisson's ratio
	const λ  = E * ν / (1 + ν) / (1 - 2 * ν);	// Lamé's parameters
	const μ  = E / (1 + ν) / 2;					// Lamé's parameters

	// quadratic B-spline
	function B(x) {
		x /= h;
		// U+01C0 Latin Letter Dental Click
		var ǀxǀ = Math.abs(x);
		if (ǀxǀ < 0.5) return -ǀxǀ * ǀxǀ + 3 / 4;
		if (ǀxǀ < 1.5) return ǀxǀ * ǀxǀ / 2 - 3 * ǀxǀ / 2 + 9 / 8;
		else return 0;
	}
	function dB(x) {
		x /= h;
		var ǀxǀ = Math.abs(x);
		if (ǀxǀ < 0.5) return -2 * x;
		if (ǀxǀ < 1.5) return x - 3 / 2 * x / ǀxǀ;
		return 0;
	}
	// cubic B-spline
	function B(x) {
		x /= h;
		// U+01C0 Latin Letter Dental Click
		var ǀxǀ = Math.abs(x);
		if (ǀxǀ < 1) return ǀxǀ*ǀxǀ*ǀxǀ/2 - ǀxǀ*ǀxǀ + 2/3;
		if (ǀxǀ < 2) return (2-ǀxǀ)*(2-ǀxǀ)*(2-ǀxǀ) / 6;
		return 0;
	}
	function dB(x) {
		x /= h;
		var ǀxǀ = Math.abs(x);
		if (ǀxǀ < 1) return 1.5 * x * ǀxǀ - 2 * x;
		if (ǀxǀ < 2) return -x * ǀxǀ / 2 + 2 * x - 2 * x / ǀxǀ;
		return 0;
	}
	function getW(v) {return B(v[0]) * B(v[1]);}
	function getdW(v) {return [dB(v[0]) * B(v[1]), B(v[0]) * dB(v[1])];}

	class Grid {
		constructor(x, y) {
			this.Mi     = 0;		// mass
			this.Xi     = [x, y];	// coordinates (lowercase x)
			this.Vi     = [0, 0];	// velocity/momentum
			this.Ai     = [0, 0];	// acceleration/force
			this.Vi_res = [0, 0];	// velocity with resitutition
			this.Vi_fri = [0, 0];	// velocity with friction
		}
	}

	function init_grids() {
		var grids = [];
		for (let i=-1; i<=(N-1)+2; i++)
			for (let j=-1; j<=(N-1)+2; j++)
				grids.push(new Grid(j*h, i*h));
		return grids;
	}

	class Particle {
		constructor(Mp, Xp, Vp) {
			this.Mp = Mp;			// mass
			this.Xp = Xp;			// position (uppercase X)
			this.Xp_new = [0,0];	// temporary variable 
			this.Vp = Vp;			// velocity
			// U+1401 Canadian Syllabics E
			this.ᐁVp = [0,0,0,0];	// velocity gradient
			this.εp = [0,0,0,0];	// strain
			this.σp = [0,0,0,0];	// stress
			this.Fp = [1,0,0,1];	// deformation gradient
			this.σ̅p = [0,0,0,0];	// force-based stress
			this.Bp = [0,0,0,0];	// APIC
			this.Dp = [1,0,0,1];	// APIC
		}
	}

	function init_particles() {
		const PAD = (N/4) | 0;	// square padding
		const ρ = 1;			// density of material
		var particles = [];
		for (let i=PAD*K; i<(N-PAD)*K; i++)
			for (let j=PAD*K; j<(N-PAD)*K; j++) {
				var Mp = ρ * dV;
				var Xp = [(j + 0.5) * h/K, (i + 0.5) * h/K];
				var Vp = [0, 0];
				particles.push( new Particle(Mp, Xp, Vp) );
			}
		return particles;
	}

	class Wall {
		constructor(point1, point2, normal) {
			this.point1 = point1;
			this.point2 = point2;
			this.normal = normal;
		}

		Restitution(grid) {
			let {normal, point1} = this;
			// distance between grid and wall
			var distance = dot(normal, sub(grid.Xi, point1));
			var trial_position = addmul(grid.Xi, grid.Vi, dt);
			var trial_distance = dot(normal, sub(trial_position, point1));
			var d = trial_distance - Math.min(distance, 0.0);
			if (d >= 0) return;
			var vr = mul(normal, d / dt);
			subto(grid.Vi_res, vr);
		}

		Friction(grid) {
			let {normal} = this;
			// apply Coulomb's friction to tangential velocity
			var Vn = mul(normal, dot(normal, grid.Vi_res));
			var Vt = sub(grid.Vi_res, Vn);
			var length = norm(Vt);
			if (length < 1e-7) return;
			var coeff = Math.min(length, μₖ * dist(grid.Vi_res, grid.Vi));
			var Vf = mul(Vt, 1 / length * coeff);
			subto(grid.Vi_fri, Vf);
		}
	}

	function init_walls() {
		var w = N * h;
		var l = new Wall([0, 0], [0, w], [1, 0]);
		var d = new Wall([0, 0], [w, 0], [0, 1]);
		var r = new Wall([w, 0], [w, w], [-1, 0]);
		var u = new Wall([0, w], [w, w], [0, -1]);
		return [l, d, r, u];
	}

	var grids     = init_grids();
	var particles = init_particles();
	var walls     = init_walls();

	function reset() {
		particles = init_particles();
	}

	function particle_to_grid() {
		for (let g of grids)
			if (g.Mi > 0) {
				g.Mi = 0;
				g.Vi = [0, 0];
				g.Ai = [0, 0];
			}

		for (let p of particles) {
			var grid_base = (N+3) * Math.floor(p.Xp[1]/h+1) + Math.floor(p.Xp[0]/h+1);

			// Loop over all the close grids
			for (let i=-1; i<=2; i++) for (let j=-1; j<=2; j++) {
				var grid_id = grid_base + (N+3) * i + j;
				if (!(grid_id >= 0 && grid_id < grids.length)) continue;
				var g = grids[grid_id];

				var diff = sub(g.Xi, p.Xp);
				var Wip  = getW(diff);
				var dWip = getdW(diff);

				// mass
				g.Mi += p.Mp * Wip;
				// momentum
				addto(g.Vi, mul(p.Vp, p.Mp * Wip));
				// force (linear elasticity)
				addto(g.Ai, mulms(mulmv(p.σp, dWip), dV));
				// force (hyperelasticity)
//				addto(g.Ai, mulms(mulmv(p.σ̅p, dWip), dV));
				// momentum (APIC)
//				addto(g.Vi, mul(mulmv(p.Bp, mulmv(inv(p.Dp), diff)), p.Mp * Wip));
			}
		}

		for (let g of grids)
			if (g.Mi > 0) {
				// velocity
				multo(g.Vi, 1/g.Mi);
				// acceleration
				multo(g.Ai, -1/g.Mi);
				// gravity
				g.Ai[1] += gr;
				// velocity update
				addto(g.Vi, mul(g.Ai, dt));
			}

		// wall collision
		for (let g of grids)
			if (g.Mi > 0) {
				assto(g.Vi_res, g.Vi);
				for (let w of walls) w.Restitution(g);
				assto(g.Vi_fri, g.Vi_res);
				for (let w of walls) w.Friction(g);
			}
	}

	function grid_to_particle() {
		for (let p of particles) {
			p.Xp_new = [0,0];		// position
			p.Vp     = [0,0];		// velocity
			p.ᐁVp    = [0,0,0,0];	// velocity gradient
			p.Bp     = [0,0,0,0];	// APIC
			p.Dp     = [0,0,0,0];	// APIC
		}

		for (let p of particles) {
			var grid_base = (N+3) * Math.floor(p.Xp[1]/h+1) + Math.floor(p.Xp[0]/h+1);

			// Loop over all the close grids
			for (let i=-1; i<=2; i++) for (let j=-1; j<=2; j++) {
				var grid_id = grid_base + (N+3) * i + j;
				if (!(grid_id >= 0 && grid_id < grids.length)) continue;
				var g = grids[grid_id];

				var diff = sub(g.Xi, p.Xp);
				var Wip  = getW(diff);
				var dWip = getdW(diff);

				// position
				var Xi_new = addmul(g.Xi, g.Vi_fri, dt);
				addto(p.Xp_new, mul(Xi_new, Wip));
				// velocity
				addto(p.Vp, mul(g.Vi_fri, Wip));
				// velocity gradient
				addmmto(p.ᐁVp, outer(g.Vi_fri, dWip));
				// APIC
				addmmto(p.Bp, mulms(outer(g.Vi_fri, diff), Wip));
				addmmto(p.Dp, mulms(outer(diff, diff), Wip));
			}
		}

		for (let p of particles) {
			// position
			assto(p.Xp, p.Xp_new);
			// strain (linear elasticity)
			addmmto(p.εp, mulms(addmm(p.ᐁVp, transpose(p.ᐁVp)), dt/2));
			// stress (linear elasticity)
			stress(p.σp, p.εp, λ, μ);
			// deformation gradient (hyperelasticity)
			addmmto(p.Fp, mulms(p.ᐁVp, dt));
			// deformation gradient [Stomakhin et al. 2013]
//			addmmto(p.Fp, mulmm(mulms(p.ᐁVp, dt), p.Fp));
			// force-based stress (hyperelasticity)
			var Jp = det(p.Fp);
			var [Rp, Sp] = polar(p.Fp);
			var line = mulms(mulmm(submm(p.Fp, Rp), transpose(p.Fp)), 2 * μ);
			var bulk = mulms([1,0,0,1], λ * (Jp - 1) * Jp);
			p.σ̅p  = addmm(line, bulk);
		}
	}

	function stress(σ, ε, λ, μ) {
		// σ_xx = 2μ ε_xx + λ(ε_xx + ε_yy + ε_zz)
		σ[0] = 2 * μ * ε[0] + λ * (ε[0] + ε[3]);
		σ[3] = 2 * μ * ε[3] + λ * (ε[0] + ε[3]);
		// σ_xy = μ ε_xy
		σ[1] = 2 * μ * ε[1];
		σ[2] = 2 * μ * ε[2];
	}

	const gwidth      = (canvas.width / N) | 0;	// grid width
	const pwidth      = (gwidth / K) | 0;		// particle width
	const half_pwidth = (pwidth / 2) | 0;
	function draw(ctx) {
		for (let p of particles) {
			ctx.fillStyle = "darkgreen";
			var x = p.Xp[0] * gwidth - half_pwidth;
			var y = p.Xp[1] * gwidth - half_pwidth;
			ctx.fillRect(x, y, pwidth, pwidth);
		}
	}

	var press = false;
	canvas.onmousedown = function() {press = true;};
	canvas.onmouseup = function() {press = false;};
//	canvas.onmouseenter = function(e) {press = (e.buttons == 1);}
	canvas.onmouseout = function(e) {press = false;}

	var mouse = [0, 0];
	canvas.onmousemove = function(e) {
		mouse[0] = e.offsetX;
		mouse[1] = e.offsetY;
	}

	const range = 2 * h;
	const pull = 0.1;
	function interact() {
		for (let p of particles) {
			if (!press) {p.fixed = false; continue;}
			if (!p.fixed) {
				var dx = mouse[0] - p.Xp[0] * gwidth;
				var dy = mouse[1] - p.Xp[1] * gwidth;
				var l = Math.sqrt(dx*dx + dy*dy);
				if (l > range) continue;
				p.fixed = true;
				p.offset = [dx, dy];
				p.Vp = [0, 0];
			}
			if (!p.fixed) continue;

			var target = add(mouse, p.offset);
			var diff   = sub(target, mul(p.Xp, gwidth));
			assto(p.Vp, mul(diff, dt * pull));
		}
	}

	canvas.onkeydown = function(e) {
		if (e.keyCode == 90) reset();	// z
	}

	function title() {
		ctx.font = "12pt Verdana";
		ctx.textBaseline = "top";
		ctx.textAlign = "center";
		ctx.fillStyle = "gray";
		ctx.fillText("[z] reset", canvas.width/2, 0);
	}

	draw(ctx);
	function update() {
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		particle_to_grid();
		grid_to_particle();
		interact();
		draw(ctx);
		title();
	};
})();
</script>
</div>