<div class="canvas" name="ShapeRendering">
<canvas id="ShapeRendering" width="500" height="300"></canvas>
<script id="ShapeRendering.js">
(function(){
	var canvas = document.getElementById('ShapeRendering');
	var ctx = canvas.getContext('2d');
	var imageData;

	// stroke style
	ctx.lineWidth = 2;
	ctx.lineJoin = 'round';
	ctx.lineCap = 'round';

	// paint()
	var tools = {
		hand: function() {
			ctx.strokeStyle = 'gray';
			ctx.lineTo(mouse.x, mouse.y);
			ctx.stroke();
		},

		line: function() {
			ctx.strokeStyle = 'gold';
			ctx.putImageData(imageData, 0, 0);
			ctx.beginPath();
			ctx.moveTo(mouse_down.x, mouse_down.y);
			ctx.lineTo(mouse.x, mouse.y);
			ctx.stroke();
			ctx.closePath();
		},

		ractangle: function() {
			ctx.strokeStyle = 'lightblue';
			ctx.putImageData(imageData, 0, 0);
			var x = Math.min(mouse.x, mouse_down.x);
			var y = Math.min(mouse.y, mouse_down.y);
			var width = Math.abs(mouse.x - mouse_down.x);
			var height = Math.abs(mouse.y - mouse_down.y);
			ctx.strokeRect(x, y, width, height);
		},

		cycle: function() {
			ctx.strokeStyle = 'lightgreen';
			ctx.putImageData(imageData, 0, 0);
			var x = (mouse.x + mouse_down.x) / 2;
			var y = (mouse.y + mouse_down.y) / 2;
			var radius = Math.max(
				Math.abs(mouse.x - mouse_down.x),
				Math.abs(mouse.y - mouse_down.y)
			) / 2;
			ctx.beginPath();
			ctx.arc(x, y, radius, 0, Math.PI*2, false);
			ctx.stroke();
			ctx.closePath();
		}
	};

	// mouse control
	var tool = tools.hand;
	var mouse = {x: 0, y: 0};
	var mouse_down = {x: 0, y: 0};
	canvas.addEventListener('mousemove', function(e) {
		mouse.x = e.offsetX;
		mouse.y = e.offsetY;
	});
	canvas.onmousedown = function(e) {
		imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
		mouse_down.x = mouse.x;
		mouse_down.y = mouse.y;
		ctx.beginPath();
		ctx.moveTo(mouse.x, mouse.y);
		canvas.onmousemove = tool;
	};
	canvas.onmouseup = function() {
		canvas.onmousemove = function(e){return false;};
	};

	// keyboard control
	canvas.tabIndex = 0;
	canvas.onmouseover = canvas.focus;
	canvas.onmouseout = canvas.blur;
	canvas.onkeydown = function(e) {
		if (e.keyCode == 72) tool = tools.hand;
		if (e.keyCode == 76) tool = tools.line;
		if (e.keyCode == 82) tool = tools.ractangle;
		if (e.keyCode == 67) tool = tools.cycle;
		if (e.keyCode == 88) {
			ctx.clearRect(0, 0, canvas.width, canvas.height);
			ctx.font = "12pt Verdana";
			ctx.textBaseline = "top";
			ctx.textAlign = "center";
			ctx.fillStyle = "gray";
			ctx.fillText("[h] hand [l] line [r] rectangle [c] cycle [x] clear", canvas.width/2, 0);
		}
	};

	// key 'x' for initialization
	var e = new KeyboardEvent("keydown", {
		bubbles : true,
		cancelable : true,
		char : " ",
		key : " ",
		shiftKey : false,
		keyCode : 88
	});
	canvas.dispatchEvent(e);
})();
</script>
</div><div class="canvas" name="FontRendering1">
<canvas id="FontRendering1" width="300" height="300"></canvas>
<script id="FontRendering1.js">
(function(){
	var canvas    = document.getElementById('FontRendering1');
	var ctx       = canvas.getContext('2d');
	var data      = document.getElementById('uni76BF_SourceHanSans-Normal.eps').innerHTML;
	var line      = data.split(/\r?\n/);

	var c = -1;
	canvas.onclick = function(){ while(1){
		c = (c + 1) % line.length;

		if (c == 0) {
			ctx.setTransform(1, 0, 0, 1, 0, 0);
			ctx.clearRect(0, 0, canvas.width, canvas.height);
			var ox = 0, oy = 35, scale = 0.3;
			ctx.translate(ox, canvas.height - oy);
			ctx.scale(scale, -scale);
			ctx.lineWidth = 15;
			ctx.strokeStyle = "rgb(192,0,0)";
		}

		var s = line[c].replace(/\s+/gm, ' ').replace(/^\s+|\s+$/g, '').split(/\s/);
		if (s[0][0] == '%') continue;

		for (var i=0; i<s.length; ++i) {
			var n = parseInt(s[i], 10);
			if ( !isNaN(n) ) s[i] = n;
		}

		if        (s.length == 3 && s[s.length - 1] == "moveto") {
			ctx.beginPath();
			ctx.moveTo(s[0], s[1]);
			continue;
		} else if (s.length == 3 && s[s.length - 1] == "lineto") {
			ctx.lineTo(s[0], s[1]);
			ctx.stroke();
		} else if (s.length == 7 && s[s.length - 1] == "curveto") {
			ctx.bezierCurveTo(s[0], s[1], s[2], s[3], s[4], s[5]);
			ctx.stroke();
		} else
			continue;
	break;}}

	for (var i=0; i<line.length - 18; ++i) canvas.click();
	ctx.setTransform(1, 0, 0, 1, 0, 0);
	ctx.font = "32pt Arial";
	ctx.textAlign = "center";
	ctx.textBaseline = "middle";
	ctx.fillStyle = "green";
	ctx.fillText("click me", canvas.width/2, canvas.height/2);
})();
</script>
<div id="uni76BF_SourceHanSans-Normal.eps" style="display:none;">
gsave newpath
	509 711 moveto
	 203 711 lineto
	 167 657 127 607 87 569 curveto
	 78 583 58 611 45 624 curveto
	 106 677 166 756 201 839 curveto
	 262 821 lineto
	 254 801 244 783 233 763 curveto
	 509 763 lineto
	 509 711 lineto
	closepath
	389 633 moveto
	 332 611 lineto
	 324 633 306 664 288 687 curveto
	 345 706 lineto
	 361 684 381 653 389 633 curveto
	closepath
	769 503 moveto
	 769 564 lineto
	 247 564 lineto
	 247 503 lineto
	 769 503 lineto
	closepath
	769 397 moveto
	 769 458 lineto
	 247 458 lineto
	 247 397 lineto
	 769 397 lineto
	closepath
	769 288 moveto
	 769 352 lineto
	 247 352 lineto
	 247 288 lineto
	 769 288 lineto
	closepath
	646 150 moveto
	 646 242 lineto
	 385 242 lineto
	 385 176 lineto
	 385 168 385 159 384 150 curveto
	 646 150 lineto
	closepath
	952 765 moveto
	 952 712 lineto
	 776 712 lineto
	 800 689 826 658 839 636 curveto
	 785 610 lineto
	 774 633 748 666 725 690 curveto
	 773 712 lineto
	 624 712 lineto
	 599 673 570 637 539 610 curveto
	 837 610 lineto
	 837 242 lineto
	 713 242 lineto
	 713 150 lineto
	 944 150 lineto
	 944 94 lineto
	 713 94 lineto
	 713 -74 lineto
	 646 -74 lineto
	 646 94 lineto
	 371 94 lineto
	 348 31 287 -32 144 -81 curveto
	 135 -67 116 -44 102 -32 curveto
	 218 3 273 49 298 94 curveto
	 59 94 lineto
	 59 150 lineto
	 316 150 lineto
	 318 160 318 169 318 177 curveto
	 318 242 lineto
	 181 242 lineto
	 181 610 lineto
	 537 610 lineto
	 524 620 496 636 481 643 curveto
	 540 691 592 763 620 839 curveto
	 682 825 lineto
	 675 804 666 785 656 765 curveto
	 952 765 lineto
	closepath
fill grestore
</div>
</div><div class="canvas" name="FontRendering2">
<canvas id="FontRendering2" width="300" height="300"></canvas>
<script id="FontRendering2.js">
(function(){
	var canvas    = document.getElementById('FontRendering2');
	var ctx       = canvas.getContext('2d');
	var data      = document.getElementById('uni26E9_SourceHanSans-Normal.eps').innerHTML;
	var lines      = data.split(/\r?\n/);

	/* .eps parser */

	var outline   = new Array();
	var p         = new Array();
	for (var line of lines) {
		var s = line.replace(/\s+/gm, ' ').replace(/^\s+|\s+$/g, '').split(/\s/);
		if (s[0][0] == '%') continue;

		for (var i=0; i<s.length; ++i) {
			var n = parseInt(s[i], 10);
			if ( !isNaN(n) ) s[i] = n;
		}

		var n0, n1, n2, n3, n4;
		if        (s.length == 3 && s[s.length - 1] == "moveto") {
			n0 = n1 = p.push([s[0], s[1]]) - 1;
		} else if (s.length == 3 && s[s.length - 1] == "lineto") {
			n2 = p.push([s[0], s[1]]) - 1;
			outline.push(["line", n1, n2]);
			n1 = n2;
		} else if (s.length == 7 && s[s.length - 1] == "curveto") {
			n2 = p.push([s[0], s[1]]) - 1;
			n3 = p.push([s[2], s[3]]) - 1;
			n4 = p.push([s[4], s[5]]) - 1;
			outline.push(["curve", n1, n2, n3, n4]);
			n1 = n4;
		} else if (s.length == 1 && s[s.length - 1] == "closepath") {
			p.pop();
			var o = outline[outline.length - 1];
			o[o.length - 1] = n0;
		}
	}

	/* control points */

	var ox = 0, oy = 35, scale = 0.3;
	function coordinate(x, y) {
		y = canvas.height - y; y -= oy; x /= scale; y /= scale;
		return {x: Math.round(x), y: Math.round(y)};
	}
	function touch(x, y, mousex, mousey) {
		return x - 20 <= mousex && x + 20 >= mousex
			&& y - 20 <= mousey && y + 20 >= mousey;
	}

	var hit = -1;
	canvas.onmousedown = function(e){
		var mouse = coordinate(e.offsetX, e.offsetY);
		hit = -1;
		for (var i=0; i<p.length; ++i)
			if (touch(p[i][0], p[i][1], mouse.x, mouse.y))
				hit = i;
	};

	canvas.onmousemove = function(e){
		if (hit == -1) return;
		var mouse = coordinate(e.offsetX, e.offsetY);
		p[hit][0] = mouse.x;
		p[hit][1] = mouse.y;
		DrawOutline();
		DrawKnot();
	};

	canvas.onmouseup = function(e){
		hit = -1;
	};

	DrawOutline();
	DrawKnot();
	DrawTitle();

	function DrawTitle() {
		ctx.setTransform(1, 0, 0, 1, 0, 0);
		ctx.font = "32pt Arial";
		ctx.textAlign = "center";
		ctx.textBaseline = "middle";
		ctx.fillStyle = "green";
		ctx.fillText("drag point", canvas.width/2, canvas.height/2);
	}

	function DrawOutline() {
		ctx.setTransform(1, 0, 0, 1, 0, 0);
		ctx.clearRect(0, 0, canvas.width, canvas.height);
//		var ox = 0, oy = 25, scale = 0.2;
		ctx.translate(ox, canvas.height - oy);
		ctx.scale(scale, -scale);
		ctx.lineWidth = 15;
		ctx.strokeStyle = "rgb(192,0,0)";

		ctx.beginPath();
		for (var o of outline) {
			var p1 = p[o[1]], p2 = p[o[2]], p3 = p[o[3]], p4 = p[o[4]];
			if (o[0] == "line") {
				ctx.moveTo(p1[0], p1[1]);
				ctx.lineTo(p2[0], p2[1]);
			} else if (o[0] == "curve") {
				ctx.moveTo(p1[0], p1[1]);
				ctx.bezierCurveTo(p2[0], p2[1], p3[0], p3[1], p4[0], p4[1]);
			}
		}
		ctx.stroke();
	}

	function DrawKnot() {
		ctx.lineWidth = 5;
		for (var o of outline) {
			var p1 = p[o[1]], p2 = p[o[2]], p3 = p[o[3]], p4 = p[o[4]];
			if (o[0] == "line") {
				ctx.strokeStyle = "rgba(0,0,0,.7)";
				ctx.fillStyle = "rgba(0,0,128,.7)";
				ctx.beginPath();
				ctx.arc(p1[0], p1[1], 15, 0, 2 * Math.PI);
				ctx.fill();
				ctx.stroke();
			} else if (o[0] == "curve") {
				ctx.strokeStyle = "rgba(0,255,0,.7)";
				ctx.beginPath();
				ctx.moveTo(p1[0], p1[1]);
				ctx.lineTo(p2[0], p2[1]);
				ctx.moveTo(p3[0], p3[1]);
				ctx.lineTo(p4[0], p4[1]);
				ctx.stroke();

				ctx.strokeStyle = "rgba(0,0,0,.7)";
				ctx.fillStyle = "rgba(0,0,255,.7)";
				ctx.beginPath();
				ctx.arc(p1[0], p1[1], 15, 0, 2 * Math.PI);
				ctx.fill();
				ctx.stroke();

				ctx.strokeStyle = "rgba(0,0,0,.7)";
				ctx.fillStyle = "rgba(0,255,0,.7)";
				ctx.beginPath();
				ctx.arc(p2[0], p2[1], 15, 0, 2 * Math.PI);
				ctx.fill();
				ctx.stroke();
				ctx.beginPath();
				ctx.arc(p3[0], p3[1], 15, 0, 2 * Math.PI);
				ctx.fill();
				ctx.stroke();
			}
		}
	}
})();
</script>
<div id="uni26E9_SourceHanSans-Normal.eps" style="display:none;">
gsave newpath
	292 31 moveto
	 767 31 lineto
	 841 31 857 68 864 236 curveto
	 883 226 912 216 932 212 curveto
	 919 9 883 -37 762 -37 curveto
	 301 -37 lineto
	 161 -37 93 13 93 96 curveto
	 93 167 120 243 648 684 curveto
	 98 684 lineto
	 98 752 lineto
	 741 752 lineto
	 756 757 lineto
	 803 724 lineto
	 800 720 795 717 790 714 curveto
	 198 227 164 154 164 101 curveto
	 164 53 217 31 292 31 curveto
	closepath
fill grestore
</div>
</div><div class="canvas" name="MeshRendering">
<canvas id="MeshRendering" width="500" height="300"></canvas>
<script id="MeshRendering.js">
(function(){
var canvas = document.getElementById("MeshRendering");
var ctx = canvas.getContext("2d");
ctx.fillStyle = 'rgb(255,255,255)';
ctx.strokeStyle = 'rgb(0,0,0)';
ctx.lineWidth = 0.5;
ctx.globalAlpha = 0.6;

var id = 0;
canvas.tabIndex = 0;
canvas.onmouseover = canvas.focus;
canvas.onmouseout = canvas.blur;
canvas.onmousemove = onMouseMove;
canvas.onkeydown = onKeyDown;
canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
canvas.onfocus = function(){if (!id) id = requestAnimationFrame(update, canvas);};
function update() {id = requestAnimationFrame(update,canvas); draw();}

var button_opacity = true;
var button_shade = false;
var button_wire = true;
function onKeyDown(event) {
	if (event.keyCode == 79) {	// o
		button_opacity = !button_opacity;
		ctx.globalAlpha = (button_opacity ? 0.6 : 1.0);
	}
	if (event.keyCode == 83)	// s
		button_shade = !button_shade;
	if (event.keyCode == 87)	// w
		button_wire = !button_wire;
}

var angle_x = 0;
var angle_y = 0;
function onMouseMove(event) {
	angle_x = (event.offsetX/canvas.width-.5)*3;
	angle_y = (event.offsetY/canvas.height-.5)*3;
}

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

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

	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];

	var projected_points = new Array(model.points.length);
	var distances = new Array(model.points.length);

	for (var i=0; i<model.points.length; i++) {
		projected_points[i] = mul(ry, mul(rx, model.points[i]));
		translate(projected_points[i], [0,0,distance]);
		distances[i] = length2(projected_points[i]);
		project(projected_points[i], focal_length);
		translate(projected_points[i], screen_center);
	}

	var visible_faces = new Array(model.faces.length);

	for (var i=0; i<model.faces.length; i++) {
		var max = distances[model.faces[i][0]];
		for (var j=1; j<model.faces[i].length; j++)
			max = Math.max(max, distances[model.faces[i][j]]);
		visible_faces[i] = {face_index:i, distance:max};
	}

	visible_faces.sort(function(a,b){return b.distance - a.distance;});

	for (var i=0; i<visible_faces.length; i++) {
		var vertex = model.faces[visible_faces[i].face_index];

		ctx.beginPath();
		ctx.moveTo(projected_points[vertex[0]][0], projected_points[vertex[0]][1]);
		for (var j=1; j<vertex.length; j++)
			ctx.lineTo(projected_points[vertex[j]][0], projected_points[vertex[j]][1]);
		ctx.closePath();

		if (button_shade) {
			var normal = model.normals[visible_faces[i].face_index];
			var c = Math.floor(256.0 * Math.abs(mul(ry, mul(rx, normal))[2]));
			ctx.fillStyle = 'rgb(' + c + ',' + c + ',' + c + ')';
		}

		ctx.fill();
		if (button_wire) ctx.stroke();
	}

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

function length2(p) {
	return p[0] * p[0] + p[1] * p[1] + p[2] * p[2];
}

function translate(p, v) {
	p[0] += v[0];
	p[1] += v[1];
	p[2] += v[2];
}

function scale(p, v) {
	p[0] *= v[0];
	p[1] *= v[1];
	p[2] *= v[2];
}

function mul(m, p) {
	var q = new Array();
	q[0] = m[0] * p[0] + m[1] * p[1] + m[2] * p[2];
	q[1] = m[3] * p[0] + m[4] * p[1] + m[5] * p[2];
	q[2] = m[6] * p[0] + m[7] * p[1] + m[8] * p[2];
	return q;
}

function project(p, focal_length) {
	p[0] = p[0] * focal_length / p[2];
	p[1] = p[1] * focal_length / p[2];
	p[2] = focal_length;
}

function normal(o, a, b) {
	var p = [o[0] - a[0], o[1] - a[1], o[2] - a[2]];
	var q = [o[0] - b[0], o[1] - b[1], o[2] - b[2]];
	var r = [0, 0, 0];
	r[0] = p[1] * q[2] - p[2] * q[1];
	r[1] = p[2] * q[0] - p[0] * q[2];
	r[2] = p[0] * q[1] - p[1] * q[0];
	var length = Math.sqrt(length2(r));
	if (length <= 0) return r;
	r[0] /= length;
	r[1] /= length;
	r[2] /= length;
	return r;
}

function loadmodel(data, model) {
	model.points = 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.points.push([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) {
	center = [0, 0, 0];
	for (var i=0; i<model.points.length; i++) {
		center[0] += model.points[i][0];
		center[1] += model.points[i][1];
		center[2] += model.points[i][2];
	}
	center[0] = -(center[0] / model.points.length);
	center[1] = -(center[1] / model.points.length);
	center[2] = -(center[2] / model.points.length);

	for (var i=0; i<model.points.length; i++) {
		translate(model.points[i], center);
		scale(model.points[i], [scalar,-scalar,-scalar]);
	}

	model.normals = new Array(model.faces.length);
	for (var i=0; i<model.faces.length; i++) {
		model.normals[i] = normal(
			model.points[model.faces[i][0]],
			model.points[model.faces[i][1]],
			model.points[model.faces[i][2]]
		);
	}
}
})();
</script>
</div><div class="canvas" name="IsosurfaceRendering1">
<canvas id="IsosurfaceRendering1" width="200" height="200"></canvas>
<script id="IsosurfaceRendering1.js">
(function(){
var canvas = document.getElementById("IsosurfaceRendering1");
var ctx    = canvas.getContext("2d");
var w      = canvas.width;
var h      = canvas.height;
var imgdt  = ctx.getImageData(0, 0, w, h);

var id = 0;
canvas.tabIndex = 0;
canvas.onmouseover = canvas.focus;
canvas.onmouseout = canvas.blur;
canvas.onmousemove = onMouseMove;
canvas.onkeydown = onKeyDown;
canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
canvas.onfocus = function(){if (!id) id = requestAnimationFrame(update, canvas);};
function update() {id = requestAnimationFrame(update,canvas); draw();}

var angle_x = 0;
var angle_y = 0;
function onMouseMove(event) {
	var x = event.offsetX;
	var y = event.offsetY;
	if (x>0 && x<canvas.width && y>0 && y<canvas.height) {
		angle_x = (x-canvas.width/2)/(canvas.width/2);
		angle_y = (y-canvas.height/2)/(canvas.height/2);
	}
}

var center = [[0,0,0],[4,4,4],[-5,6,-1],[-4,-4,-4],[4,4,-4],[-4,0,6],[5,-4,5]];
var index  = 2;
var fieldradius = 10; // dependent by center
const R = 6, R2 = R*R, R4 = R2*R2, R6=R4*R2, bound = 0.5;
function length(x,y,z) {return Math.sqrt(x*x + y*y + z*z);}
function blob(x,y,z) {
	var r2 = x*x + y*y + z*z; var r4 = r2*r2; var r6 = r4*r2;
	if (r2 >= R2) return 0;
	return 1 - 0.444444*r6/R6 + 1.888889*r4/R4 - 2.444444*r2/R2;
}
function f(x,y,z) {
	var density = 0;
	for (let i=0; i<index; ++i)
		density += blob(x-center[i][0], y-center[i][1], z-center[i][2]);
	return density;
}
function onKeyDown(event) {
	if (event.keyCode >= 49 && event.keyCode < 49 + center.length)
		index = event.keyCode - 48;
}

const eps = 0.3;
const sqrt3 = Math.sqrt(3);
const distance = 100;
const focal_length = 1000;

draw();
function draw() {
	// center = {0,0,distance};
	// center = rotate(center, ax, ay);
	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];
	[cx,cy,cz] = mul(ry, mul(rx, [0,0,distance]));

	function mul(m, p) {
		var q = [0,0,0,0,0,0,0,0,0];
		q[0] = m[0] * p[0] + m[1] * p[1] + m[2] * p[2];
		q[1] = m[3] * p[0] + m[4] * p[1] + m[5] * p[2];
		q[2] = m[6] * p[0] + m[7] * p[1] + m[8] * p[2];
		return q;
	}

	var I = imgdt.data;
	for (let i=0; i<h; ++i) for (let j=0; j<w; ++j) {
		// view = from center to pixel(i,j);
		var dx = j - w/2;
		var dy = i - h/2;
		var dz = -focal_length;
		[dx,dy,dz] = mul(ry, mul(rx, [dx,dy,dz]));
		var dl = length(dx,dy,dz);
		dx/=dl; dy/=dl; dz/=dl;
		// hit = raymarch(center, view, f);
		var x = cx + dx * (distance - fieldradius);
		var y = cy + dy * (distance - fieldradius);
		var z = cz + dz * (distance - fieldradius);
		var precision = 1;	// smaller is better
		var step = fieldradius * 2 / eps / precision;
		var d = 100, touch = false;
		for (let k=0; k<step; ++k) {
			d = f(x, y, z);
			if (Math.abs(d - bound) < eps) {touch = true; break;}
			x += dx * eps * precision;
			y += dy * eps * precision;
			z += dz * eps * precision;
		}
		var light = 0;
		if (touch) {
			// normal = gradient(hit, f);
			var nx = f(x+eps, y, z) - f(x-eps, y, z);
			var ny = f(x, y+eps, z) - f(x, y-eps, z);
			var nz = f(x, y, z+eps) - f(x, y, z-eps);
			var nl = length(nx,ny,nz);
			nx=-nx; ny=-ny; nz=-nz;
			light = Math.max(0, -nx+ny+nz) / 2 + 0.01
				  + Math.max(0, +nx-ny+nz);
			light = light / nl / sqrt3;
		}
		// fill
		var s = (i * w + j) * 4;
		I[s] = I[s+1] = I[s+2] = 255 * light;
		I[s+2] *= 0.6;
		I[s+3] = (touch ? 255 : 0);
	}
	ctx.putImageData(imgdt, 0, 0);

	ctx.font = "12pt Verdana";
	ctx.textBaseline = "top";
	ctx.textAlign = "center";
	ctx.fillStyle = "gray";
	ctx.fillText("[1234567] show/hidden", canvas.width/2, 0);
}
})();
</script>
</div><div class="canvas" name="IsosurfaceRendering2">
<canvas id="IsosurfaceRendering2" width="200" height="200"></canvas>
<script id="IsosurfaceRendering2.js">
(function(){
var canvas = document.getElementById("IsosurfaceRendering2");
var ctx    = canvas.getContext("2d");
var w      = canvas.width;
var h      = canvas.height;
var imgdt  = ctx.getImageData(0, 0, w, h);

var id = 0;
canvas.tabIndex = 0;
canvas.onmouseover = canvas.focus;
canvas.onmouseout = canvas.blur;
canvas.onmousemove = onMouseMove;
canvas.onkeydown = onKeyDown;
canvas.onblur = function(){cancelAnimationFrame(id); id = 0;};
canvas.onfocus = function(){if (!id) id = requestAnimationFrame(update, canvas);};
function update() {id = requestAnimationFrame(update,canvas); draw();}

var angle_x = 0;
var angle_y = 0;
function onMouseMove(event) {
	var x = event.offsetX;
	var y = event.offsetY;
	if (x>0 && x<canvas.width && y>0 && y<canvas.height) {
		angle_x = (x-canvas.width/2)/(canvas.width/2);
		angle_y = (y-canvas.height/2)/(canvas.height/2);
	}
}

function length(x,y,z) {return Math.sqrt(x*x + y*y + z*z);}
function pow8(x) {x=x*x; x=x*x; return x*x;}
function sqrt8(x) {return Math.sqrt(Math.sqrt(Math.sqrt(x)));}
function length8(x,y,z) {return sqrt8(pow8(x)+pow8(y)+pow8(z));}
function sphere(x,y,z) {return length(x,y,z) - 5;}
function sphereU(x,y,z) {return Math.min(sphere(x,y,z), sphere(x-3,y-3,z-3));}
function sphereD(x,y,z) {return Math.max(sphere(x,y,z), -sphere(x-3,y-3,z-3));}
function sphereI(x,y,z) {return Math.max(sphere(x,y,z), sphere(x-3,y-3,z-3));}
function torus(x,y,z) {return length(length(x,0,z) - 5, y, 0) - 1.5;}
function torus82(x,y,z) {return length8(length(x,0,z) - 5, y, 0) - 1.5;}
function torus88(x,y,z) {return length8(length8(x,0,z) - 5, y, 0) - 1.5;}
function knot(x,y,z) {return Math.min(torus82(y,x,z), torus82(x,y,z), torus82(x,z,y));}
function rbox(x,y,z) {
	return length(
		Math.max(0, Math.abs(x)-2),
		Math.max(0, Math.abs(y)-3),
		Math.max(0, Math.abs(z)-4)) - 3;
}
var shape = [rbox, sphere, sphereU, sphereD, sphereI, torus, torus82, torus88, knot];
var f = shape[0];
function onKeyDown(event) {
	if (event.keyCode >= 49 && event.keyCode < 49 + shape.length)
		f = shape[event.keyCode - 49];
}

const eps = 0.1;
const sqrt3 = Math.sqrt(3);
const distance = 100;
const focal_length = 1000;

draw();
function draw() {
	// center = {0,0,distance};
	// center = rotate(center, ax, ay);
	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];
	[cx,cy,cz] = mul(ry, mul(rx, [0,0,distance]));

	function mul(m, p) {
		var q = [0,0,0,0,0,0,0,0,0];
		q[0] = m[0] * p[0] + m[1] * p[1] + m[2] * p[2];
		q[1] = m[3] * p[0] + m[4] * p[1] + m[5] * p[2];
		q[2] = m[6] * p[0] + m[7] * p[1] + m[8] * p[2];
		return q;
	}

	var I = imgdt.data;
	for (let i=0; i<h; ++i) for (let j=0; j<w; ++j) {
		// view = from center to pixel(i,j);
		var dx = j - w/2;
		var dy = i - h/2;
		var dz = -focal_length;
		[dx,dy,dz] = mul(ry, mul(rx, [dx,dy,dz]));
		var dl = length(dx,dy,dz);
		dx/=dl; dy/=dl; dz/=dl;
		// hit = raymarch(center, view, f);
		var x = cx, y = cy, z = cz, d = distance, touch = false;
		for (let k=0; k<64; ++k) {
			d = f(x, y, z);
			if (Math.abs(d) < eps) {touch = true; break;}
			if (z < -distance || z > distance) break;
			x += dx * d;
			y += dy * d;
			z += dz * d;
		}
		var light = 0;
		if (touch) {
			// normal = gradient(hit, f);
			var nx = f(x+eps, y, z) - f(x-eps, y, z);
			var ny = f(x, y+eps, z) - f(x, y-eps, z);
			var nz = f(x, y, z+eps) - f(x, y, z-eps);
			var nl = length(nx,ny,nz);
			light = Math.max(0, -nx+ny+nz) / 2 + 0.01
				  + Math.max(0, +nx-ny+nz);
			light = light / nl / sqrt3;
		}
		// fill
		var s = (i * w + j) * 4;
		I[s] = I[s+1] = I[s+2] = 255 * light;
		I[s+2] *= 0.6;
		I[s+3] = (touch ? 255 : 0);
	}
	ctx.putImageData(imgdt, 0, 0);

	ctx.font = "12pt Verdana";
	ctx.textBaseline = "top";
	ctx.textAlign = "center";
	ctx.fillStyle = "gray";
	ctx.fillText("[123456789] shape", canvas.width/2, 0);
}
})();
</script>
</div>