324 lines
8.5 KiB
JavaScript

"use strict";
const v3 = {
add: function (v1, v2) {
return [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]];
},
normalize: function (v) {
return v3.scale(v, 1 / v3.norm(v));
},
norm: function (v) {
return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2) + Math.pow(v[2], 2));
},
scale: function (v, f) {
return [v[0] * f, v[1] * f, v[2] * f];
},
};
function getGyroPermission(){
if(!DeviceOrientationEvent.requestPermission){
return Promise.resolve("granted")
}
else{
return DeviceOrientationEvent.requestPermission()
}
}
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(shader));
}
return shader;
}
function createProgram(gl, shaders) {
const program = gl.createProgram();
for (const shader of shaders) {
gl.attachShader(program, shader);
}
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error(gl.getProgramInfoLog(program));
}
return program;
}
function initIsocahedron(vertices, elements) {
const t = 1 + Math.sqrt(5) / 2;
vertices.push(
v3.normalize([-1, t, 0]),
v3.normalize([1, t, 0]),
v3.normalize([-1, -t, 0]),
v3.normalize([1, -t, 0]),
v3.normalize([0, -1, t]),
v3.normalize([0, 1, t]),
v3.normalize([0, -1, -t]),
v3.normalize([0, 1, -t]),
v3.normalize([t, 0, -1]),
v3.normalize([t, 0, 1]),
v3.normalize([-t, 0, -1]),
v3.normalize([-t, 0, 1]),
);
elements.push(
0, 11, 5,
0, 5, 1,
0, 1, 7,
0, 7, 10,
0, 10, 11,
1, 5, 9,
5, 11, 4,
11, 10, 2,
10, 7, 6,
7, 1, 8,
3, 9, 4,
3, 4, 2,
3, 2, 6,
3, 6, 8,
3, 8, 9,
4, 9, 5,
2, 4, 11,
6, 2, 10,
8, 6, 7,
9, 8, 1,
);
}
function getMidpoint(vertices, midpoints, i0, i1) {
const k = `${i0}_${i1}`;
if (midpoints.has(k)) {
return vertices[midpoints[k]];
}
const p0 = vertices[i0];
const p1 = vertices[i1];
const midpoint = v3.normalize(v3.scale(v3.add(p0, p1), 0.5));
const midpointIndex = vertices.length;
midpoints.set(k, midpointIndex);
vertices.push(midpoint);
return midpointIndex;
}
function subdivideIsocahedron(vertices, elements, midpoints) {
const newElements = [];
for (let i = 0; i < elements.length; i += 3) {
const p0 = elements[i];
const p1 = elements[i + 1];
const p2 = elements[i + 2];
const m0 = getMidpoint(vertices, midpoints, p0, p1);
const m1 = getMidpoint(vertices, midpoints, p1, p2);
const m2 = getMidpoint(vertices, midpoints, p2, p0);
newElements.push(
m0, m1, m2,
p0, m0, m2,
p1, m1, m0,
p2, m2, m1
);
}
return newElements;
}
function generateIsocahedron() {
const r = 1;
const n = 3;
const tau = 2 * Math.PI;
const vertices = [];
const normals = [];
let elements = [];
initIsocahedron(vertices, elements);
const midpoints = new Map();
for (let i = 0; i < 1; i++) {
elements = subdivideIsocahedron(vertices, elements, midpoints);
}
return {
vertices: [].concat(...vertices),
// as we want to approximate a unit sphere, all the vertices are unit vectors
normals: [].concat(...vertices),
elements
};
}
const canvas = document.querySelector("#canvas");
const gl = canvas.getContext("webgl");
const vertexShader = createShader(gl, gl.VERTEX_SHADER, `
attribute vec4 a_position;
attribute vec3 a_normal;
uniform mat4 u_matrix;
uniform mat4 u_world;
uniform mat4 u_worldIT;
uniform vec3 u_light;
uniform vec3 u_camera;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
varying vec3 v_pos;
void main() {
gl_Position = u_matrix * a_position;
v_pos = a_position.rgb;
v_pos.r = abs(v_pos.r);
v_pos.g = abs(v_pos.g);
v_pos.b = abs(v_pos.b);
v_normal = mat3(u_worldIT) * a_normal;
vec3 surfaceWorldPos = (u_world * a_position).xyz;
v_surfaceToLight = u_light - surfaceWorldPos;
v_surfaceToView = u_camera - surfaceWorldPos;
}
`);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, `
precision highp float;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
varying vec3 v_pos;
void main() {
vec3 halfVector = normalize(normalize(v_surfaceToLight) + normalize(v_surfaceToView));
float ka = 0.3;
float kd = 0.8;
float ks = 0.6;
float gamma = 100.0;
gl_FragColor = vec4(v_pos, 1.0);
gl_FragColor.rgb *= ka + (kd * dot(normalize(v_normal), normalize(v_surfaceToLight)));
gl_FragColor.rgb += ks * pow(max(0.0, dot(normalize(v_normal), halfVector)), gamma);
}
`);
const program = createProgram(gl, [vertexShader, fragmentShader]);
const {vertices, normals, elements} = generateIsocahedron();
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
const elementBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(elements), gl.STATIC_DRAW);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
const positionAttribLocation = gl.getAttribLocation(program, "a_position");
const normalAttribLocation = gl.getAttribLocation(program, "a_normal");
const matrixLocation = gl.getUniformLocation(program, "u_matrix");
const worldMatrixLocation = gl.getUniformLocation(program, "u_world");
const worldITMatrixLocation = gl.getUniformLocation(program, "u_worldIT");
const lightVecLocation = gl.getUniformLocation(program, "u_light");
const cameraVecLocation = gl.getUniformLocation(program, "u_camera");
const aspectRatio = gl.canvas.width / gl.canvas.height;
const zNear = 1;
const zFar = 2000;
const rotation = [0, 0, 0];
gl.useProgram(program);
gl.enableVertexAttribArray(positionAttribLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(
positionAttribLocation,
3, // element size
gl.FLOAT,
false, // normalize
0, // stride
0 // offset
);
gl.enableVertexAttribArray(normalAttribLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.vertexAttribPointer(normalAttribLocation, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementBuffer);
function drawScene () {
window.requestAnimationFrame(drawScene);
const t = performance.now() / 1000;
const lightPos = [10, 10, 30];
const projectionMatrix = m4.perspective(Math.PI / 180 * 60, aspectRatio, zNear, zFar);
const cameraPos = [0, 0, 30];
const target = [0, 0, 0];
const up = [0, 1, 0];
const cameraMatrix = m4.lookAt(cameraPos, target, up);
const viewMatrix = m4.inverse(cameraMatrix);
let worldMatrix = m4.identity();
worldMatrix = m4.xRotate(worldMatrix, rotation[0]);
worldMatrix = m4.yRotate(worldMatrix, rotation[1]);
worldMatrix = m4.zRotate(worldMatrix, rotation[2]);
worldMatrix = m4.scale(worldMatrix, 12, 12, 12);
const worldITMatrix = m4.transpose(m4.inverse(worldMatrix));
const viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
const worldViewProjectionMatrix = m4.multiply(viewProjectionMatrix, worldMatrix);
/*
let matrix = projectionMatrix;
matrix = m4.translate(matrix, 0, 0, -30);
matrix = m4.xRotate(matrix, rotation[0]);
matrix = m4.yRotate(matrix, rotation[1]);
matrix = m4.zRotate(matrix, rotation[2]);
matrix = m4.scale(matrix, 10, 10, 10);
*/
gl.uniformMatrix4fv(matrixLocation, false, worldViewProjectionMatrix);
gl.uniformMatrix4fv(worldMatrixLocation, false, worldMatrix);
gl.uniformMatrix4fv(worldITMatrixLocation, false, worldITMatrix);
gl.uniform3fv(lightVecLocation, lightPos);
gl.uniform3fv(cameraVecLocation, cameraPos);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawElements(
gl.TRIANGLES,
elements.length, // count
gl.UNSIGNED_SHORT,
0, // offset
);
}
drawScene();
canvas.addEventListener("click", e => {
getGyroPermission()
.then(response => {
window.addEventListener("deviceorientation", e => {
rotation[0] = 2 * (-e.beta / 180 * Math.PI);
rotation[1] = 2 * (-e.gamma / 180 * Math.PI);
rotation[2] = 2 * (-e.alpha / 180 * Math.PI + Math.PI / 2);
});
});
});