"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); }); }); });