// @ts-ignore import mat4 from 'gl-mat4'; // @ts-ignore import {convert} from './objparser'; import $ from 'jquery'; import {initBuffers, deleteBuffers} from './buffers'; import {initShaderProgram} from './shaders'; import {changeFragmentShader} from './changeshader'; import {loadTexture} from './texture'; let squareRotation = 0.0; main(); /** * The program purpose is encapsulated in a main function */ async function main() { const canvas: any = document.querySelector('#glCanvas')!; const gl = canvas.getContext('webgl'); if (gl == null) { canvas.parentNode.removeChild(canvas); document.getElementById('root')!.insertAdjacentHTML('beforeend', `

Unable to initialize WebGL. Your browser or machine may not support it.

`); } /* eslint-disable */ const vsSource = ` attribute vec4 aVertexPosition; attribute vec4 aVertexNormal; attribute vec2 aTextureCoord; uniform mat4 uProjectionMatrix; uniform mat4 uviewMatrix; uniform mat4 umodelMatrix; varying highp vec4 vNormal; varying highp vec3 vPosition; varying highp vec2 vTextureCoord; void main() { gl_Position = uProjectionMatrix * uviewMatrix * umodelMatrix * aVertexPosition; vPosition = vec3(aVertexPosition); vNormal = umodelMatrix * aVertexNormal; vTextureCoord = aTextureCoord; } `; const fsSource = ` precision highp float; varying highp vec4 vNormal; varying highp vec3 vPosition; vec3 extremize(vec3 v, float n) { if (v.x > n / 2.) v.x = n; else v.x = 0.; if (v.y > n / 2.) v.y = n; else v.y = 0.; if (v.z > n / 2.) v.z = n; else v.z = 0.; return v; } void main() { vec3 n = normalize(vec3(-50000., 100000., 50000.) - vPosition); float diffuse = max(dot(normalize(vNormal.xyz), n), 0.); float specular = pow( max(dot( reflect(n, normalize(vNormal.xyz)), normalize(vec3(0., 0., -50.) - vPosition)), 0.), 10.); vec3 tmp = extremize(mod(vPosition.xyz + vec3(1000.), vec3(2.)), 2.); float texture = (tmp.x + tmp.y + tmp.z) / 6.; gl_FragColor = vec4((texture * diffuse * 0.8) + (texture * vec3(0.2)) + (specular * vec3(1.)), 1.0); }`; const fsSource2 = ` precision highp float; varying highp vec4 vNormal; varying highp vec3 vPosition; vec3 extremize(vec3 v, float n) { if (v.x > n / 2.) v.x = n; else v.x = 0.; if (v.y > n / 2.) v.y = n; else v.y = 0.; if (v.z > n / 2.) v.z = n; else v.z = 0.; return v; } void main() { vec3 n = normalize(vec3(-50000., 100000., 50000.) - vPosition); float diffuse = max(dot(normalize(vNormal.xyz), n), 0.); float specular = pow( max(dot( reflect(n, normalize(vNormal.xyz)), normalize(vec3(0., 0., -50.) - vPosition)), 0.), 10.); vec3 texture = extremize(mod(vPosition.xyz + vec3(1000.), vec3(2.)), 2.) / vec3(2); gl_FragColor = vec4((texture * diffuse * 0.8) + (texture * vec3(0.2)) + (specular * vec3(1.)), 1.0); }`; const fsSource3 = ` precision highp float; varying highp vec4 vNormal; varying highp vec3 vPosition; vec3 extremize(vec3 v, float n) { if (v.x > n / 2.) v.x = n; else v.x = 0.; if (v.y > n / 2.) v.y = n; else v.y = 0.; if (v.z > n / 2.) v.z = n; else v.z = 0.; return v; } void main() { vec3 n = normalize(vec3(-50000., 100000., 50000.) - vPosition); float diffuse = max(dot(normalize(vNormal.xyz), n), 0.); float specular = pow( max(dot( reflect(n, normalize(vNormal.xyz)), normalize(vec3(0., 0., -50.) - vPosition)), 0.), 10.); vec3 texture = extremize(mod(vPosition.xyz + vec3(1000.), vec3(2.)), 2.) / vec3(2); gl_FragColor = vec4((diffuse * 0.8) + (vec3(0.2)) + (specular * vec3(1.)), 1.0); }`; const fsSource4 = ` precision highp float; varying highp vec2 vTextureCoord; varying highp vec4 vNormal; varying highp vec3 vPosition; uniform sampler2D uSampler; void main() { highp vec4 texelColor = texture2D(uSampler, vTextureCoord); vec3 n = normalize(vec3(-50000., 100000., 50000.) - vPosition); float diffuse = max(dot(normalize(vNormal.xyz), n), 0.); float specular = pow( max(dot( reflect(n, normalize(vNormal.xyz)), normalize(vec3(0., 0., -50.) - vPosition)), 0.), 10.); gl_FragColor = vec4((texelColor.xyz * diffuse * 0.8) + (texelColor.xyz * vec3(0.2)) + (specular * vec3(1.)), 1.0); }`; /* eslint-enable */ /** * Fetch an obj file * @param {string} url the url to fetch the object from * @return {string} the raw data of the obj file */ async function getObj(url: string) { const response = await fetch(url); const data = await response.text(); return data; } const data = await getObj('/static/objs/fox.obj'); let distance: any = $('#input1').val(); let circleSize: any = $('#input2').val(); distance = parseFloat(distance); circleSize = parseFloat(circleSize); const [ positions, normals, uvs, indices, ] = convert(data); let length = indices.length; let [shaderProgram, fragmentShader]: any = initShaderProgram(gl, vsSource, fsSource4); let programInfo: any = { program: shaderProgram, attribLocations: { vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'), vertexNormal: gl.getAttribLocation(shaderProgram, 'aVertexNormal'), textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'), }, uniformLocations: { projectionMatrix: gl.getUniformLocation( shaderProgram, 'uProjectionMatrix'), viewMatrix: gl.getUniformLocation( shaderProgram, 'uviewMatrix'), modelMatrix: gl.getUniformLocation( shaderProgram, 'umodelMatrix'), uSampler: gl.getUniformLocation( shaderProgram, 'uSampler'), }, }; let texture = loadTexture(gl, '/static/textures/fox.png'); let buffers = initBuffers(gl, positions, indices, normals, uvs); let then = 0; let changed = false; /** * Draws the scene repeatedly * @param {number} now the current time */ function render(now: any) { now *= 0.001; const deltaTime = now - then; if (now >= 1 && changed == false) { changed = true; } then = now; drawScene(gl, programInfo, buffers, deltaTime, length, distance, circleSize, texture); requestAnimationFrame(render); } /** * Pushes a new obj file to the gl buffer * @param {string} data the obj file to push */ function updateObj(data: string) { const [ positions, normals, uvs, indices, ] = convert(data); length = indices.length; deleteBuffers(gl, buffers); buffers = initBuffers(gl, positions, indices, normals, uvs); } $(function() { $('#input1').on('keypress', function(event: any) { if (event.which === 13) { event.preventDefault(); $('#changedistance').click(); } }); $('#input2').on('keypress', function(event: any) { if (event.which === 13) { event.preventDefault(); $('#changecirclesize').click(); } }); $('#s_blackandwhite').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource, vsSource); }); $('#s_color').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource2, vsSource); }); $('#s_flat').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource3, vsSource); }); $('#s_texture').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource4, vsSource); }); $('#changedistance').on('click', function() { distance = $('#input1').val(); distance = parseFloat(distance); }); $('#changecirclesize').on('click', function() { circleSize = $('#input2').val(); circleSize = parseFloat(circleSize); }); $('#o_sphere').on('click', async function() { const data = await getObj('/static/objs/sphere.obj'); updateObj(data); }); $('#o_teapot').on('click', async function() { const data = await getObj('/static/objs/teapot.obj'); updateObj(data); }); $('#o_fox').on('click', async function() { const data = await getObj('/static/objs/fox.obj'); updateObj(data); }); $('#t_wall').on('click', async function() { texture = loadTexture(gl, '/static/textures/wall.png'); }); $('#t_ice').on('click', async function() { texture = loadTexture(gl, '/static/textures/ice.png'); }); $('#t_noise').on('click', async function() { texture = loadTexture(gl, '/static/textures/noise.png'); }); $('#t_fox').on('click', async function() { texture = loadTexture(gl, '/static/textures/fox.png'); }); }); requestAnimationFrame(render); } /** * Draw a webgl scene * @param {any} gl the WebGL context * @param {any} programInfo WebGL program information * @param {any} buffers the buffers to draw * @param {number} deltaTime the difference in time since last call * @param {number} length the index buffer length * @param {number} distance distance of camera * @param {number} circleSize size of circle the object is rotating around * @param {any} texture the texture to load */ function drawScene(gl: any, programInfo: any, buffers: any, deltaTime: number, length: number, distance: number, circleSize: number, texture: any) { gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clearDepth(1.0); gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); // Clear the canvas before we start drawing on it. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Create a perspective matrix, a special matrix that is // used to simulate the distortion of perspective in a camera. // Our field of view is 45 degrees, with a width/height // ratio that matches the display size of the canvas // and we only want to see objects between 0.1 units // and 100 units away from the camera. const fieldOfView = 45 * Math.PI / 180; const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; const zNear = 0.1; const zFar = 1000.0; const projectionMatrix = mat4.create(); // note: glmatrix.js always has the first argument // as the destination to receive the result. mat4.perspective( projectionMatrix, fieldOfView, aspect, zNear, zFar); const modelMatrix = mat4.create(); mat4.rotate(modelMatrix, modelMatrix, squareRotation, [ 0, 1, 0, ]); // Set the drawing position to the "identity" point, which is // the center of the scene. const viewMatrix = mat4.create(); mat4.translate( viewMatrix, viewMatrix, [ Math.cos(squareRotation) * circleSize, Math.sin(squareRotation) * circleSize, 0, ]); mat4.translate( viewMatrix, viewMatrix, [0.0, 0.0, -distance]); // Tell WebGL how to pull out the positions from the position // buffer into the vertexPosition attribute. { const numComponents = 3; const type = gl.FLOAT; const normalize = false; const stride = 0; const offset = 0; gl.bindBuffer(gl.ARRAY_BUFFER, buffers.positions); gl.vertexAttribPointer( programInfo.attribLocations.vertexPosition, numComponents, type, normalize, stride, offset); gl.enableVertexAttribArray( programInfo.attribLocations.vertexPosition); } { const numComponents = 3; const type = gl.FLOAT; const normalize = false; const stride = 0; const offset = 0; gl.bindBuffer(gl.ARRAY_BUFFER, buffers.normals); gl.vertexAttribPointer( programInfo.attribLocations.vertexNormal, numComponents, type, normalize, stride, offset); gl.enableVertexAttribArray( programInfo.attribLocations.vertexNormal); } // tell webgl how to pull out the texture coordinates from buffer { const num = 2; // every coordinate composed of 2 values const type = gl.FLOAT; // the data in the buffer is 32 bit float const normalize = false; // don't normalize const stride = 0; // how many bytes to get from one set to the next const offset = 0; // how many bytes inside the buffer to start from gl.bindBuffer(gl.ARRAY_BUFFER, buffers.uvs); gl.vertexAttribPointer(programInfo.attribLocations.textureCoord, num, type, normalize, stride, offset); gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord); } // Tell WebGL which indices to use to index the vertices gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices); // Tell WebGL to use our program when drawing gl.useProgram(programInfo.program); // Set the shader uniforms gl.uniformMatrix4fv( programInfo.uniformLocations.projectionMatrix, false, projectionMatrix); gl.uniformMatrix4fv( programInfo.uniformLocations.viewMatrix, false, viewMatrix); gl.uniformMatrix4fv( programInfo.uniformLocations.modelMatrix, false, modelMatrix); // Tell WebGL we want to affect texture unit 0 gl.activeTexture(gl.TEXTURE0); // Bind the texture to texture unit 0 gl.bindTexture(gl.TEXTURE_2D, texture); // Tell the shader we bound the texture to texture unit 0 gl.uniform1i(programInfo.uniformLocations.uSampler, 0); { const vertexCount = length; const type = gl.UNSIGNED_SHORT; const offset = 0; gl.drawElements(gl.TRIANGLES, vertexCount, type, offset); } squareRotation += deltaTime; }