324 lines
8.5 KiB
JavaScript
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);
|
|
});
|
|
});
|
|
});
|