<!DOCTYPE html>
<html>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<body style="background-color: black;">
<script>
function matrix_mul(m1, m2) {
let out = Array(16).fill(0);
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
for (let k = 0; k < 4; k++) {
out[4 * i + k] += m1[4 * j + k] * m2[4 * i + j];
}
}
}
return out;
}
let mesh_data = Float32Array.from([
+0.176, +0.9, +0.8382, +0.8, 0.0, 0.236, 1.382, -1.618,
+0.176, +0.8, +0.9, +0.8382, 0.0, 0.236, 1.382, -1.618,
-0.176, +0.8, +0.9, +0.8382, 0.0, 0.236, 1.382, -1.618,
]);
let instance_data = function () {
function quaternion_mul(q1, q2) {
let [w1, x1, y1, z1] = q1;
let [w2, x2, y2, z2] = q2;
return [w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
, w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2
, w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2
, w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2
];
}
let binary_icosahedral_group = Array();
let q = [0.809, 0.309, 0.5, 0];
[
[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1],
[0.5, 0.5, 0.5, 0.5], [0.5, 0.5, 0.5, -0.5], [0.5, 0.5, -0.5, 0.5], [0.5, 0.5, -0.5, -0.5],
[0.5, -0.5, 0.5, 0.5], [0.5, -0.5, 0.5, -0.5], [0.5, -0.5, -0.5, 0.5], [0.5, -0.5, -0.5, -0.5]
].forEach(tmp => {
for (let i = 0; i < 10; i++) {
binary_icosahedral_group.push(tmp);
tmp = quaternion_mul(q, tmp);
}
});
let matrices = Array();
binary_icosahedral_group.forEach(q1 => binary_icosahedral_group.forEach(q2 =>
[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]].forEach(v =>
quaternion_mul(q1, quaternion_mul(v, q2)).forEach(x => { matrices.push(x); })
)
));
return Float32Array.from(matrices);
}();
let canvas_size = 800;
let canvas = window.document.createElement("canvas");
canvas.setAttribute("width", canvas_size);
canvas.setAttribute("height", canvas_size);
canvas.setAttribute("style", "background-color: #555; cursor: pointer;");
window.document.body.appendChild(canvas);
canvas.addEventListener("click", function (e) { canvas.requestPointerLock(); });
let perspective = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 0, -1,
0, 0, -0.01, 0
];
let transform = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
];
let gl = canvas.getContext("webgl2");
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.viewport(0, 0, canvas_size, canvas_size);
gl.enable(gl.DEPTH_TEST);
let vshader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vshader,
'#version 300 es\n' +
'in vec4 pos;\n' +
'in vec4 norm;\n' +
'in mat4 instance_transform;\n' +
'out vec4 vpos;\n' +
'out vec4 vnorm;\n' +
'out vec4 lightpos[3];\n' +
'uniform mat4 transform;\n' +
'uniform mat4 perspective;\n' +
'uniform vec4 lightPositions[3];\n' +
'void main() {\n' +
' vpos = transform * instance_transform * pos;\n' +
' vnorm = transform * instance_transform * norm;\n' +
' lightpos[0] = transform * lightPositions[0];\n' +
' lightpos[1] = transform * lightPositions[1];\n' +
' lightpos[2] = transform * lightPositions[2];\n' +
' gl_Position = perspective * vpos;\n' +
'}\n'
);
gl.compileShader(vshader);
let fshader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fshader,
'#version 300 es\n' +
'precision mediump float;\n' +
'in vec4 vpos;\n' +
'in vec4 vnorm;\n' +
'in vec4 lightpos[3];\n' +
'out vec4 color;\n' +
'vec4 directionTo(vec4 u, vec4 v) {\n' +
' return u - v * (dot(u, v) / dot(v, v));\n' +
'}\n' +
'float lighting(vec4 u, vec4 v) {\n' +
' float x = dot(normalize(u), normalize(v));\n' +
' return 1. / (1. - x * x);\n' +
'}\n' +
'void main() {\n' +
' color = vec4(0.3, 0.3, 0.3, 1.0);\n' +
' float cos_angle;\n' +
' cos_angle = dot(\n' +
' normalize(directionTo(lightpos[0], vpos)),\n' +
' normalize(vnorm)\n' +
' );\n' +
' color.x += 0.3 * max(0., cos_angle) * lighting(lightpos[0], vpos);\n' +
' cos_angle = dot(\n' +
' normalize(directionTo(lightpos[1], vpos)),\n' +
' normalize(vnorm)\n' +
' );\n' +
' color.y += 0.3 * max(0., cos_angle) * lighting(lightpos[1], vpos);\n' +
' cos_angle = dot(\n' +
' normalize(directionTo(lightpos[2], vpos)),\n' +
' normalize(vnorm)\n' +
' );\n' +
' color.z += 0.3 * max(0., cos_angle) * lighting(lightpos[2], vpos);\n' +
'}\n'
);
gl.compileShader(fshader);
let program = gl.createProgram();
gl.attachShader(program, vshader);
gl.attachShader(program, fshader);
gl.linkProgram(program);
gl.useProgram(program);
gl.deleteShader(vshader);
gl.deleteShader(fshader);
let vao = gl.createVertexArray();
gl.bindVertexArray(vao);
let vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, mesh_data, gl.STATIC_DRAW);
let instance_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, instance_buffer);
gl.bufferData(gl.ARRAY_BUFFER,
instance_data,
gl.STATIC_DRAW,
);
let attrib_pos = gl.getAttribLocation(program, "pos");
let attrib_norm = gl.getAttribLocation(program, "norm");
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.enableVertexAttribArray(attrib_pos);
gl.enableVertexAttribArray(attrib_norm);
gl.vertexAttribPointer(attrib_pos, 4, gl.FLOAT, false, 8 * 4, 0);
gl.vertexAttribPointer(attrib_norm, 4, gl.FLOAT, false, 8 * 4, 4 * 4);
let attrib_instance_transform = gl.getAttribLocation(program, "instance_transform");
gl.bindBuffer(gl.ARRAY_BUFFER, instance_buffer);
for (let i = 0; i < 4; i++) {
gl.enableVertexAttribArray(attrib_instance_transform + i);
gl.vertexAttribPointer(attrib_instance_transform + i, 4, gl.FLOAT, false, 16 * 4, 16 * i);
gl.vertexAttribDivisor(attrib_instance_transform + i, 1);
}
let running = null;
let key_pressed = {};
window.addEventListener("keydown", function (e) { key_pressed[e.code] = true });
window.addEventListener("keyup", function (e) { key_pressed[e.code] = false });
let time;
canvas.addEventListener("mousemove", function (e) {
transform = matrix_mul([
Math.cos(e.movementX / canvas_size), 0, -Math.sin(e.movementX / canvas_size), 0,
0, 1, 0, 0,
Math.sin(e.movementX / canvas_size), 0, Math.cos(e.movementX / canvas_size), 0,
0, 0, 0, 1,
], transform);
transform = matrix_mul([
1, 0, 0, 0,
0, Math.cos(e.movementY / canvas_size), Math.sin(e.movementY / canvas_size), 0,
0, -Math.sin(e.movementY / canvas_size), Math.cos(e.movementY / canvas_size), 0,
0, 0, 0, 1,
], transform);
});
function animation_frame_callback(t) {
running = null;
if (!time) { time = t; }
let dt = (t - time) / 1000;
time = t;
if (key_pressed["KeyW"]) {
transform = matrix_mul([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, Math.cos(dt), -Math.sin(dt),
0, 0, Math.sin(dt), Math.cos(dt)
], transform);
}
if (key_pressed["KeyS"]) {
transform = matrix_mul([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, Math.cos(dt), Math.sin(dt),
0, 0, -Math.sin(dt), Math.cos(dt)
], transform);
}
if (key_pressed["ShiftLeft"]) {
transform = matrix_mul([
1, 0, 0, 0,
0, Math.cos(dt), 0, -Math.sin(dt),
0, 0, 1, 0,
0, Math.sin(dt), 0, Math.cos(dt)
], transform);
}
if (key_pressed["Space"]) {
transform = matrix_mul([
1, 0, 0, 0,
0, Math.cos(dt), 0, Math.sin(dt),
0, 0, 1, 0,
0, -Math.sin(dt), 0, Math.cos(dt)
], transform);
}
if (key_pressed["KeyD"]) {
transform = matrix_mul([
Math.cos(dt), 0, 0, Math.sin(dt),
0, 1, 0, 0,
0, 0, 1, 0,
-Math.sin(dt), 0, 0, Math.cos(dt)
], transform);
}
if (key_pressed["KeyA"]) {
transform = matrix_mul([
Math.cos(dt), 0, 0, -Math.sin(dt),
0, 1, 0, 0,
0, 0, 1, 0,
Math.sin(dt), 0, 0, Math.cos(dt)
], transform);
}
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv(gl.getUniformLocation(program, "transform"), false, transform);
gl.uniformMatrix4fv(gl.getUniformLocation(program, "perspective"), false, perspective);
gl.uniform4fv(gl.getUniformLocation(program, "lightPositions"), Float32Array.from([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]));
gl.drawArraysInstanced(gl.TRIANGLES, 0, mesh_data.length / 8, instance_data.length / 16);
running = window.requestAnimationFrame(animation_frame_callback);
};
document.addEventListener('pointerlockchange', function (e) {
if (running) {
window.cancelAnimationFrame(running)
}
if (document.pointerLockElement === canvas) {
key_pressed = {};
running = window.requestAnimationFrame(animation_frame_callback);
}
}, false);
</script>
</body>
</html>