├── README.md ├── LICENSE ├── assets ├── js │ ├── texture.js │ ├── vbo.js │ ├── meshviewer.js │ ├── vao.js │ ├── primitives.js │ ├── glmanager.js │ ├── jsutils.js │ ├── camera.js │ ├── light.js │ ├── objects.js │ ├── vertex.js │ ├── shader.js │ ├── mesh.js │ └── objloader.js └── libs │ └── gl-matrix.js ├── .gitignore └── index.html /README.md: -------------------------------------------------------------------------------- 1 | # mesh-viewer-webgl 2 | mesh viewer in webgl 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 DKE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/js/texture.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // texture usage 3 | const TEXTURE_TYPE = { 4 | DIFFUSE: "DIFFUSE", 5 | SPECULAR: "SPECULAR", 6 | NORMAL: "NORMAL", 7 | HEIGHT: "HEIGHT", 8 | DISPLACEMENT: "DISPLACEMENT", 9 | AO: "AO", 10 | ROUGHNESS: "ROUGHNESS", 11 | ALBEDO: "ALBEDO", 12 | METALLIC: "METALLIC", 13 | }; 14 | 15 | class Texture { 16 | constructor(ttype = null, flag = null, name = null, id = null) { 17 | this._type = ttype; 18 | this._flag = flag; 19 | this._name = name; 20 | this._id = id; 21 | } 22 | get ttype() { 23 | if (this._type === null) { 24 | throw "texture type is null"; 25 | } 26 | return this._type; 27 | } 28 | get flag() { 29 | if (this._flag === null) { 30 | throw "texture flag is null"; 31 | } 32 | return this._flag; 33 | } 34 | get name() { 35 | if (this._name === null) { 36 | throw "texture name is null"; 37 | } 38 | return this._name; 39 | } 40 | get id() { 41 | if (this._id === null) { 42 | throw "texture id is null"; 43 | } 44 | return this._id; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /assets/js/vbo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // vertex buffer object 4 | 5 | class VertexBufferObject { 6 | constructor(arr) { 7 | this._buffer_ptr = null; 8 | this._data = arr; 9 | this.set_triangle_data(); 10 | } 11 | set_triangle_data() { 12 | this._data = new Float32Array([ 13 | -0.5, -0.5, 0.0, 14 | 0.5, -0.5, 0.0, 15 | 0.0, 0.5, 0.0, 16 | ]); 17 | } 18 | set_cube_data() { 19 | this._data = cube_vertex_normal_texture["data"]; 20 | } 21 | get vbo() { 22 | if (this._buffer_ptr === null) { 23 | throw "vertex buffer object has not been initialized"; 24 | } 25 | return this._buffer_ptr; 26 | } 27 | get data() { 28 | if (this._data === null) { 29 | throw "there is no data associated to this buffer"; 30 | } 31 | return this._data; 32 | } 33 | set data(arr) { 34 | this._data = arr; 35 | } 36 | init(gl, arr) { 37 | this._buffer_ptr = gl.createBuffer(); 38 | this.data = arr; 39 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo); 40 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.data), 41 | gl.STATIC_DRAW); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /assets/js/meshviewer.js: -------------------------------------------------------------------------------- 1 | // mesh viewer scripts 2 | "use strict"; 3 | 4 | /** 5 | starts the webgl object using the associated canvas id 6 | */ 7 | 8 | class SceneManager { 9 | constructor() { 10 | this._gl_manager = null; 11 | this._shader = null; 12 | this.drawables = []; 13 | } 14 | clear() { 15 | this.drawables = []; 16 | } 17 | get gl() { 18 | if (this._gl_manager === null) { 19 | throw "gl manager is null"; 20 | } 21 | return this._gl_manager.gl; 22 | } 23 | set gl(glmanager) { 24 | this._gl_manager = glmanager; 25 | } 26 | 27 | get mShader() { 28 | if (this._shader === null) { 29 | throw "shader is null"; 30 | } 31 | return this._shader; 32 | } 33 | set mShader(s) { 34 | this._shader = s; 35 | } 36 | load_renderables(arr) { 37 | // 38 | let color = glMatrix.vec3.fromValues(0.2, 0.5, 0.5); 39 | let model = glMatrix.mat4.create(); 40 | } 41 | add_renderable(a) { 42 | this.drawables.push(a); 43 | } 44 | init(canvas_id, vs_id, fs_id) { 45 | this.gl = new GLManager(canvas_id); 46 | this.mShader = new ConstantColorShader(vs_id, fs_id); 47 | this.mShader.link_program(this.gl); 48 | } 49 | draw() { 50 | let gl = this.gl; 51 | this.mShader.activate(gl); 52 | 53 | for (const key in this.drawables) { 54 | this.drawables[key].draw(gl, this.mShader); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /assets/js/vao.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | class VertexArrayObject { 5 | constructor() { 6 | this._props = null; 7 | this._vao = null; 8 | } 9 | init(gl) { 10 | this._vao = gl.createVertexArray(); 11 | } 12 | get vao() { 13 | if (this._vao === null) { 14 | throw "vertex array object is null"; 15 | } 16 | return this._vao; 17 | } 18 | get props() { 19 | if (this._props === null) { 20 | throw "vertex array props is null"; 21 | } 22 | return this._props; 23 | 24 | } 25 | set_props(coord) { 26 | if (this._props === null) { 27 | this._props = {}; 28 | } 29 | this.props[coord["name"]] = coord; 30 | } 31 | set_prop_size(name, size) { 32 | this.props[name]["size"] = size; 33 | } 34 | set_prop_type(name, type) { 35 | this.props[name]["type"] = type; 36 | } 37 | set_prop_location(name, loc) { 38 | this.props[name]["location"] = loc; 39 | } 40 | 41 | enable(gl, vbo) { 42 | // 43 | gl.bindVertexArray(this.vao); 44 | 45 | // 46 | gl.bindBuffer(gl.ARRAY_BUFFER, vbo); 47 | // 48 | var offset = 0; 49 | for (const key in this.props) { 50 | let prop = this.props[key]; 51 | gl.vertexAttribPointer( 52 | prop["location"], // index 53 | prop["size"], // size 54 | prop["type"], // GLenum type 55 | false, // normalized 56 | prop["stride"], // GLsizei stride 57 | prop["offset"] // GLintptr offset 58 | ); 59 | gl.enableVertexAttribArray(prop["location"]); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /assets/js/primitives.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class Triangle { 4 | constructor(v1 = null, v2 = null, v3 = null, index = null, normal = null) { 5 | this._v1 = v1; 6 | this._v2 = v2; 7 | this._v3 = v3; 8 | this._index = index; 9 | this._normal = normal; 10 | } 11 | get v1() { 12 | if (this._v1 === null) { 13 | throw "first vertex is null"; 14 | } 15 | return this._v1; 16 | } 17 | get v2() { 18 | if (this._v2 === null) { 19 | throw "second vertex is null"; 20 | } 21 | return this._v2; 22 | } 23 | get v3() { 24 | if (this._v3 === null) { 25 | throw "third vertex is null"; 26 | } 27 | return this._v3; 28 | } 29 | get index() { 30 | if (this._index === null) { 31 | throw "index is null"; 32 | } 33 | return this._index; 34 | } 35 | get face() { 36 | return this.index; 37 | } 38 | set normal(s) { 39 | if (!Array.isArray(s)) { 40 | throw "must be an array with 3 elements or vec3"; 41 | } 42 | this._normal = s; 43 | } 44 | get normal() { 45 | if (this._normal === null) { 46 | let edges = get_edges(); 47 | if (this.v1._normal === null) { 48 | this.v1.set_normal_from_triangle_edges(edges); 49 | } 50 | this.normal = this.v1.normal; 51 | this.v2._normal = this.v1.normal; 52 | this.v3._normal = this.v1.normal; 53 | } 54 | return this._normal; 55 | } 56 | get_edges() { 57 | let e2 = glMatrix.vec3.create(); 58 | glMatrix.vec3.subtract(e2, this.v2.pos, this.v1.pos); 59 | 60 | let e1 = glMatrix.vec3.create(); 61 | glMatrix.vec3.subtract(e1, this.v3.pos, this.v1.pos); 62 | 63 | let e3 = glMatrix.vec3.create(); 64 | glMatrix.vec3.subtract(e3, this.v3.pos, this.v2.pos); 65 | 66 | return [e1, e2, e3]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /assets/js/glmanager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // error check code 4 | 5 | 6 | class GLManager { 7 | constructor(canvas_id) { 8 | let cnvs = document.getElementById(canvas_id); 9 | let mgl = cnvs.getContext("webgl2") || cnvs.getContext("webgl"); 10 | if (mgl === null) { 11 | alert("Browser does not support webgl!"); 12 | } 13 | this.mGL = mgl; 14 | this.clear_canvas(glMatrix.vec4.fromValues(0, 0, 0, 1)); 15 | } 16 | get gl() { 17 | if (this.mGL === null) { 18 | throw "webgl instance is null"; 19 | } 20 | return this.mGL 21 | } 22 | clear_canvas(color) { 23 | this.gl.clearColor(color[0], color[1], color[2], color[3]); 24 | this.gl.clear(this.gl.COLOR_BUFFER_BIT); 25 | } 26 | } 27 | 28 | 29 | function check_error(gl) { 30 | let err = gl.getError(); 31 | if (err === gl.NO_ERROR) { 32 | return true; 33 | } else if (err === gl.INVALID_ENUM) { 34 | let mess = "An unacceptable value has been specified for an enumerated"; 35 | mess += " argument. The command is ignored and the error flag is set."; 36 | throw "INVALID_ENUM :: " + mess; 37 | } else if (err === gl.INVALID_VALUE) { 38 | let mess = "A numeric argument is out of range."; 39 | mess += " The command is ignored and the error flag is set."; 40 | throw "INVALID_VALUE :: " + mess; 41 | } else if (err === gl.INVALID_OPERATION) { 42 | let mess = "The specified command is not allowed for the current"; 43 | mess += " state. The command is ignored and the error flag is set."; 44 | throw "INVALID_OPERATION :: " + mess; 45 | } else if (err === gl.INVALID_FRAMEBUFFER_OPERATION) { 46 | let mess = "The currently bound framebuffer is not framebuffer complete"; 47 | mess += " when trying to render to or to read from it."; 48 | throw "INVALID_FRAMEBUFFER_OPERATION :: " + mess; 49 | } else if (err === gl.OUT_OF_MEMORY) { 50 | let mess = "Not enough memory is left to execute the command."; 51 | throw "OUT_OF_MEMORY :: " + mess; 52 | } else if (err === gl.CONTEXT_LOST_WEBGL) { 53 | let mess = "If the WebGL context is lost, this error is returned on "; 54 | mess += "the first call to getError. Afterwards and until the context"; 55 | mess += " has been restored, it returns gl.NO_ERROR."; 56 | throw "CONTEXT_LOST_WEBGL :: " + mess; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /assets/js/jsutils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // utils 3 | function isIterable(val) { 4 | return Symbol.iterator in Object(val); 5 | } 6 | 7 | function check_type(val, expected_type) { 8 | if (expected_type === "iterable") { 9 | if (!isIterable(val)) { 10 | let vstr = val.toString(); 11 | throw "argument " + vstr + " must be of type " + expected_type; 12 | } 13 | } else if (typeof val !== expected_type) { 14 | let vstr = val.toString(); 15 | throw "argument " + vstr + " must be of type " + expected_type; 16 | } 17 | return true; 18 | } 19 | 20 | 21 | function first_token_fn(str, token, boolfn, pos = null) { 22 | let ival = 0; 23 | if (pos !== null) { 24 | ival = pos; 25 | } 26 | for (var index = ival; index < str.length; index++) { 27 | if (boolfn(str[index], token)) { 28 | return index; 29 | } 30 | } 31 | return -1; 32 | 33 | } 34 | 35 | function last_token_fn(str, token, boolfn, pos = null) { 36 | var last = -1; 37 | let ival = 0; 38 | if (pos !== null) { 39 | ival = pos; 40 | } 41 | for (var index = ival; index < str.length; index++) { 42 | 43 | if (boolfn(str[key], token)) { 44 | last = key; 45 | } 46 | } 47 | return last; 48 | } 49 | 50 | function first_not_of(str, token, pos = null) { 51 | return first_token_fn(str, token, (a, b) => { 52 | return a !== b; 53 | }, pos); 54 | } 55 | 56 | function first_of(str, token, pos = null) { 57 | return first_token_fn(str, token, (a, b) => { 58 | return a === b; 59 | }, pos); 60 | } 61 | 62 | function last_not_of(str, token, pos = null) { 63 | return last_token_fn(str, token, (a, b) => { 64 | return a !== b; 65 | }, pos); 66 | } 67 | 68 | function last_of(str, token, pos = null) { 69 | return last_token_fn(str, token, (a, b) => { 70 | return a === b; 71 | }, pos); 72 | } 73 | 74 | function tail_str(str) { 75 | let token_start = first_not_of(str, " \t"); 76 | let space_start = first_of(str, " \t", token_start); 77 | let tail_start = first_not_of(str, " \t", space_start); 78 | let tail_end = last_not_of(str, " \t"); 79 | if (tail_start !== -1 && tail_end !== -1) { 80 | return str.substring(tail_start, tail_end - tail_start + 1); 81 | } else if (tail_start !== -1) { 82 | return str.substring(tail_start); 83 | } 84 | return ""; 85 | } 86 | 87 | function firstToken(str) { 88 | if (str !== "") { 89 | let token_start = first_not_of(str, " \t"); 90 | let token_end = first_of(str, " \t", token_start); 91 | if (token_start != -1 && token_end != -1) { 92 | return str.substring(token_start, token_end - token_start); 93 | } else if (token_start != -1) { 94 | return str.substring(token_start); 95 | } 96 | } 97 | return ""; 98 | } 99 | -------------------------------------------------------------------------------- /assets/js/camera.js: -------------------------------------------------------------------------------- 1 | // camera object 2 | "use strict"; 3 | 4 | const CAMERA_MOVEMENT = { 5 | FORWARD: "FORWARD", 6 | BACKWARD: "BACKWARD", 7 | LEFT: "LEFT", 8 | RIGHT: "RIGHT" 9 | }; 10 | 11 | // default values for the camera 12 | const M_YAW = -90.0; 13 | const M_PITCH = 0.0; 14 | const M_SPEED = 2.5; 15 | const M_SENSITIVITY = 0.00001; 16 | const M_ZOOM = 45.0; 17 | 18 | class Camera { 19 | constructor(pos = glMatrix.vec3.fromValues(0.0, 0.0, 0.0), 20 | up = glMatrix.vec3.fromValues(0.0, 1.0, 0.0), 21 | yaw = M_YAW, pitch = M_PITCH, zoom = M_ZOOM, 22 | front = glMatrix.vec3.fromValues(0.0, 0.0, -1.0), 23 | speed = M_SPEED, sens = M_SENSITIVITY 24 | ) { 25 | this.pos = pos; 26 | this.front = front; 27 | this.worldUp = up; 28 | // euler angles 29 | this.yaw = yaw; 30 | this.pitch = pitch; 31 | 32 | // camera options 33 | this.movementSpeed = speed; 34 | this.mouseSensitivity = sens; 35 | this.zoom = zoom; 36 | this.update_camera_vectors(); 37 | } 38 | set_camera(posX, posY, posZ, upX, upY, upZ, 39 | yaw, pitch, front = glMatrix.vec3.fromValues(0.0, 0.0, -1.0), 40 | speed = M_SPEED, sens = M_SENSITIVITY, zoom = M_ZOOM) { 41 | this.pos = glMatrix.vec3.fromValues(posX, posY, posZ); 42 | this.worldUp = glMatrix.vec3.fromValues(upX, upY, upZ); 43 | this.yaw = yaw; 44 | this.pitch = pitch; 45 | this.movementSpeed = speed; 46 | this.mouseSensitivity = sens; 47 | this.zoom = zoom; 48 | this.update_camera_vectors(); 49 | } 50 | update_camera_vectors() { 51 | let front = glMatrix.vec3.create(); 52 | let rad_yaw = glMatrix.glMatrix.toRadian(this.yaw); 53 | let rad_pitch = glMatrix.glMatrix.toRadian(this.pitch); 54 | front[0] = Math.cos(rad_yaw) * Math.cos(rad_pitch); 55 | front[1] = Math.sin(rad_pitch); 56 | front[2] = Math.sin(rad_yaw) * Math.cos(rad_pitch); 57 | glMatrix.vec3.normalize(this.front, front); 58 | // 59 | // compute right 60 | let right = glMatrix.vec3.create(); 61 | glMatrix.vec3.cross(right, this.front, this.worldUp); 62 | this.right = glMatrix.vec3.create(); 63 | glMatrix.vec3.normalize(this.right, right); 64 | // 65 | // compute up 66 | let up = glMatrix.vec3.create(); 67 | glMatrix.vec3.cross(up, this.right, this.front); 68 | this.up = glMatrix.vec3.create(); 69 | glMatrix.vec3.normalize(this.up, up); 70 | } 71 | process_keyboard(movement, deltaTime) { 72 | let velocity = this.movementSpeed * deltaTime; 73 | let fvel = glMatrix.vec3.create(); 74 | let pos = glMatrix.vec3.create(); 75 | if (movement === CAMERA_MOVEMENT["FORWARD"]) { 76 | glMatrix.vec3.scale(fvel, this.front, velocity); 77 | console.log(fvel); 78 | glMatrix.vec3.add(pos, this.pos, fvel); 79 | this.pos = pos; 80 | } else if (movement === CAMERA_MOVEMENT["BACKWARD"]) { 81 | glMatrix.vec3.scale(fvel, this.front, velocity); 82 | glMatrix.vec3.subtract(pos, this.pos, fvel); 83 | this.pos = pos; 84 | } else if (movement === CAMERA_MOVEMENT["RIGHT"]) { 85 | glMatrix.vec3.scale(fvel, this.right, velocity); 86 | glMatrix.vec3.add(pos, this.pos, fvel); 87 | this.pos = pos; 88 | } else if (movement === CAMERA_MOVEMENT["LEFT"]) { 89 | glMatrix.vec3.scale(fvel, this.right, velocity); 90 | glMatrix.vec3.subtract(pos, this.pos, fvel); 91 | this.pos = pos; 92 | } 93 | } 94 | get_view_matrix() { 95 | let cpos = this.pos; 96 | let cfront = this.front; 97 | let cup = this.up; 98 | let front_pos = glMatrix.vec3.create(); 99 | glMatrix.vec3.add(front_pos, cpos, cfront); 100 | let view = glMatrix.mat4.create(); 101 | glMatrix.mat4.lookAt(view, cpos, front_pos, cup); 102 | return view; 103 | } 104 | 105 | process_keyboard_rotate(direction, deltaTime) { 106 | 107 | deltaTime *= this.movementSpeed; 108 | if (direction === CAMERA_MOVEMENT["FORWARD"]) { 109 | this.pitch += deltaTime; 110 | } else if (direction === CAMERA_MOVEMENT["BACKWARD"]) { 111 | this.pitch -= deltaTime; 112 | } else if (direction === CAMERA_MOVEMENT["RIGHT"]) { 113 | this.yaw += deltaTime; 114 | } else if (direction === CAMERA_MOVEMENT["LEFT"]) { 115 | this.yaw -= deltaTime; 116 | } 117 | this.update_camera_vectors(); 118 | } 119 | process_mouse_scroll(yoffset) { 120 | let zoom = this.zoom; 121 | 122 | if (this.zoom >= 1.0 && this.zoom <= 45.0) { 123 | this.zoom -= yoffset; 124 | } 125 | if (this.zoom <= 1.0) { 126 | this.zoom = 1.0; 127 | } 128 | if (this.zoom >= 45.0) { 129 | this.zoom = 45.0; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /assets/js/light.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // light implementation 3 | 4 | const LYAW = -90.0; 5 | const LPITCH = 0.0; 6 | const LSPEED = 2.5; 7 | 8 | // 9 | 10 | const LIGHT_MOVEMENT = { 11 | L_FORWARD: "L_FORWARD", 12 | L_BACKWARD: "L_BACKWARD", 13 | L_LEFT: "L_LEFT", 14 | L_RIGHT: "L_RIGHT" 15 | }; 16 | 17 | class Light { 18 | constructor(lightColor) { 19 | this.emitColor = lightColor; 20 | } 21 | emitted(emits) { 22 | emits = this.emitColor; 23 | return true; 24 | } 25 | } 26 | 27 | class DirectionalLight extends Light { 28 | constructor(lightColor, wup, y = LYAW, p = LPITCH) { 29 | super(lightColor); 30 | this.yaw = y; 31 | this.pitch = p; 32 | this.worldUp = wup; 33 | this.front = null; 34 | this.right = null; 35 | this.up = null; 36 | this.updateDirection(); 37 | } 38 | 39 | setYaw(val) { 40 | this.yaw = val; 41 | this.updateDirection(); 42 | } 43 | setPitch(val) { 44 | this.pitch = val; 45 | this.updateDirection(); 46 | } 47 | updateDirection() { 48 | // 49 | let front = glMatrix.vec3.create(); 50 | let rad_yaw = glMatrix.toRadian(this.yaw); 51 | let rad_pitch = glMatrix.toRadian(this.pitch); 52 | front.x = Math.cos(rad_yaw) * Math.cos(rad_pitch); 53 | front.y = Math.sin(rad_pitch); 54 | front.z = Math.sin(rad_yaw) * Math.cos(rad_pitch); 55 | glMatrix.vec3.normalize(this.front, front); 56 | // 57 | // compute right 58 | let right = glMatrix.vec3.create(); 59 | glMatrix.vec3.normalize(this.right, 60 | glMatrix.vec3.cross(right, this.front, this.worldUp)); 61 | // 62 | // compute up 63 | let up = glMatrix.vec3.create(); 64 | glMatrix.vec3.normalize(this.up, 65 | glMatrix.vec3.cross(up, this.right, this.front)); 66 | } 67 | processKeyBoardRotate(direction, deltaTime) { 68 | 69 | deltaTime *= this.movementSpeed; 70 | if (direction === LIGHT_MOVEMENT["L_FORWARD"]) { 71 | this.pitch += deltaTime; 72 | } else if (direction === LIGHT_MOVEMENT["L_BACKWARD"]) { 73 | this.pitch -= deltaTime; 74 | } else if (direction === LIGHT_MOVEMENT["L_RIGHT"]) { 75 | this.yaw += deltaTime; 76 | } else if (direction === LIGHT_MOVEMENT["L_LEFT"]) { 77 | this.yaw -= deltaTime; 78 | } 79 | this.updateDirection(); 80 | } 81 | } 82 | 83 | class PointLight extends Light { 84 | constructor(lightColor, pos) { 85 | super(lightColor); 86 | this.pos = pos; 87 | } 88 | 89 | } 90 | 91 | class SpotLight { 92 | constructor(lightColor, pos, wup, y = LYAW, 93 | p = LPITCH, cutOffAngleDegree = 0.91, 94 | outerCut = 0.82, mspeed = LSPEED) { 95 | // 96 | this.cutOff = glMatrix.toRadian(cutOffAngleDegree); 97 | this.outerCutoff = outerCut; 98 | this.dirLight = new DirectionalLight(lightColor, wup, y, p); 99 | this.pointLight = new PointLight(lightColor, pos); 100 | this.movementSpeed = mspeed; 101 | } 102 | get_view_matrix() { 103 | let target = this.pointLight.pos + this.dirLight.front; 104 | let upvec = this.dirLight.up; 105 | let cameraDirection = glMatrix.vec3.create(); 106 | glMatrix.vec3.normalize(cameraDirection, this.pointLight.pos - target); 107 | let upv = glMatrix.vec3.create(); 108 | let right = glMatrix.vec3.create(); 109 | // 110 | glMatrix.vec3.normalize(right, 111 | glMatrix.vec3.cross(upv, upvec, cameraDirection) 112 | ); 113 | let realUp = glMatrix.vec3.create(); 114 | glMatrix.vec3.normalize(realUp, 115 | glMatrix.vec3.cross(glMatrix.vec3.create(), cameraDirection, right) 116 | ); 117 | // 118 | let trans = glMatrix.mat4.create(1.0); 119 | trans[3][0] = -this.pointLight.pos.x; 120 | trans[3][1] = -this.pointLight.pos.y; 121 | trans[3][2] = -this.pointLight.pos.z; 122 | 123 | // 124 | let rotation = glMatrix.mat4.create(1.0); 125 | rotation[0][0] = right.x; 126 | rotation[1][0] = right.y; 127 | rotation[2][0] = right.z; 128 | rotation[0][1] = realUp.x; 129 | rotation[1][1] = realUp.y; 130 | rotation[2][1] = realUp.z; 131 | rotation[0][2] = cameraDirection.x; 132 | rotation[1][2] = cameraDirection.y; 133 | rotation[2][2] = cameraDirection.z; 134 | return rotation * trans; 135 | } 136 | processKeyBoardRotate(direction, deltaTime) { 137 | this.dirLight.processKeyBoardRotate(direction, deltaTime); 138 | } 139 | process_keyboard(movement, deltaTime) { 140 | let velocity = this.movementSpeed * deltaTime; 141 | if (movement === LIGHT_MOVEMENT["L_FORWARD"]) { 142 | this.pointLight.pos += this.dirLight.front * velocity; 143 | } else if (movement === LIGHT_MOVEMENT["L_BACKWARD"]) { 144 | this.pointLight.pos -= this.dirLight.front * velocity; 145 | } else if (movement === LIGHT_MOVEMENT["L_RIGHT"]) { 146 | this.pointLight.pos += this.dirLight.right * velocity; 147 | } else if (movement === LIGHT_MOVEMENT["L_LEFT"]) { 148 | this.pointLight.pos -= this.dirLight.right * velocity; 149 | } 150 | } 151 | }; 152 | -------------------------------------------------------------------------------- /assets/js/objects.js: -------------------------------------------------------------------------------- 1 | // objects in ndc coordinates 2 | var cube_vertex_normal_texture = { 3 | "data": new Float32Array([ 4 | // back face 5 | -1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // bottom-let 6 | 1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0, // top-right 7 | 1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 0.0, // bottom-right 8 | 1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0, // top-right 9 | -1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // bottom-let 10 | -1.0, 1.0, -1.0, 0.0, 0.0, -1.0, 0.0, 1.0, // top-let 11 | // front face 12 | -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom-let 13 | 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, // bottom-right 14 | 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, // top-right 15 | 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, // top-right 16 | -1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, // top-let 17 | -1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom-let 18 | // left face 19 | -1.0, 1.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0, // top-right 20 | -1.0, 1.0, -1.0, -1.0, 0.0, 0.0, 1.0, 1.0, // top-let 21 | -1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 1.0, // bottom-let 22 | -1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 1.0, // bottom-let 23 | -1.0, -1.0, 1.0, -1.0, 0.0, 0.0, 0.0, 0.0, // bottom-right 24 | -1.0, 1.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0, // top-right 25 | // right face 26 | 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, // top-let 27 | 1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, 1.0, // bottom-right 28 | 1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, // top-right 29 | 1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, 1.0, // bottom-right 30 | 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, // top-let 31 | 1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, // bottom-let 32 | // bottom face 33 | -1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 0.0, 1.0, // top-right 34 | 1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 1.0, 1.0, // top-let 35 | 1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, // bottom-let 36 | 1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, // bottom-let 37 | -1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, // bottom-right 38 | -1.0, -1.0, -1.0, 0.0, -1.0, 0.0, 0.0, 1.0, // top-right 39 | // top face 40 | -1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0, // top-let 41 | 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom-right 42 | 1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 1.0, // top-right 43 | 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom-right 44 | -1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0, // top-let 45 | -1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0 // bottom-let 46 | ]), 47 | "nb_triangles": 0, 48 | "vertices": [], 49 | "indices": [] 50 | }; 51 | cube_vertex_normal_texture["vertices"] = 52 | Vertex.from_arrays(cube_vertex_normal_texture["data"]); 53 | 54 | cube_vertex_normal_texture["nb_triangles"] = 55 | cube_vertex_normal_texture["vertices"].length / 3; 56 | for (var index = 0; index < cube_vertex_normal_texture["nb_triangles"]; index++) { 57 | cube_vertex_normal_texture["indices"].push(index); 58 | } 59 | 60 | // 61 | var cube_vertex_texture_normal = { 62 | "data": new Float32Array([ 63 | // back face 64 | -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, -1.0, // bottom-let 65 | 1.0, 1.0, -1.0, 1.0, 1.0, 0.0, 0.0, -1.0, // top-right 66 | 1.0, -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, -1.0, // bottom-right 67 | 1.0, 1.0, -1.0, 1.0, 1.0, 0.0, 0.0, -1.0, // top-right 68 | -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, -1.0, // bottom-let 69 | -1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 0.0, -1.0, // top-let 70 | // front face 71 | -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, // bottom-let 72 | 1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, // bottom-right 73 | 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, // top-right 74 | 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, // top-right 75 | -1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, // top-let 76 | -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, // bottom-let 77 | // left face 78 | -1.0, 1.0, 1.0, 1.0, 0.0, -1.0, 0.0, 0.0, // top-right 79 | -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 0.0, 0.0, // top-let 80 | -1.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, // bottom-let 81 | -1.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, // bottom-let 82 | -1.0, -1.0, 1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // bottom-right 83 | -1.0, 1.0, 1.0, 1.0, 0.0, -1.0, 0.0, 0.0, // top-right 84 | // right face 85 | 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, // top-let 86 | 1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0, // bottom-right 87 | 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 0.0, 0.0, // top-right 88 | 1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0, // bottom-right 89 | 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, // top-let 90 | 1.0, -1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom-let 91 | // bottom face 92 | -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, -1.0, 0.0, // top-right 93 | 1.0, -1.0, -1.0, 1.0, 1.0, 0.0, -1.0, 0.0, // top-let 94 | 1.0, -1.0, 1.0, 1.0, 0.0, 0.0, -1.0, 0.0, // bottom-let 95 | 1.0, -1.0, 1.0, 1.0, 0.0, 0.0, -1.0, 0.0, // bottom-let 96 | -1.0, -1.0, 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, // bottom-right 97 | -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, -1.0, 0.0, // top-right 98 | // top face 99 | -1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, // top-let 100 | 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, // bottom-right 101 | 1.0, 1.0, -1.0, 1.0, 1.0, 0.0, 1.0, 0.0, // top-right 102 | 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, // bottom-right 103 | -1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, // top-let 104 | -1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0 // bottom-let 105 | ]), 106 | "nb_triangles": 0, 107 | "vertices": [], 108 | "indices": [] 109 | }; 110 | cube_vertex_texture_normal["vertices"] = 111 | Vertex.from_arrays(cube_vertex_texture_normal["data"]); 112 | 113 | cube_vertex_texture_normal["nb_triangles"] = 114 | cube_vertex_texture_normal["vertices"].length / 3; 115 | for (var index = 0; index < cube_vertex_texture_normal["nb_triangles"]; index++) { 116 | cube_vertex_texture_normal["indices"].push(index); 117 | } 118 | -------------------------------------------------------------------------------- /assets/js/vertex.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // vertex 3 | 4 | class Vertex { 5 | /** 6 | 7 | Vertex for in a 3d plane 8 | 9 | @param {vec3} pos - position of vertex 10 | @param {vec3} normal - normal of vertex 11 | @param {vec2} texcoord - texture coordinate of vertex 12 | @param {vec3} tan - tangent coordinate of vertex 13 | @param {vec3} bitan - bitangent coordinate of vertex 14 | */ 15 | constructor(pos = null, normal = null, texcoord = null, tan = null, bitan = null) { 16 | this._position = pos; 17 | this._normal = normal; 18 | this._texcoord = texcoord; 19 | this._tangent = tan; 20 | this._bitangent = bitan; 21 | } 22 | get pos() { 23 | if (this._position === null) { 24 | throw "position is null"; 25 | } 26 | return this._position; 27 | } 28 | get normal() { 29 | if (this._normal === null) { 30 | throw "normal is null"; 31 | } 32 | return this._normal; 33 | } 34 | get uv() { 35 | if (this._texcoord === null) { 36 | throw "texture coordinate is null"; 37 | } 38 | return this._texcoord; 39 | } 40 | get tan() { 41 | if (this._tangent === null) { 42 | throw "tangent coordinate is null"; 43 | } 44 | return this._tangent; 45 | } 46 | get bitan() { 47 | if (this._bitangent === null) { 48 | throw "bitangent coordinate is null"; 49 | } 50 | return this._bitangent; 51 | } 52 | /** 53 | to quad array, first position, and to uv (texture coordinate) 54 | 55 | @return {Float32Array} an array with 5 values 56 | */ 57 | to_quad() { 58 | return new Float32Array([ 59 | this.pos[0], 60 | this.pos[1], 61 | this.pos[2], 62 | this.uv[0], 63 | this.uv[1] 64 | ]); 65 | } 66 | /** 67 | * to quad array, first position, and to uv (texture coordinate) 68 | * 69 | * @return {Float32Array} an array with 8 values 70 | */ 71 | to_array() { 72 | let quad = this.to_quad(); 73 | let quads = []; 74 | for (const key in quad) { 75 | quads.push(quad[key]); 76 | } 77 | quads.push(this.normal[0]); 78 | quads.push(this.normal[1]); 79 | quads.push(this.normal[2]); 80 | return new Float32Array(quads); 81 | } 82 | /** 83 | * to quad array, first position, and to uv (texture coordinate) 84 | * 85 | * @return {Float32Array} an array with 14 values 86 | */ 87 | to_primitive() { 88 | let arr = this.to_array(); 89 | let lst = []; 90 | for (const key in arr) { 91 | lst.push(arr[key]); 92 | } 93 | 94 | lst.push(this.tan[0]); 95 | lst.push(this.tan[1]); 96 | lst.push(this.tan[2]); 97 | lst.push(this.bitan[0]); 98 | lst.push(this.bitan[1]); 99 | lst.push(this.bitan[2]); 100 | return new Float32Array(lst); 101 | } 102 | size() { 103 | let count = 0; 104 | let p = this._position; 105 | let n = this._normal; 106 | let t = this._texcoord; 107 | let tt = this._tangent; 108 | let bt = this._bitangent; 109 | if (p !== null) { 110 | count += 3; 111 | } 112 | if (n !== null) { 113 | count += 3; 114 | } 115 | if (t !== null) { 116 | count += 2; 117 | } 118 | if (tt !== null) { 119 | count += 3; 120 | } 121 | if (bt !== null) { 122 | count += 3; 123 | } 124 | return count; 125 | } 126 | to_list() { 127 | let s = this.size(); 128 | if (s === 5) { 129 | return this.to_quad(); 130 | } else if (s === 8) { 131 | return this.to_array(); 132 | } else if (s === 14) { 133 | return this.to_primitive(); 134 | } else { 135 | throw "unsupported size"; 136 | } 137 | } 138 | set_normal_from_triangle_edges(edges) { 139 | let edge1 = edges[0]; 140 | let edge2 = edges[1]; 141 | let n = glMatrix.vec3.create(); 142 | glMatrix.vec3.cross(n, edge1, edge2); 143 | let n1 = glMatrix.vec3.create(); 144 | glMatrix.vec3.normalize(n1, n); 145 | this._normal = n1; 146 | } 147 | set_tangent_values_from_triangle(tri) { 148 | let v1 = tri.v1; 149 | let v2 = tri.v2; 150 | let v3 = tri.v3; 151 | let edges = tri.get_edges(); 152 | let edge1 = edges[0]; 153 | let edge2 = edges[1]; 154 | 155 | let deltaUV1 = glMatrix.vec3.create(); 156 | glMatrix.vec3.subtract(deltaUV1, v2.uv, v1.uv); 157 | 158 | let deltaUV2 = glMatrix.vec3.create(); 159 | glMatrix.vec3.subtract(deltaUV2, v3.uv, v1.uv); 160 | 161 | let f = 1.0 / (deltaUV1[0] * deltaUV2[1] - deltaUV2[0] * deltaUV1[1]); 162 | 163 | let tangent1 = glMatrix.vec3.create(); 164 | let tangent2 = glMatrix.vec3.create(); 165 | 166 | tangent1[0] = f * (deltaUV2[1] * edge1[0] - deltaUV1[1] * edge2[0]); 167 | tangent1[1] = f * (deltaUV2[1] * edge1[1] - deltaUV1[1] * edge2[1]); 168 | tangent1[2] = f * (deltaUV2[1] * edge1[2] - deltaUV1[1] * edge2[2]); 169 | 170 | bitangent1[0] = f * (-deltaUV2[0] * edge1[0] + deltaUV1[0] * edge2[0]); 171 | bitangent1[1] = f * (-deltaUV2[0] * edge1[1] + deltaUV1[0] * edge2[1]); 172 | bitangent1[2] = f * (-deltaUV2[0] * edge1[2] + deltaUV1[0] * edge2[2]); 173 | this._tangent = tangent1; 174 | this._bitangent = bitangent1; 175 | } 176 | static from_quad(arr) { 177 | let v = new Vertex(); 178 | v._position = glMatrix.vec3.create(); 179 | v._texcoord = glMatrix.vec2.create(); 180 | v.pos[0] = arr[0]; 181 | v.pos[1] = arr[1]; 182 | v.pos[2] = arr[2]; 183 | v.uv[0] = arr[3]; 184 | v.uv[1] = arr[4]; 185 | return v; 186 | } 187 | static from_array(arr) { 188 | let v = Vertex.from_quad(arr); 189 | v._normal = glMatrix.vec3.create(); 190 | v.normal[0] = arr[5]; 191 | v.normal[1] = arr[6]; 192 | v.normal[2] = arr[7]; 193 | return v; 194 | } 195 | static from_primitive(arr) { 196 | let v = Vertex.from_array(arr); 197 | v._tangent = glMatrix.vec3.create(); 198 | v._bitangent = glMatrix.vec3.create(); 199 | v.tan[0] = arr[8]; 200 | v.tan[1] = arr[9]; 201 | v.tan[2] = arr[10]; 202 | v.bitan[0] = arr[11]; 203 | v.bitan[1] = arr[12]; 204 | v.bitan[2] = arr[13]; 205 | return v; 206 | } 207 | static is_arr_fit(arr, expected_nb_components) { 208 | if (!isIterable(arr)) { 209 | console.log(arr); 210 | console.log(typeof(arr)); 211 | throw "argument is not an iterable"; 212 | } 213 | let cond = arr.length % expected_nb_components === 0; 214 | if (!cond) { 215 | let m = expected_nb_components.toString(); 216 | throw "length of array must be a multiple of " + m; 217 | } 218 | 219 | } 220 | static from_values(arr, expected_nb_components, vertex_fn) { 221 | let vs = []; 222 | Vertex.is_arr_fit(arr, expected_nb_components); 223 | 224 | for (var i = 0; i < arr.length; i += expected_nb_components) { 225 | let values = []; 226 | for (var j = 0; j < expected_nb_components; j++) { 227 | let val = arr[i + j]; 228 | values.push(val); 229 | } 230 | let vtx = vertex_fn(values); 231 | vs.push(vtx); 232 | } 233 | return vs; 234 | } 235 | static from_quads(arr) { 236 | return Vertex.from_values( 237 | arr, 5, Vertex.from_quad 238 | ); 239 | } 240 | static from_arrays(arr) { 241 | return Vertex.from_values( 242 | arr, 8, Vertex.from_array 243 | ); 244 | } 245 | static from_primitives(arr) { 246 | return Vertex.from_values( 247 | arr, 14, Vertex.from_primitive 248 | ); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /assets/js/shader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // simple shader class 4 | 5 | // check the shader compilation 6 | function checkShaderCompilation(gl, shader) { 7 | let gbool = gl.getShaderParameter(shader, gl.COMPILE_STATUS); 8 | if (!gbool) { 9 | let mess = gl.getShaderInfoLog(shader); 10 | throw mess; 11 | } 12 | return gbool; 13 | } 14 | 15 | function checkShaderProgramCompilation(gl, program) { 16 | // check the program compilation 17 | let pstat = gl.getProgramParameter(program, gl.LINK_STATUS); 18 | if (!pstat) { 19 | let mess = gl.getProgramInfoLog(program); 20 | throw mess; 21 | } 22 | return pstat; 23 | } 24 | 25 | function getUniformLocation(gl, program, name) { 26 | let locVal = gl.getUniformLocation(program, name); 27 | if (locVal === null) { 28 | throw "no location is given for the uniform in the program"; 29 | } 30 | check_error(gl); 31 | return locVal; 32 | } 33 | 34 | class Shader { 35 | constructor(vs_id, fs_id) { 36 | // shader source [{"text": "", "type": ""}] 37 | this._shader_sources = null; 38 | this._shader_id = null; 39 | this.init_shader_sources_vs_fs(vs_id, fs_id); 40 | } 41 | init_shader_sources(arr) { 42 | for (const key in arr) { 43 | let source_id_type = arr[key]; 44 | let text = 45 | document.getElementById(source_id_type["id"]).text.trim(); 46 | let stype = source_id_type["type"]; 47 | this.set_shader_source(text, stype); 48 | } 49 | } 50 | init_shader_sources_vs_fs(vs_id, fs_id) { 51 | let arr = [{ 52 | "type": "FRAGMENT_SHADER", 53 | "id": fs_id 54 | }, { 55 | "type": "VERTEX_SHADER", 56 | "id": vs_id 57 | }]; 58 | this.init_shader_sources(arr); 59 | } 60 | get shader_sources() { 61 | if (this._shader_sources === null) { 62 | throw "shader sources are null"; 63 | } 64 | return this._shader_sources; 65 | } 66 | get_shader_source_by_type(stype) { 67 | for (const key in this.shader_sources) { 68 | let source = this.shader_sources[key]; 69 | if (source["type"] === stype) { 70 | return source["text"]; 71 | } 72 | } 73 | return null; 74 | } 75 | set_shader_source(stext, stype) { 76 | if (this._shader_sources === null) { 77 | this._shader_sources = []; 78 | } 79 | let st = this.get_shader_source_by_type(stype); 80 | if (st === null) { 81 | this.shader_sources.push({ 82 | "text": stext, 83 | "type": stype 84 | }); 85 | } else { 86 | throw "same type shader already exists among shader source"; 87 | } 88 | } 89 | get vs_text() { 90 | return this.get_shader_source_by_type("VERTEX_SHADER"); 91 | } 92 | get fs_text() { 93 | return this.get_shader_source_by_type("FRAGMENT_SHADER"); 94 | } 95 | get program_id() { 96 | if (this._shader_id === null) { 97 | throw "shader id is null"; 98 | } 99 | return this._shader_id; 100 | } 101 | load_shader(gl, shader_type, shader_txt) { 102 | check_error(gl); 103 | let compiled_shader = gl.createShader(shader_type); 104 | gl.shaderSource(compiled_shader, shader_txt); 105 | gl.compileShader(compiled_shader); 106 | checkShaderCompilation(gl, compiled_shader); 107 | check_error(gl); 108 | return compiled_shader; 109 | } 110 | activate(gl) { 111 | gl.useProgram(this.program_id); 112 | } 113 | 114 | link_program(gl) { 115 | this._shader_id = gl.createProgram(); 116 | let shaders = []; 117 | for (const key in this.shader_sources) { 118 | let source = this.shader_sources[key]; 119 | let text = source["text"]; 120 | var stype = null; 121 | if (source["type"] === "FRAGMENT_SHADER") { 122 | stype = gl.FRAGMENT_SHADER; 123 | } else if (source["type"] === "VERTEX_SHADER") { 124 | stype = gl.VERTEX_SHADER; 125 | } else { 126 | throw "unsupported shader type " + source["type"]; 127 | } 128 | let shader = this.load_shader(gl, stype, text); 129 | shaders.push(shader); 130 | } 131 | for (const key in shaders) { 132 | gl.attachShader(this.program_id, shaders[key]); 133 | } 134 | gl.linkProgram(this.program_id); 135 | checkShaderProgramCompilation(gl, this.program_id); 136 | } 137 | check_type(val, expected_type) { 138 | check_type(val, expected_type); 139 | } 140 | check_components(val, expected_nb_components) { 141 | let nc = expected_nb_components; 142 | if (typeof(val) === "object") { 143 | if (val.length === nc) { 144 | return true; 145 | } else { 146 | let mess = "expected number of components is " + nc.toString(); 147 | mess += " but you had provided an object of length: "; 148 | mess += val.length.toString(); 149 | throw mess; 150 | } 151 | } else { 152 | let m = "object " + val.toString() + " is not an array"; 153 | m += " type: " + typeof(val); 154 | m += " with length " + val.length.toString(); 155 | m += " number of components: " + 156 | expected_nb_components.toString(); 157 | throw m; 158 | } 159 | } 160 | set_int_uniform(gl, name, value) { 161 | this.check_type(value, "number") 162 | let uloc = getUniformLocation(gl, this.program_id, name); 163 | gl.uniform1i(uloc, value); 164 | } 165 | set_bool_uniform(gl, name, value) { 166 | this.check_type(value, "boolean") 167 | let uloc = getUniformLocation(gl, this.program_id, name); 168 | gl.uniform1i(uloc, Number(value)); 169 | } 170 | set_float_uniform(gl, name, value) { 171 | this.check_type(value, "number") 172 | let uloc = getUniformLocation(gl, this.program_id, name); 173 | gl.uniform1f(uloc, parseFloat(value)); 174 | } 175 | set_vec2_uniform(gl, name, value) { 176 | this.check_components(value, 2) 177 | let uloc = getUniformLocation(gl, this.program_id, name); 178 | gl.uniform2f(uloc, value[0], value[1]); 179 | } 180 | set_vec3_uniform(gl, name, value) { 181 | this.check_components(value, 3) 182 | let uloc = getUniformLocation(gl, this.program_id, name); 183 | gl.uniform3f(uloc, value[0], value[1], value[2]); 184 | } 185 | set_vec4_uniform(gl, name, value) { 186 | this.check_components(value, 4) 187 | let uloc = getUniformLocation(gl, this.program_id, name); 188 | gl.uniform4f(uloc, value[0], value[1], value[2], value[3]); 189 | } 190 | set_mat2_uniform(gl, name, value) { 191 | this.check_components(value, 4) 192 | let uloc = getUniformLocation(gl, this.program_id, name); 193 | gl.uniformMatrix2fv(uloc, false, value); 194 | } 195 | set_mat3_uniform(gl, name, value) { 196 | this.check_components(value, 9) 197 | let uloc = getUniformLocation(gl, this.program_id, name); 198 | gl.uniformMatrix3fv(uloc, false, value); 199 | } 200 | set_mat4_uniform(gl, name, value) { 201 | this.check_components(value, 16) 202 | let uloc = getUniformLocation(gl, this.program_id, name); 203 | gl.uniformMatrix4fv(uloc, false, value); 204 | } 205 | set_uniform(gl, name, udata, utype) { 206 | if (utype === "vec2") { 207 | this.set_vec2_uniform(gl, name, udata); 208 | } else if (utype === "vec3") { 209 | this.set_vec3_uniform(gl, name, udata); 210 | } else if (utype === "vec4") { 211 | this.set_vec4_uniform(gl, name, udata); 212 | } else if (utype === "mat2") { 213 | this.set_mat2_uniform(gl, name, udata); 214 | } else if (utype === "mat3") { 215 | this.set_mat3_uniform(gl, name, udata); 216 | } else if (utype === "mat4") { 217 | this.set_mat4_uniform(gl, name, udata); 218 | } else if (utype === "bool") { 219 | this.set_bool_uniform(gl, name, udata); 220 | } else if (utype === "int") { 221 | this.set_int_uniform(gl, name, udata); 222 | } else if (utype === "float") { 223 | this.set_float_uniform(gl, name, udata); 224 | } else { 225 | throw "unknown uniform type: " + utype; 226 | } 227 | } 228 | } 229 | 230 | class ConstantColorShader extends Shader { 231 | constructor(vs_id, fs_id) { 232 | super(vs_id, fs_id); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /assets/js/mesh.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // mesh object 3 | 4 | class BaseMesh { 5 | constructor(gl, vertices = null, textures = null, indices = null, uniforms = null) { 6 | this._vertices = vertices; 7 | this._textures = textures; 8 | this._indices = indices; 9 | this._vao = null; 10 | this._vbo = null; 11 | this._uniforms = uniforms; 12 | } 13 | set uniforms(u) { 14 | this._uniforms = u; 15 | } 16 | set_uniform_prop(u) { 17 | if (this._uniforms === null) { 18 | this._uniforms = {}; 19 | } 20 | this._uniforms[u["name"]] = u; 21 | } 22 | get uniforms() { 23 | if (this._uniforms === null) { 24 | throw "uniforms for this renderable are null"; 25 | } 26 | return this._uniforms; 27 | } 28 | load_uniforms(gl, shaderProgram) { 29 | for (const uniform_name in this.uniforms) { 30 | // 31 | let udata = this.uniforms[uniform_name].data; 32 | let utype = this.uniforms[uniform_name].type; 33 | shaderProgram.set_uniform(gl, uniform_name, udata, utype); 34 | } 35 | } 36 | get vertices() { 37 | if (this._vertices === null) { 38 | throw "vertces are null"; 39 | } 40 | return this._vertices; 41 | } 42 | get textures() { 43 | if (this._textures === null) { 44 | throw "textures are null"; 45 | } 46 | return this._textures; 47 | } 48 | get indices() { 49 | if (this._indices === null) { 50 | throw "indices are null"; 51 | } 52 | return this._indices; 53 | } 54 | get vbo() { 55 | if (this._vbo === null) { 56 | throw "vertex buffer object null"; 57 | } 58 | return this._vbo; 59 | } 60 | get vao() { 61 | if (this._vao === null) { 62 | throw "vertex array object null"; 63 | } 64 | return this._vao; 65 | } 66 | to_vertex_array() { 67 | let arr = []; 68 | let vsize = this.vertices[0].size(); 69 | for (const index in this.vertices) { 70 | let vertex = this.vertices[index]; 71 | let size = vertex.size(); 72 | if (size !== vsize) { 73 | let mes = "vertex size is not correct for given vertex array"; 74 | mes += " this messes up stride for overall vertex array"; 75 | throw mes; 76 | } 77 | let lst = vertex.to_list(); 78 | for (const i in lst) { 79 | arr.push(lst[i]); 80 | } 81 | } 82 | return new Float32Array(arr); 83 | } 84 | set_vao_values(gl) { 85 | let lst = this.vertices[0].to_list(); 86 | let vsize = lst.byteLength; 87 | if (vsize >= 5) { 88 | // enable position 89 | gl.vertexAttribPointer(0, 3, gl.FLOAT, false, vsize, 0); 90 | gl.enableVertexAttribArray(0); 91 | 92 | let subarr = lst.subarray(0, 3); 93 | // enable uv coordinate 94 | gl.vertexAttribPointer(1, 2, gl.FLOAT, false, vsize, 95 | subarr.byteLength); 96 | gl.enableVertexAttribArray(1); 97 | } 98 | if (vsize >= 8) { 99 | let subarr = lst.subarray(0, 5); 100 | // set normals 101 | gl.vertexAttribPointer(2, 3, gl.FLOAT, false, vsize, 102 | subarr.byteLength); 103 | gl.enableVertexAttribArray(2); 104 | } 105 | if (vsize === 14) { 106 | // set tangent and bitangent 107 | let subarr = lst.subarray(0, 8); 108 | 109 | // set tangent 110 | gl.vertexAttribPointer(3, 3, gl.FLOAT, false, vsize, 111 | subarr.byteLength); 112 | gl.enableVertexAttribArray(3); 113 | 114 | subarr = lst.subarray(0, 11); 115 | // set bitangent 116 | gl.vertexAttribPointer(4, 3, gl.FLOAT, 117 | false, stride, 118 | subarr.byteLength 119 | ); 120 | gl.enableVertexAttribArray(4); 121 | } 122 | } 123 | /* 124 | map vertex attributes to shader 125 | */ 126 | map_vertex_attrib_to_shader(gl, shaderProgram) { 127 | let vsize = this.vertices[0].size(); 128 | if (vsize >= 5) { 129 | gl.bindAttribLocation(shaderProgram, 0, 'aPos'); 130 | gl.bindAttribLocation(shaderProgram, 1, 'aTexCoord'); 131 | } 132 | if (vsize >= 8) { 133 | // set normals 134 | gl.bindAttribLocation(shaderProgram, 2, 'aNormal'); 135 | } 136 | if (vsize === 14) { 137 | // set tangent and bitangent 138 | gl.bindAttribLocation(shaderProgram, 3, 'aTan'); 139 | gl.bindAttribLocation(shaderProgram, 4, 'aBiTan'); 140 | } 141 | //Set the attributes in the vertex shader to the same indices 142 | //Since the attribute indices have changed, we must re-link the shader 143 | //Note that this will reset all uniforms that were previously set. 144 | shaderProgram.link_program(gl); 145 | this.load_uniforms(gl, shaderProgram); 146 | } 147 | activate_textures(gl, shaderProgram) { 148 | // bind textures 149 | let diffuseNb = 1; 150 | let specularNb = 1; 151 | let normalNb = 1; 152 | let heightNb = 1; 153 | let dispNb = 1; 154 | let aoNb = 1; 155 | let roughNb = 1; 156 | let metalNb = 1; 157 | let albedoNb = 1; 158 | for (var i = 0; i < this.textures.length; i++) { 159 | let gflag = textures[i].flag; 160 | gl.activeTexture(gflag); 161 | let nb = null; 162 | let ttype = textures[i].type; 163 | let tname = textures[i].name; 164 | if (ttype === TEXTURE_TYPE["DIFFUSE"]) { 165 | diffuseNb++; 166 | nb = diffuseNb.toString(); 167 | } else if (ttype === TEXTURE_TYPE["SPECULAR"]) { 168 | specularNb++; 169 | nb = specularNb.toString(); 170 | } else if (ttype === TEXTURE_TYPE["NORMAL"]) { 171 | normalNb++; 172 | nb = normalNb.toString(); 173 | } else if (ttype === TEXTURE_TYPE["HEIGHT"]) { 174 | heightNb++; 175 | nb = heightNb.toString(); 176 | } else if (ttype === TEXTURE_TYPE["DISPLACEMENT"]) { 177 | dispNb++; 178 | nb = heightNb.toString(); 179 | } else if (ttype === TEXTURE_TYPE["AO"]) { 180 | aoNb++; 181 | nb = aoNb.toString(); 182 | } else if (ttype === TEXTURE_TYPE["ROUGHNESS"]) { 183 | roughNb++; 184 | nb = roughNb.toString(); 185 | } else if (ttype === TEXTURE_TYPE["ALBEDO"]) { 186 | albedoNb++; 187 | nb = albedoNb.toString(); 188 | } else if (ttype === TEXTURE_TYPE["METALLIC"]) { 189 | metalNb++; 190 | nb = metalNb.toString(); 191 | } 192 | shaderProgram.set_int_uniform(gl, tname + nb, i); 193 | gl.bindTexture(gl.TEXTURE_2D, textures[i].id); 194 | } 195 | } 196 | } 197 | 198 | class ArrayMesh extends BaseMesh { 199 | constructor(gl, vertices = null, textures = null, indices = null, uniforms = null) { 200 | super(gl, vertices, textures, indices, uniforms); 201 | this.setup_mesh(gl); 202 | } 203 | setup_mesh(gl) { 204 | // create object 205 | this._vao = gl.createVertexArray(); 206 | this._vbo = gl.createBuffer(); 207 | 208 | // bind vertex array object 209 | gl.bindVertexArray(this.vao); 210 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo); 211 | check_error(gl); 212 | 213 | // bind buffer data 214 | let arr = this.to_vertex_array(); 215 | gl.bufferData(gl.ARRAY_BUFFER, arr, gl.STATIC_DRAW); 216 | check_error(gl); 217 | 218 | // bind vao values 219 | this.set_vao_values(gl); 220 | check_error(gl); 221 | } 222 | draw(gl, shaderProgram) { 223 | // this.activate_textures(); 224 | shaderProgram.activate(gl); 225 | this.load_uniforms(gl, shaderProgram); 226 | gl.bindVertexArray(this.vao); 227 | 228 | gl.drawArrays(gl.TRIANGLES, 0, this.vertices.length); 229 | gl.bindVertexArray(null); 230 | gl.activeTexture(gl.TEXTURE0); 231 | 232 | } 233 | } 234 | 235 | class Mesh extends BaseMesh { 236 | constructor(gl, vertices = null, textures = null, indices = null, uniforms = null) { 237 | super(gl, vertices, textures, indices, uniforms); 238 | this._ebo = null; 239 | this.setup_mesh(gl); 240 | } 241 | 242 | get ebo() { 243 | if (this._ebo === null) { 244 | throw "element buffer object null"; 245 | } 246 | return this._ebo; 247 | } 248 | setup_mesh(gl) { 249 | // create object 250 | this._vao = gl.createVertexArray(); 251 | this._vbo = gl.createBuffer(); 252 | this._ebo = gl.createBuffer(); 253 | 254 | // bind vertex array object 255 | gl.bindVertexArray(this.vao); 256 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo); 257 | check_error(gl); 258 | 259 | // bind buffer data 260 | let arr = this.to_vertex_array(); 261 | gl.bufferData(gl.ARRAY_BUFFER, arr, gl.STATIC_DRAW); 262 | check_error(gl); 263 | 264 | // bind index buffer 265 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ebo); 266 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); 267 | check_error(gl); 268 | 269 | // bind vao values 270 | this.set_vao_values(gl); 271 | check_error(gl); 272 | } 273 | draw(gl, shaderProgram) { 274 | // bind textures 275 | this.activate_textures(gl, shaderProgram); 276 | 277 | gl.bindVertexArray(this.vao); 278 | gl.drawElements(gl.TRIANGLES, this.indices.length, gl.UNSIGNED_INT, 0); 279 | 280 | // 281 | gl.bindVertexArray(null); 282 | gl.activeTexture(gl.TEXTURE0); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |