├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build ├── app.js └── minimal-gltf-loader.js ├── css └── style.css ├── examples ├── occlusion-culling-demo.html ├── style.css ├── utility.js ├── webgl2-renderer-old.html ├── webgl2-renderer.html └── webpack.config.js ├── gulpfile.js ├── img ├── drone-pbr.png ├── drone.gif ├── helmet-pbr.png ├── skin.gif └── viewer-screenshot-buggy-bbox.png ├── package.json ├── src ├── glTFLoader.ts ├── index.js ├── minimal-gltf-loader.js └── shaders │ ├── fs-bbox.glsl │ ├── fs-cube-map.glsl │ ├── fs-pbr-master.glsl │ ├── vs-bbox.glsl │ ├── vs-cube-map.glsl │ └── vs-pbr-master.glsl ├── textures ├── brdfLUT.png ├── environment │ ├── diffuse │ │ ├── bakedDiffuse_01.jpg │ │ ├── bakedDiffuse_02.jpg │ │ ├── bakedDiffuse_03.jpg │ │ ├── bakedDiffuse_04.jpg │ │ ├── bakedDiffuse_05.jpg │ │ └── bakedDiffuse_06.jpg │ ├── nx.jpg │ ├── ny.jpg │ ├── nz.jpg │ ├── px.jpg │ ├── py.jpg │ └── pz.jpg └── papermill │ ├── diffuse │ ├── diffuse_back_0.jpg │ ├── diffuse_bottom_0.jpg │ ├── diffuse_front_0.jpg │ ├── diffuse_left_0.jpg │ ├── diffuse_right_0.jpg │ └── diffuse_top_0.jpg │ ├── environment_back_0.jpg │ ├── environment_bottom_0.jpg │ ├── environment_front_0.jpg │ ├── environment_left_0.jpg │ ├── environment_right_0.jpg │ └── environment_top_0.jpg ├── third-party-license └── glMatrix └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .orig 2 | 3 | # Created by https://www.gitignore.io/api/vim,emacs,sublimetext,webstorm,eclipse 4 | 5 | ### Vim ### 6 | [._]*.s[a-w][a-z] 7 | [._]s[a-w][a-z] 8 | *.un~ 9 | Session.vim 10 | .netrwhist 11 | *~ 12 | 13 | 14 | ### Emacs ### 15 | # -*- mode: gitignore; -*- 16 | *~ 17 | \#*\# 18 | /.emacs.desktop 19 | /.emacs.desktop.lock 20 | *.elc 21 | auto-save-list 22 | tramp 23 | .\#* 24 | 25 | # Org-mode 26 | .org-id-locations 27 | *_archive 28 | 29 | # flymake-mode 30 | *_flymake.* 31 | 32 | # eshell files 33 | /eshell/history 34 | /eshell/lastdir 35 | 36 | # elpa packages 37 | /elpa/ 38 | 39 | # reftex files 40 | *.rel 41 | 42 | # AUCTeX auto folder 43 | /auto/ 44 | 45 | # cask packages 46 | .cask/ 47 | 48 | 49 | ### SublimeText ### 50 | # cache files for sublime text 51 | *.tmlanguage.cache 52 | *.tmPreferences.cache 53 | *.stTheme.cache 54 | 55 | # workspace files are user-specific 56 | *.sublime-workspace 57 | 58 | # project files should be checked into the repository, unless a significant 59 | # proportion of contributors will probably not be using SublimeText 60 | # *.sublime-project 61 | 62 | # sftp configuration file 63 | sftp-config.json 64 | 65 | 66 | ### WebStorm ### 67 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 68 | 69 | *.iml 70 | 71 | ## Directory-based project format: 72 | .idea/ 73 | # if you remove the above rule, at least ignore the following: 74 | 75 | # User-specific stuff: 76 | # .idea/workspace.xml 77 | # .idea/tasks.xml 78 | # .idea/dictionaries 79 | 80 | # Sensitive or high-churn files: 81 | # .idea/dataSources.ids 82 | # .idea/dataSources.xml 83 | # .idea/sqlDataSources.xml 84 | # .idea/dynamic.xml 85 | # .idea/uiDesigner.xml 86 | 87 | # Gradle: 88 | # .idea/gradle.xml 89 | # .idea/libraries 90 | 91 | # Mongo Explorer plugin: 92 | # .idea/mongoSettings.xml 93 | 94 | ## File-based project format: 95 | *.ipr 96 | *.iws 97 | 98 | ## Plugin-specific files: 99 | 100 | # IntelliJ 101 | /out/ 102 | 103 | # mpeltonen/sbt-idea plugin 104 | .idea_modules/ 105 | 106 | # JIRA plugin 107 | atlassian-ide-plugin.xml 108 | 109 | # Crashlytics plugin (for Android Studio and IntelliJ) 110 | com_crashlytics_export_strings.xml 111 | crashlytics.properties 112 | crashlytics-build.properties 113 | 114 | 115 | ### Eclipse ### 116 | *.pydevproject 117 | .metadata 118 | .gradle 119 | bin/ 120 | tmp/ 121 | *.tmp 122 | *.bak 123 | *.swp 124 | *~.nib 125 | local.properties 126 | .settings/ 127 | .loadpath 128 | 129 | # Eclipse Core 130 | .project 131 | 132 | # External tool builders 133 | .externalToolBuilders/ 134 | 135 | # Locally stored "Eclipse launch configurations" 136 | *.launch 137 | 138 | # CDT-specific 139 | .cproject 140 | 141 | # JDT-specific (Eclipse Java Development Tools) 142 | .classpath 143 | 144 | # Java annotation processor (APT) 145 | .factorypath 146 | 147 | # PDT-specific 148 | .buildpath 149 | 150 | # sbteclipse plugin 151 | .target 152 | 153 | # TeXlipse plugin 154 | .texlipse 155 | 156 | # VS Code 157 | jsconfig.json 158 | 159 | debug/ 160 | 161 | .DS_Store 162 | 163 | node_modules/ 164 | npm-debug.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4.4 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Shuai Shao (shrekshao) and Contributors 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minimal-gltf-loader 2 | [![Build Status](https://travis-ci.org/shrekshao/minimal-gltf-loader.svg?branch=master)](https://travis-ci.org/shrekshao/minimal-gltf-loader) 3 | [![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/shrekshao/minimal-gltf-loader/blob/master/LICENSE.md) 4 | 5 | A minimal, engine-agnostic JavaScript glTF Loader, with a raw WebGL 2 simple renderer example using the loader. 6 | 7 | ## Viewer Screenshot 8 | ![](img/drone.gif) 9 | ![](img/helmet-pbr.png) 10 | ![](img/skin.gif) 11 | ![](img/viewer-screenshot-buggy-bbox.png) 12 | 13 | ## Live Demo 14 | 15 | [click here for live demo](https://shrekshao.github.io/minimal-gltf-loader/examples/webgl2-renderer.html) 16 | 17 | # Usage 18 | 19 | ```javascript 20 | import {vec3, vec4, quat, mat4} from 'gl-matrix'; 21 | var MinimalGLTFLoader = require('build/minimal-gltf-loader.js'); 22 | 23 | var glTFLoader = new MinimalGLTFLoader.glTFLoader(); 24 | glTFLoader.loadGLTF(url, function(glTF){ 25 | //... 26 | }); 27 | ``` 28 | 29 | 30 | ## Loading Features 31 | 32 | * [x] Accessors 33 | - [ ] Progressive loading / rendering 34 | * [x] Buffers 35 | * [x] BufferViews 36 | * [x] Images 37 | * [x] Meshes 38 | * [x] Nodes 39 | * [x] Primitives 40 | * [x] Samplers 41 | * [x] Textures 42 | * [x] ~~Shader Loader~~ (not part of the core of glTF 2.0) 43 | * [x] Animations 44 | * [x] Cameras 45 | * [x] Materials 46 | * [x] Skins 47 | 48 | ## Formats 49 | 50 | * [x] glTF (.gltf) with separate resources: .bin (geometry, animation, skins), .glsl (shaders), and image files 51 | * [ ] glTF (.gltf) with embedded resources 52 | * [ ] Binary glTF (.glb) using the [KHR_binary_glTF](https://github.com/KhronosGroup/glTF/blob/master/extensions/Khronos/KHR_binary_glTF/README.md) extension 53 | 54 | ## Examples 55 | 56 | * [x] WebGL 2 simple renderer 57 | * [x] baseColorFactor 58 | * [x] baseColorTexture 59 | * [x] normalTexture 60 | * [x] Skybox 61 | * [x] PBR 62 | * [x] Animation 63 | * [ ] Interpolations 64 | - [x] LINEAR 65 | - [ ] STEP 66 | - [ ] CATMULLROMSPLINE 67 | - [ ] CUBICSPLINE 68 | * [x] Skin 69 | * [ ] Camera (from glTF) 70 | * [ ] Progressive rendering (No plan for this) 71 | * [ ] Occlusion Culling experiment 72 | * [x] Bounding Box 73 | * [x] AABB (Axis Aligned Bounding Box, *static) 74 | * [x] OBB (Object/Oriented Bounding Box) 75 | * [x] Scene Bounding Box (fast iterated) And auto centered and scaled 76 | * [ ] Build octree 77 | * [ ] Occlusion Query with hierarchy 78 | 79 | 80 | ## Credits 81 | 82 | * [glTF sample Model](https://github.com/KhronosGroup/glTF-Sample-Models) and [Buster Drone By LaVADraGoN](https://sketchfab.com/models/294e79652f494130ad2ab00a13fdbafd) 83 | * Great thanks to Trung Le ([@trungtle](https://github.com/trungtle)) and Patrick Cozzi ([@pjcozzi](https://github.com/pjcozzi)) for contributing and advising. 84 | * gl-Matrix by Brandon Jones ([@toji](https://github.com/toji)) and Colin MacKenzie IV ([@sinisterchipmunk](https://github.com/sinisterchipmunk)) 85 | * [glTF-WebGL-PBR](https://github.com/KhronosGroup/glTF-WebGL-PBR) 86 | 87 | 88 | 89 | --- 90 | # minimal-gltf-loader-typescript 91 | 92 | A minimal, engine-agnostic TypeScript glTF Loader. 93 | 94 | ## What's new in gltf-loader-typescript 95 | 96 | This loader is a new instance of the minimal-gltf-loader. 97 | 98 | In this branch, the author updated the loading file fuction from XMLHttpRequest to fetch API and used Promise and async/await to make loading procedure much more clearer. 99 | 100 | In glTF 2.0 standard, the target attribute on bufferView is optional. Different 3D softwares have different interpretation of glTF exporter. 101 | For instance, the glTF files exported by Cinema4D have filled target attribute, but those exported by Blender 2.80+ **will NOT** have the target attribute filled. (Can see the issue in the following link: 102 | [KhronosGroup/glTF-Blender-IO#142](https://github.com/KhronosGroup/glTF-Blender-IO/issues/142)) 103 | Therefore, before binding buffer, the loader should infer the target attribute is whether ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER accoring to the usage of bufferview. 104 | ## Usage 105 | 106 | ```typescript 107 | import {vec3, vec4, quat, mat4} from 'gl-matrix'; 108 | import {GLTFLoader, GLTF} from './src/glTFLoader.ts' 109 | 110 | let gl : WebGLRenderingContext | WebGL2RenderingContext; 111 | 112 | new GLTFLoader(gl).loadGLTF('YourURL').then((glTF: GLTF) => { 113 | // Create with glTF object, and proceed rendering process... 114 | }).catch (() => { 115 | // Error control... 116 | }); 117 | ``` -------------------------------------------------------------------------------- /build/minimal-gltf-loader.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("gl-matrix")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["gl-matrix"], factory); 6 | else if(typeof exports === 'object') 7 | exports["MinimalGLTFLoader"] = factory(require("gl-matrix")); 8 | else 9 | root["MinimalGLTFLoader"] = factory(root[undefined]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_1__) { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) { 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ } 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ i: moduleId, 25 | /******/ l: false, 26 | /******/ exports: {} 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.l = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // define getter function for harmony exports 47 | /******/ __webpack_require__.d = function(exports, name, getter) { 48 | /******/ if(!__webpack_require__.o(exports, name)) { 49 | /******/ Object.defineProperty(exports, name, { 50 | /******/ configurable: false, 51 | /******/ enumerable: true, 52 | /******/ get: getter 53 | /******/ }); 54 | /******/ } 55 | /******/ }; 56 | /******/ 57 | /******/ // getDefaultExport function for compatibility with non-harmony modules 58 | /******/ __webpack_require__.n = function(module) { 59 | /******/ var getter = module && module.__esModule ? 60 | /******/ function getDefault() { return module['default']; } : 61 | /******/ function getModuleExports() { return module; }; 62 | /******/ __webpack_require__.d(getter, 'a', getter); 63 | /******/ return getter; 64 | /******/ }; 65 | /******/ 66 | /******/ // Object.prototype.hasOwnProperty.call 67 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 68 | /******/ 69 | /******/ // __webpack_public_path__ 70 | /******/ __webpack_require__.p = ""; 71 | /******/ 72 | /******/ // Load entry module and return exports 73 | /******/ return __webpack_require__(__webpack_require__.s = 0); 74 | /******/ }) 75 | /************************************************************************/ 76 | /******/ ([ 77 | /* 0 */ 78 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 79 | 80 | "use strict"; 81 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); 82 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "glTFLoader", function() { return glTFLoader; }); 83 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_gl_matrix__ = __webpack_require__(1); 84 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_gl_matrix___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_gl_matrix__); 85 | 86 | 87 | var MinimalGLTFLoader = MinimalGLTFLoader || {}; 88 | 89 | var globalUniformBlockID = 0; 90 | 91 | var curLoader = null; // @tmp, might be unsafe if loading multiple model at the same time 92 | 93 | var NUM_MAX_JOINTS = 65; 94 | 95 | // Data classes 96 | var Scene = MinimalGLTFLoader.Scene = function (gltf, s) { 97 | this.name = s.name !== undefined ? s.name : null; 98 | this.nodes = new Array(s.nodes.length); // root node object of this scene 99 | for (var i = 0, len = s.nodes.length; i < len; i++) { 100 | this.nodes[i] = gltf.nodes[s.nodes[i]]; 101 | } 102 | 103 | this.extensions = s.extensions !== undefined ? s.extensions : null; 104 | this.extras = s.extras !== undefined ? s.extras : null; 105 | 106 | 107 | this.boundingBox = null; 108 | }; 109 | 110 | /** 111 | * 112 | * @param {vec3} min 113 | * @param {vec3} max 114 | */ 115 | var BoundingBox = MinimalGLTFLoader.BoundingBox = function (min, max, isClone) { 116 | // this.min = min; 117 | // this.max = max; 118 | min = min || __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].fromValues(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY); 119 | max = max || __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].fromValues(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY); 120 | 121 | if (isClone === undefined || isClone === true) { 122 | this.min = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].clone(min); 123 | this.max = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].clone(max); 124 | } else { 125 | this.min = min; 126 | this.max = max; 127 | } 128 | 129 | 130 | this.transform = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].create(); 131 | }; 132 | 133 | BoundingBox.prototype.updateBoundingBox = function (bbox) { 134 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].min(this.min, this.min, bbox.min); 135 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].max(this.max, this.max, bbox.max); 136 | }; 137 | 138 | BoundingBox.prototype.calculateTransform = function () { 139 | // transform from a unit cube whose min = (0, 0, 0) and max = (1, 1, 1) 140 | 141 | // scale 142 | this.transform[0] = this.max[0] - this.min[0]; 143 | this.transform[5] = this.max[1] - this.min[1]; 144 | this.transform[10] = this.max[2] - this.min[2]; 145 | // translate 146 | this.transform[12] = this.min[0]; 147 | this.transform[13] = this.min[1]; 148 | this.transform[14] = this.min[2]; 149 | }; 150 | 151 | BoundingBox.getAABBFromOBB = (function() { 152 | var transformRight = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].create(); 153 | var transformUp = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].create(); 154 | var transformBackward = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].create(); 155 | 156 | var tmpVec3a = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].create(); 157 | var tmpVec3b = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].create(); 158 | 159 | return (function (obb, matrix) { 160 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].set(transformRight, matrix[0], matrix[1], matrix[2]); 161 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].set(transformUp, matrix[4], matrix[5], matrix[6]); 162 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].set(transformBackward, matrix[8], matrix[9], matrix[10]); 163 | 164 | var min = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].fromValues(matrix[12], matrix[13], matrix[14]); // init with matrix translation 165 | var max = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].clone(min); 166 | 167 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].scale(tmpVec3a, transformRight, obb.min[0]); 168 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].scale(tmpVec3b, transformRight, obb.max[0]); 169 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].min(transformRight, tmpVec3a, tmpVec3b); 170 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].add(min, min, transformRight); 171 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].max(transformRight, tmpVec3a, tmpVec3b); 172 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].add(max, max, transformRight); 173 | 174 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].scale(tmpVec3a, transformUp, obb.min[1]); 175 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].scale(tmpVec3b, transformUp, obb.max[1]); 176 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].min(transformUp, tmpVec3a, tmpVec3b); 177 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].add(min, min, transformUp); 178 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].max(transformUp, tmpVec3a, tmpVec3b); 179 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].add(max, max, transformUp); 180 | 181 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].scale(tmpVec3a, transformBackward, obb.min[2]); 182 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].scale(tmpVec3b, transformBackward, obb.max[2]); 183 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].min(transformBackward, tmpVec3a, tmpVec3b); 184 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].add(min, min, transformBackward); 185 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].max(transformBackward, tmpVec3a, tmpVec3b); 186 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].add(max, max, transformBackward); 187 | 188 | var bbox = new BoundingBox(min, max, false); 189 | bbox.calculateTransform(); 190 | return bbox; 191 | }); 192 | })(); 193 | 194 | 195 | 196 | var Accessor = MinimalGLTFLoader.Accessor = function (a, bufferViewObject) { 197 | this.bufferView = bufferViewObject; 198 | this.componentType = a.componentType; // required 199 | this.byteOffset = a.byteOffset !== undefined ? a.byteOffset : 0; 200 | this.byteStride = bufferViewObject.byteStride; 201 | this.normalized = a.normalized !== undefined ? a.normalized : false; 202 | this.count = a.count; // required 203 | this.type = a.type; // required 204 | this.size = Type2NumOfComponent[this.type]; 205 | 206 | this.min = a.min; // @tmp assume required for now (for bbox) 207 | this.max = a.max; // @tmp assume required for now (for bbox) 208 | 209 | this.extensions = a.extensions !== undefined ? a.extensions : null; 210 | this.extras = a.extras !== undefined ? a.extras : null; 211 | }; 212 | 213 | Accessor.prototype.prepareVertexAttrib = function(location, gl) { 214 | gl.vertexAttribPointer( 215 | location, 216 | this.size, 217 | this.componentType, 218 | this.normalized, 219 | this.byteStride, 220 | this.byteOffset 221 | ); 222 | gl.enableVertexAttribArray(location); 223 | }; 224 | 225 | var BufferView = MinimalGLTFLoader.BufferView = function(bf, bufferData) { 226 | this.byteLength = bf.byteLength; //required 227 | this.byteOffset = bf.byteOffset !== undefined ? bf.byteOffset : 0; 228 | this.byteStride = bf.byteStride !== undefined ? bf.byteStride : 0; 229 | this.target = bf.target !== undefined ? bf.target : null; 230 | 231 | this.data = bufferData.slice(this.byteOffset, this.byteOffset + this.byteLength); 232 | 233 | this.extensions = bf.extensions !== undefined ? bf.extensions : null; 234 | this.extras = bf.extras !== undefined ? bf.extras : null; 235 | 236 | // runtime stuffs ------------- 237 | this.buffer = null; // gl buffer 238 | }; 239 | 240 | BufferView.prototype.createBuffer = function(gl) { 241 | this.buffer = gl.createBuffer(); 242 | }; 243 | 244 | BufferView.prototype.bindData = function(gl) { 245 | if (this.target) { 246 | gl.bindBuffer(this.target, this.buffer); 247 | gl.bufferData(this.target, this.data, gl.STATIC_DRAW); 248 | gl.bindBuffer(this.target, null); 249 | return true; 250 | } 251 | return false; 252 | }; 253 | 254 | 255 | var Camera = MinimalGLTFLoader.Camera = function(c) { 256 | this.name = c.name !== undefined ? c.name : null; 257 | this.type = c.type; // required 258 | 259 | this.othographic = c.othographic === undefined ? null : c.othographic; // every attribute inside is required (excluding extensions) 260 | this.perspective = c.perspective === undefined ? null : { 261 | yfov: c.perspective.yfov, 262 | znear: c.perspective.znear, 263 | zfar: c.perspective.zfar !== undefined ? c.perspective.zfar : null, 264 | aspectRatio: c.perspective.aspectRatio !== undefined ? c.perspective.aspectRatio : null 265 | }; 266 | 267 | this.extensions = c.extensions !== undefined ? c.extensions : null; 268 | this.extras = c.extras !== undefined ? c.extras : null; 269 | }; 270 | 271 | 272 | 273 | var Node = MinimalGLTFLoader.Node = function (n, nodeID) { 274 | this.name = n.name !== undefined ? n.name : null; 275 | this.nodeID = nodeID; 276 | // TODO: camera 277 | this.camera = n.camera !== undefined ? n.camera : null; 278 | 279 | this.matrix = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].create(); 280 | if (n.hasOwnProperty('matrix')) { 281 | for(var i = 0; i < 16; ++i) { 282 | this.matrix[i] = n.matrix[i]; 283 | } 284 | 285 | this.translation = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].create(); 286 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].getTranslation(this.translation, this.matrix); 287 | 288 | this.rotation = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["quat"].create(); 289 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].getRotation(this.rotation, this.matrix); 290 | 291 | this.scale = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].create(); 292 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].getScaling(this.scale, this.matrix); 293 | } else { 294 | // this.translation = null; 295 | // this.rotation = null; 296 | // this.scale = null; 297 | this.getTransformMatrixFromTRS(n.translation, n.rotation, n.scale); 298 | } 299 | 300 | 301 | 302 | 303 | this.children = n.children || []; // init as id, then hook up to node object later 304 | this.mesh = n.mesh !== undefined ? curLoader.glTF.meshes[n.mesh] : null; 305 | 306 | this.skin = n.skin !== undefined ? n.skin : null; // init as id, then hook up to skin object later 307 | 308 | if (n.extensions !== undefined) { 309 | if (n.extensions.gl_avatar !== undefined && curLoader.enableGLAvatar === true) { 310 | var linkedSkinID = curLoader.skeletonGltf.json.extensions.gl_avatar.skins[ n.extensions.gl_avatar.skin.name ]; 311 | var linkedSkin = curLoader.skeletonGltf.skins[linkedSkinID]; 312 | this.skin = new SkinLink(curLoader.glTF, linkedSkin, n.extensions.gl_avatar.skin.inverseBindMatrices); 313 | } 314 | } 315 | 316 | 317 | 318 | // TODO: morph targets weights 319 | this.weights = n.weights !== undefined ? n.weights : null; 320 | 321 | 322 | this.extensions = n.extensions !== undefined ? n.extensions : null; 323 | this.extras = n.extras !== undefined ? n.extras : null; 324 | 325 | // runtime stuffs-------------- 326 | 327 | this.aabb = null; // axis aligned bounding box, not need to apply node transform to aabb 328 | this.bvh = new BoundingBox(); 329 | }; 330 | 331 | Node.prototype.traverse = function(parent, executeFunc) { 332 | executeFunc(this, parent); 333 | for (var i = 0, len = this.children.length; i < len; i++) { 334 | this.children[i].traverse(this, executeFunc); 335 | } 336 | }; 337 | 338 | Node.prototype.traversePostOrder = function(parent, executeFunc) { 339 | for (var i = 0, len = this.children.length; i < len; i++) { 340 | this.children[i].traversePostOrder(this, executeFunc); 341 | } 342 | executeFunc(this, parent); 343 | }; 344 | 345 | Node.prototype.traverseTwoExecFun = function(parent, execFunPre, execFunPos) { 346 | execFunPre(this, parent); 347 | for (var i = 0, len = this.children.length; i < len; i++) { 348 | this.children[i].traverseTwoExecFun(this, execFunPre, execFunPos); 349 | } 350 | execFunPos(this, parent); 351 | }; 352 | 353 | var TRSMatrix = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].create(); 354 | 355 | Node.prototype.getTransformMatrixFromTRS = function(translation, rotation, scale) { 356 | 357 | this.translation = translation !== undefined ? __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].fromValues(translation[0], translation[1], translation[2]) : __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].fromValues(0, 0, 0); 358 | this.rotation = rotation !== undefined ? __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec4"].fromValues(rotation[0], rotation[1], rotation[2], rotation[3]) : __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec4"].fromValues(0, 0, 0, 1); 359 | this.scale = scale !== undefined ? __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].fromValues(scale[0], scale[1], scale[2]) : __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].fromValues(1, 1, 1); 360 | 361 | this.updateMatrixFromTRS(); 362 | }; 363 | 364 | Node.prototype.updateMatrixFromTRS = function() { 365 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].fromRotationTranslation(TRSMatrix, this.rotation, this.translation); 366 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].scale(this.matrix, TRSMatrix, this.scale); 367 | }; 368 | 369 | 370 | 371 | var Mesh = MinimalGLTFLoader.Mesh = function (m, meshID) { 372 | this.meshID = meshID; 373 | this.name = m.name !== undefined ? m.name : null; 374 | 375 | this.primitives = []; // required 376 | 377 | 378 | 379 | // bounding box (runtime stuff) 380 | this.boundingBox = null; 381 | 382 | var p, primitive, accessor; 383 | 384 | for (var i = 0, len = m.primitives.length; i < len; ++i) { 385 | p = m.primitives[i]; 386 | primitive = new Primitive(curLoader.glTF, p); 387 | this.primitives.push(primitive); 388 | 389 | // bounding box related 390 | if (primitive.boundingBox) { 391 | if (!this.boundingBox) { 392 | this.boundingBox = new BoundingBox(); 393 | } 394 | this.boundingBox.updateBoundingBox(primitive.boundingBox); 395 | } 396 | } 397 | 398 | if (this.boundingBox) { 399 | this.boundingBox.calculateTransform(); 400 | } 401 | 402 | 403 | // TODO: weights for morph targets 404 | this.weights = m.weights !== undefined ? m.weights : null; 405 | 406 | this.extensions = m.extensions !== undefined ? m.extensions : null; 407 | this.extras = m.extras !== undefined ? m.extras : null; 408 | 409 | }; 410 | 411 | var Primitive = MinimalGLTFLoader.Primitive = function (gltf, p) { 412 | // , required 413 | // get hook up with accessor object in _postprocessing 414 | this.attributes = p.attributes; 415 | this.indices = p.indices !== undefined ? p.indices : null; // accessor id 416 | 417 | var attname; 418 | if (p.extensions !== undefined) { 419 | if (p.extensions.gl_avatar !== undefined && curLoader.enableGLAvatar === true) { 420 | if (p.extensions.gl_avatar.attributes) { 421 | for ( attname in p.extensions.gl_avatar.attributes ) { 422 | this.attributes[attname] = p.extensions.gl_avatar.attributes[attname]; 423 | } 424 | } 425 | } 426 | } 427 | 428 | 429 | if (this.indices !== null) { 430 | this.indicesComponentType = gltf.json.accessors[this.indices].componentType; 431 | this.indicesLength = gltf.json.accessors[this.indices].count; 432 | this.indicesOffset = (gltf.json.accessors[this.indices].byteOffset || 0); 433 | } else { 434 | // assume 'POSITION' is there 435 | this.drawArraysCount = gltf.json.accessors[this.attributes.POSITION].count; 436 | this.drawArraysOffset = (gltf.json.accessors[this.attributes.POSITION].byteOffset || 0); 437 | } 438 | 439 | 440 | // hook up accessor object 441 | for ( attname in this.attributes ) { 442 | this.attributes[attname] = gltf.accessors[ this.attributes[attname] ]; 443 | } 444 | 445 | 446 | this.material = p.material !== undefined ? gltf.materials[p.material] : null; 447 | 448 | 449 | this.mode = p.mode !== undefined ? p.mode : 4; // default: gl.TRIANGLES 450 | 451 | 452 | 453 | // morph related 454 | this.targets = p.targets; 455 | 456 | 457 | this.extensions = p.extensions !== undefined ? p.extensions : null; 458 | this.extras = p.extras !== undefined ? p.extras : null; 459 | 460 | 461 | // ----gl run time related 462 | this.vertexArray = null; //vao 463 | 464 | this.vertexBuffer = null; 465 | this.indexBuffer = null; 466 | 467 | 468 | this.shader = null; 469 | 470 | 471 | this.boundingBox = null; 472 | if (this.attributes.POSITION !== undefined) { 473 | var accessor = this.attributes.POSITION; 474 | if (accessor.max) { 475 | // @todo: handle cases where no min max are provided 476 | 477 | // assume vec3 478 | if (accessor.type === 'VEC3') { 479 | this.boundingBox = new BoundingBox( 480 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].fromValues(accessor.min[0], accessor.min[1], accessor.min[2]), 481 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].fromValues(accessor.max[0], accessor.max[1], accessor.max[2]), 482 | false 483 | ); 484 | this.boundingBox.calculateTransform(); 485 | 486 | 487 | 488 | } 489 | 490 | } 491 | } 492 | }; 493 | 494 | 495 | var Texture = MinimalGLTFLoader.Texture = function (t) { 496 | this.name = t.name !== undefined ? t.name : null; 497 | this.sampler = t.sampler !== undefined ? curLoader.glTF.samplers[t.sampler] : null; 498 | this.source = t.source !== undefined ? curLoader.glTF.images[t.source] : null; 499 | 500 | this.extensions = t.extensions !== undefined ? t.extensions : null; 501 | this.extras = t.extras !== undefined ? t.extras : null; 502 | 503 | // runtime 504 | this.texture = null; 505 | }; 506 | 507 | Texture.prototype.createTexture = function(gl) { 508 | this.texture = gl.createTexture(); 509 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 510 | gl.texImage2D( 511 | gl.TEXTURE_2D, // assumed 512 | 0, // Level of details 513 | // gl.RGB, // Format 514 | // gl.RGB, 515 | gl.RGBA, // Format 516 | gl.RGBA, 517 | gl.UNSIGNED_BYTE, // Size of each channel 518 | this.source 519 | ); 520 | gl.generateMipmap(gl.TEXTURE_2D); 521 | gl.bindTexture(gl.TEXTURE_2D, null); 522 | }; 523 | 524 | var Sampler = MinimalGLTFLoader.Sampler = function (s) { 525 | this.name = s.name !== undefined ? s.name : null; 526 | this.magFilter = s.magFilter !== undefined ? s.magFilter : null; 527 | this.minFilter = s.minFilter !== undefined ? s.minFilter : null; 528 | this.wrapS = s.wrapS !== undefined ? s.wrapS : 10497; 529 | this.wrapT = s.wrapT !== undefined ? s.wrapT : 10497; 530 | 531 | this.extensions = s.extensions !== undefined ? s.extensions : null; 532 | this.extras = s.extras !== undefined ? s.extras : null; 533 | 534 | this.sampler = null; 535 | }; 536 | 537 | Sampler.prototype.createSampler = function(gl) { 538 | this.sampler = gl.createSampler(); 539 | if (this.minFilter) { 540 | gl.samplerParameteri(this.sampler, gl.TEXTURE_MIN_FILTER, this.minFilter); 541 | } else { 542 | gl.samplerParameteri(this.sampler, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR); 543 | } 544 | if (this.magFilter) { 545 | gl.samplerParameteri(this.sampler, gl.TEXTURE_MAG_FILTER, this.magFilter); 546 | } else { 547 | gl.samplerParameteri(this.sampler, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 548 | } 549 | gl.samplerParameteri(this.sampler, gl.TEXTURE_WRAP_S, this.wrapS); 550 | gl.samplerParameteri(this.sampler, gl.TEXTURE_WRAP_T, this.wrapT); 551 | }; 552 | 553 | // Sampler.prototype.bindSampler = function(i, gl) { 554 | // gl.bindSampler(i, this.sampler); 555 | // } 556 | 557 | var TextureInfo = MinimalGLTFLoader.TextureInfo = function (json) { 558 | this.index = json.index; 559 | this.texCoord = json.texCoord !== undefined ? json.texCoord : 0 ; 560 | 561 | this.extensions = json.extensions !== undefined ? json.extensions : null; 562 | this.extras = json.extras !== undefined ? json.extras : null; 563 | }; 564 | 565 | var PbrMetallicRoughness = MinimalGLTFLoader.PbrMetallicRoughness = function (json) { 566 | this.baseColorFactor = json.baseColorFactor !== undefined ? json.baseColorFactor : [1, 1, 1, 1]; 567 | this.baseColorTexture = json.baseColorTexture !== undefined ? new TextureInfo(json.baseColorTexture): null; 568 | this.metallicFactor = json.metallicFactor !== undefined ? json.metallicFactor : 1 ; 569 | this.roughnessFactor = json.roughnessFactor !== undefined ? json.roughnessFactor : 1 ; 570 | this.metallicRoughnessTexture = json.metallicRoughnessTexture !== undefined ? new TextureInfo(json.metallicRoughnessTexture): null; 571 | 572 | this.extensions = json.extensions !== undefined ? json.extensions : null; 573 | this.extras = json.extras !== undefined ? json.extras : null; 574 | }; 575 | 576 | var NormalTextureInfo = MinimalGLTFLoader.NormalTextureInfo = function (json) { 577 | this.index = json.index; 578 | this.texCoord = json.texCoord !== undefined ? json.texCoord : 0 ; 579 | this.scale = json.scale !== undefined ? json.scale : 1 ; 580 | 581 | this.extensions = json.extensions !== undefined ? json.extensions : null; 582 | this.extras = json.extras !== undefined ? json.extras : null; 583 | }; 584 | 585 | var OcclusionTextureInfo = MinimalGLTFLoader.OcclusionTextureInfo = function (json) { 586 | this.index = json.index; 587 | this.texCoord = json.texCoord !== undefined ? json.texCoord : 0 ; 588 | this.strength = json.strength !== undefined ? json.strength : 1 ; 589 | 590 | this.extensions = json.extensions !== undefined ? json.extensions : null; 591 | this.extras = json.extras !== undefined ? json.extras : null; 592 | }; 593 | 594 | var Material = MinimalGLTFLoader.Material = function (m) { 595 | this.name = m.name !== undefined ? m.name : null; 596 | 597 | this.pbrMetallicRoughness = m.pbrMetallicRoughness !== undefined ? new PbrMetallicRoughness( m.pbrMetallicRoughness ) : new PbrMetallicRoughness({ 598 | baseColorFactor: [1, 1, 1, 1], 599 | metallicFactor: 1, 600 | metallicRoughnessTexture: 1 601 | }); 602 | // this.normalTexture = m.normalTexture !== undefined ? m.normalTexture : null; 603 | this.normalTexture = m.normalTexture !== undefined ? new NormalTextureInfo(m.normalTexture) : null; 604 | this.occlusionTexture = m.occlusionTexture !== undefined ? new OcclusionTextureInfo(m.occlusionTexture) : null; 605 | this.emissiveTexture = m.emissiveTexture !== undefined ? new TextureInfo(m.emissiveTexture) : null; 606 | 607 | this.emissiveFactor = m.emissiveFactor !== undefined ? m.emissiveFactor : [0, 0, 0]; 608 | this.alphaMode = m.alphaMode !== undefined ? m.alphaMode : "OPAQUE"; 609 | this.alphaCutoff = m.alphaCutoff !== undefined ? m.alphaCutoff : 0.5; 610 | this.doubleSided = m.doubleSided || false; 611 | 612 | this.extensions = m.extensions !== undefined ? m.extensions : null; 613 | this.extras = m.extras !== undefined ? m.extras : null; 614 | }; 615 | 616 | 617 | var Skin = MinimalGLTFLoader.Skin = function (gltf, s, skinID) { 618 | this.name = s.name !== undefined ? s.name : null; 619 | this.skinID = skinID; 620 | 621 | this.joints = new Array(s.joints.length); // required 622 | var i, len; 623 | for (i = 0, len = this.joints.length; i < len; i++) { 624 | this.joints[i] = gltf.nodes[s.joints[i]]; 625 | } 626 | 627 | this.skeleton = s.skeleton !== undefined ? gltf.nodes[s.skeleton] : null; 628 | this.inverseBindMatrices = s.inverseBindMatrices !== undefined ? gltf.accessors[s.inverseBindMatrices] : null; 629 | 630 | this.extensions = s.extensions !== undefined ? s.extensions : null; 631 | this.extras = s.extras !== undefined ? s.extras : null; 632 | 633 | 634 | // @tmp: runtime stuff should be taken care of renderer 635 | // since glTF model should only store info 636 | // runtime can have multiple instances of this glTF models 637 | this.uniformBlockID = globalUniformBlockID++; 638 | 639 | if (this.inverseBindMatrices) { 640 | // should be a mat4 641 | this.inverseBindMatricesData = _getAccessorData(this.inverseBindMatrices); 642 | // this.inverseBindMatricesMat4 = mat4.fromValues(this.inverseBindMatricesData); 643 | 644 | this.inverseBindMatrix = []; // for calculation 645 | this.jointMatrixUniformBuffer = null; 646 | // this.jointMatrixUnidormBufferData = _arrayBuffer2TypedArray( 647 | // this.inverseBindMatricesData, 648 | // 0, 649 | // this.inverseBindMatricesData.length, 650 | // this.inverseBindMatrices.componentType 651 | // ); // for copy to UBO 652 | 653 | // @tmp: fixed length to coordinate with shader, for copy to UBO 654 | this.jointMatrixUnidormBufferData = new Float32Array(NUM_MAX_JOINTS * 16); 655 | 656 | for (i = 0, len = this.inverseBindMatricesData.length; i < len; i += 16) { 657 | this.inverseBindMatrix.push(__WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].fromValues( 658 | this.inverseBindMatricesData[i], 659 | this.inverseBindMatricesData[i + 1], 660 | this.inverseBindMatricesData[i + 2], 661 | this.inverseBindMatricesData[i + 3], 662 | this.inverseBindMatricesData[i + 4], 663 | this.inverseBindMatricesData[i + 5], 664 | this.inverseBindMatricesData[i + 6], 665 | this.inverseBindMatricesData[i + 7], 666 | this.inverseBindMatricesData[i + 8], 667 | this.inverseBindMatricesData[i + 9], 668 | this.inverseBindMatricesData[i + 10], 669 | this.inverseBindMatricesData[i + 11], 670 | this.inverseBindMatricesData[i + 12], 671 | this.inverseBindMatricesData[i + 13], 672 | this.inverseBindMatricesData[i + 14], 673 | this.inverseBindMatricesData[i + 15] 674 | )); 675 | } 676 | } 677 | 678 | }; 679 | 680 | var SkinLink = MinimalGLTFLoader.SkinLink = function (gltf, linkedSkin, inverseBindMatricesAccessorID) { 681 | this.isLink = true; 682 | 683 | if (!gltf.skins) { 684 | gltf.skins = []; 685 | } 686 | gltf.skins.push(this); 687 | 688 | this.name = linkedSkin.name; 689 | // this.skinID = linkedSkin.skinID; // use this for uniformblock id 690 | // this.skinID = gltf.skins.length - 1; 691 | // this.skinID = curLoader.skeletonGltf.skins.length + gltf.skins.length - 1; 692 | this.skinID = gltf.skins.length - 1; 693 | 694 | this.joints = linkedSkin.joints; 695 | 696 | this.skeleton = linkedSkin.skeleton; 697 | this.inverseBindMatrices = inverseBindMatricesAccessorID !== undefined ? gltf.accessors[inverseBindMatricesAccessorID] : null; 698 | 699 | // @tmp: runtime stuff should be taken care of renderer 700 | // since glTF model should only store info 701 | // runtime can have multiple instances of this glTF models 702 | this.uniformBlockID = globalUniformBlockID++; 703 | if (this.inverseBindMatrices) { 704 | // should be a mat4 705 | this.inverseBindMatricesData = _getAccessorData(this.inverseBindMatrices); 706 | // this.inverseBindMatricesMat4 = mat4.fromValues(this.inverseBindMatricesData); 707 | 708 | this.inverseBindMatrix = []; // for calculation 709 | this.jointMatrixUniformBuffer = null; 710 | // this.jointMatrixUnidormBufferData = _arrayBuffer2TypedArray( 711 | // this.inverseBindMatricesData, 712 | // 0, 713 | // this.inverseBindMatricesData.length, 714 | // this.inverseBindMatrices.componentType 715 | // ); // for copy to UBO 716 | 717 | // @tmp: fixed length to coordinate with shader, for copy to UBO 718 | this.jointMatrixUnidormBufferData = new Float32Array(NUM_MAX_JOINTS * 16); 719 | 720 | for (var i = 0, len = this.inverseBindMatricesData.length; i < len; i += 16) { 721 | this.inverseBindMatrix.push(__WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].fromValues( 722 | this.inverseBindMatricesData[i], 723 | this.inverseBindMatricesData[i + 1], 724 | this.inverseBindMatricesData[i + 2], 725 | this.inverseBindMatricesData[i + 3], 726 | this.inverseBindMatricesData[i + 4], 727 | this.inverseBindMatricesData[i + 5], 728 | this.inverseBindMatricesData[i + 6], 729 | this.inverseBindMatricesData[i + 7], 730 | this.inverseBindMatricesData[i + 8], 731 | this.inverseBindMatricesData[i + 9], 732 | this.inverseBindMatricesData[i + 10], 733 | this.inverseBindMatricesData[i + 11], 734 | this.inverseBindMatricesData[i + 12], 735 | this.inverseBindMatricesData[i + 13], 736 | this.inverseBindMatricesData[i + 14], 737 | this.inverseBindMatricesData[i + 15] 738 | )); 739 | } 740 | } 741 | 742 | 743 | 744 | }; 745 | 746 | 747 | 748 | 749 | // animation has no potential plan for progressive rendering I guess 750 | // so everything happens after all buffers are loaded 751 | 752 | var Target = MinimalGLTFLoader.Target = function (t) { 753 | this.nodeID = t.node !== undefined ? t.node : null ; //id, to be hooked up to object later 754 | this.path = t.path; //required, string 755 | 756 | this.extensions = t.extensions !== undefined ? t.extensions : null; 757 | this.extras = t.extras !== undefined ? t.extras : null; 758 | }; 759 | 760 | var Channel = MinimalGLTFLoader.Channel = function (c, animation) { 761 | this.sampler = animation.samplers[c.sampler]; //required 762 | this.target = new Target(c.target); //required 763 | 764 | this.extensions = c.extensions !== undefined ? c.extensions : null; 765 | this.extras = c.extras !== undefined ? c.extras : null; 766 | }; 767 | 768 | var AnimationSampler = MinimalGLTFLoader.AnimationSampler = function (gltf, s) { 769 | this.input = gltf.accessors[s.input]; //required, accessor object 770 | this.output = gltf.accessors[s.output]; //required, accessor object 771 | 772 | this.inputTypedArray = _getAccessorData(this.input); 773 | this.outputTypedArray = _getAccessorData(this.output); 774 | 775 | 776 | // "LINEAR" 777 | // "STEP" 778 | // "CATMULLROMSPLINE" 779 | // "CUBICSPLINE" 780 | this.interpolation = s.interpolation !== undefined ? s.interpolation : 'LINEAR' ; 781 | 782 | 783 | this.extensions = s.extensions !== undefined ? s.extensions : null; 784 | this.extras = s.extras !== undefined ? s.extras : null; 785 | 786 | // ------- extra runtime info ----------- 787 | // runtime status thing 788 | this.curIdx = 0; 789 | // this.curValue = 0; 790 | this.curValue = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec4"].create(); 791 | this.endT = this.inputTypedArray[this.inputTypedArray.length - 1]; 792 | this.inputMax = this.endT - this.inputTypedArray[0]; 793 | }; 794 | 795 | var animationOutputValueVec4a = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec4"].create(); 796 | var animationOutputValueVec4b = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec4"].create(); 797 | 798 | AnimationSampler.prototype.getValue = function (t) { 799 | if (t > this.endT) { 800 | t -= this.inputMax * Math.ceil((t - this.endT) / this.inputMax); 801 | this.curIdx = 0; 802 | } 803 | 804 | var len = this.inputTypedArray.length; 805 | while (this.curIdx <= len - 2 && t >= this.inputTypedArray[this.curIdx + 1]) { 806 | this.curIdx++; 807 | } 808 | 809 | 810 | if (this.curIdx >= len - 1) { 811 | // loop 812 | t -= this.inputMax; 813 | this.curIdx = 0; 814 | } 815 | 816 | // @tmp: assume no stride 817 | var count = Type2NumOfComponent[this.output.type]; 818 | 819 | var v4lerp = count === 4 ? __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["quat"].slerp: __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec4"].lerp; 820 | 821 | var i = this.curIdx; 822 | var o = i * count; 823 | var on = o + count; 824 | 825 | var u = Math.max( 0, t - this.inputTypedArray[i] ) / (this.inputTypedArray[i+1] - this.inputTypedArray[i]); 826 | 827 | for (var j = 0; j < count; j++ ) { 828 | animationOutputValueVec4a[j] = this.outputTypedArray[o + j]; 829 | animationOutputValueVec4b[j] = this.outputTypedArray[on + j]; 830 | } 831 | 832 | switch(this.interpolation) { 833 | case 'LINEAR': 834 | v4lerp(this.curValue, animationOutputValueVec4a, animationOutputValueVec4b, u); 835 | break; 836 | 837 | default: 838 | break; 839 | } 840 | }; 841 | 842 | 843 | 844 | var Animation = MinimalGLTFLoader.Animation = function (gltf, a) { 845 | this.name = a.name !== undefined ? a.name : null; 846 | 847 | var i, len; 848 | 849 | 850 | 851 | this.samplers = []; // required, array of animation sampler 852 | 853 | for (i = 0, len = a.samplers.length; i < len; i++) { 854 | this.samplers[i] = new AnimationSampler(gltf, a.samplers[i]); 855 | } 856 | 857 | this.channels = []; //required, array of channel 858 | 859 | for (i = 0, len = a.channels.length; i < len; i++) { 860 | this.channels[i] = new Channel(a.channels[i], this); 861 | } 862 | 863 | this.extensions = a.extensions !== undefined ? a.extensions : null; 864 | this.extras = a.extras !== undefined ? a.extras : null; 865 | }; 866 | 867 | 868 | /** 869 | * 870 | */ 871 | var glTFModel = MinimalGLTFLoader.glTFModel = function (gltf) { 872 | this.json = gltf; 873 | this.defaultScene = gltf.scene !== undefined ? gltf.scene : 0; 874 | 875 | this.version = Number(gltf.asset.version); 876 | 877 | if (gltf.accessors) { 878 | this.accessors = new Array(gltf.accessors.length); 879 | } 880 | 881 | if (gltf.bufferViews) { 882 | this.bufferViews = new Array(gltf.bufferViews.length); 883 | } 884 | 885 | if (gltf.scenes) { 886 | this.scenes = new Array(gltf.scenes.length); // store Scene object 887 | } 888 | 889 | if (gltf.nodes) { 890 | this.nodes = new Array(gltf.nodes.length); // store Node object 891 | } 892 | 893 | if (gltf.meshes) { 894 | this.meshes = new Array(gltf.meshes.length); // store mesh object 895 | } 896 | 897 | if (gltf.materials) { 898 | this.materials = new Array(gltf.materials.length); // store material object 899 | } 900 | 901 | if (gltf.textures) { 902 | this.textures = new Array(gltf.textures.length); 903 | } 904 | 905 | if (gltf.samplers) { 906 | this.samplers = new Array(gltf.samplers.length); 907 | } 908 | 909 | if (gltf.images) { 910 | this.images = new Array(gltf.images.length); 911 | } 912 | 913 | 914 | if (gltf.skins) { 915 | this.skins = new Array(gltf.skins.length); 916 | } 917 | 918 | if (gltf.animations) { 919 | this.animations = new Array(gltf.animations.length); 920 | } 921 | 922 | if (gltf.cameras) { 923 | this.cameras = new Array(gltf.cameras.length); 924 | } 925 | 926 | this.extensions = gltf.extensions !== undefined ? gltf.extensions : null; 927 | this.extras = gltf.extras !== undefined ? gltf.extras : null; 928 | 929 | }; 930 | 931 | 932 | 933 | var gl; 934 | 935 | var glTFLoader = MinimalGLTFLoader.glTFLoader = function (glContext) { 936 | gl = glContext !== undefined ? glContext : null; 937 | this._init(); 938 | this.glTF = null; 939 | 940 | this.enableGLAvatar = false; 941 | this.linkSkeletonGltf = null; 942 | }; 943 | 944 | glTFLoader.prototype._init = function() { 945 | this._loadDone = false; 946 | 947 | this._bufferRequested = 0; 948 | this._bufferLoaded = 0; 949 | this._buffers = []; 950 | this._bufferTasks = {}; 951 | 952 | this._shaderRequested = 0; 953 | this._shaderLoaded = 0; 954 | 955 | this._imageRequested = 0; 956 | this._imageLoaded = 0; 957 | 958 | this._pendingTasks = 0; 959 | this._finishedPendingTasks = 0; 960 | 961 | this.onload = null; 962 | 963 | curLoader = this; 964 | }; 965 | 966 | 967 | glTFLoader.prototype._checkComplete = function () { 968 | if (this._bufferRequested == this._bufferLoaded && 969 | // this._shaderRequested == this._shaderLoaded && 970 | this._imageRequested == this._imageLoaded 971 | // && other resources finish loading 972 | ) { 973 | this._loadDone = true; 974 | } 975 | 976 | if (this._loadDone && this._pendingTasks == this._finishedPendingTasks) { 977 | 978 | this._postprocess(); 979 | 980 | this.onload(this.glTF); 981 | } 982 | }; 983 | 984 | glTFLoader.prototype.loadGLTF_GL_Avatar_Skin = function (uri, skeletonGltf, callback) { 985 | this.enableGLAvatar = true; 986 | this.skeletonGltf = skeletonGltf; 987 | 988 | this.loadGLTF(uri, callback); 989 | }; 990 | 991 | /** 992 | * load a glTF model 993 | * 994 | * @param {String} uri uri of the .glTF file. Other resources (bins, images) are assumed to be in the same base path 995 | * @param {Function} callback the onload callback function 996 | */ 997 | glTFLoader.prototype.loadGLTF = function (uri, callback) { 998 | 999 | this._init(); 1000 | 1001 | this.onload = callback || function(glTF) { 1002 | console.log('glTF model loaded.'); 1003 | console.log(glTF); 1004 | }; 1005 | 1006 | 1007 | this.baseUri = _getBaseUri(uri); 1008 | 1009 | var loader = this; 1010 | 1011 | _loadJSON(uri, function (response) { 1012 | // Parse JSON string into object 1013 | var json = JSON.parse(response); 1014 | 1015 | loader.glTF = new glTFModel(json); 1016 | 1017 | var bid; 1018 | 1019 | var loadArrayBufferCallback = function (resource) { 1020 | 1021 | loader._buffers[bid] = resource; 1022 | loader._bufferLoaded++; 1023 | if (loader._bufferTasks[bid]) { 1024 | var i,len; 1025 | for (i = 0, len = loader._bufferTasks[bid].length; i < len; ++i) { 1026 | (loader._bufferTasks[bid][i])(resource); 1027 | } 1028 | } 1029 | loader._checkComplete(); 1030 | 1031 | }; 1032 | 1033 | // Launch loading resources task: buffers, etc. 1034 | if (json.buffers) { 1035 | for (bid in json.buffers) { 1036 | 1037 | loader._bufferRequested++; 1038 | 1039 | _loadArrayBuffer(loader.baseUri + json.buffers[bid].uri, loadArrayBufferCallback); 1040 | 1041 | } 1042 | } 1043 | 1044 | // load images 1045 | var loadImageCallback = function (img, iid) { 1046 | loader._imageLoaded++; 1047 | loader.glTF.images[iid] = img; 1048 | loader._checkComplete(); 1049 | }; 1050 | 1051 | var iid; 1052 | 1053 | if (json.images) { 1054 | for (iid in json.images) { 1055 | loader._imageRequested++; 1056 | _loadImage(loader.baseUri + json.images[iid].uri, iid, loadImageCallback); 1057 | } 1058 | } 1059 | 1060 | loader._checkComplete(); 1061 | }); 1062 | }; 1063 | 1064 | 1065 | glTFLoader.prototype._postprocess = function () { 1066 | // if there's no plan for progressive loading (streaming) 1067 | // than simply everything should be placed here 1068 | 1069 | // console.log('finish loading all assets, do a second pass postprocess'); 1070 | 1071 | curLoader = this; 1072 | 1073 | var i, leni, j, lenj; 1074 | 1075 | var scene, s; 1076 | var node; 1077 | var mesh, primitive, accessor; 1078 | 1079 | // cameras 1080 | if (this.glTF.cameras) { 1081 | for (i = 0, leni = this.glTF.cameras.length; i < leni; i++) { 1082 | this.glTF.cameras[i] = new Camera(this.glTF.json.cameras[i]); 1083 | } 1084 | } 1085 | 1086 | // bufferviews 1087 | if (this.glTF.bufferViews) { 1088 | for (i = 0, leni = this.glTF.bufferViews.length; i < leni; i++) { 1089 | this.glTF.bufferViews[i] = new BufferView(this.glTF.json.bufferViews[i], this._buffers[ this.glTF.json.bufferViews[i].buffer ]); 1090 | } 1091 | } 1092 | 1093 | // accessors 1094 | if (this.glTF.accessors) { 1095 | for (i = 0, leni = this.glTF.accessors.length; i < leni; i++) { 1096 | this.glTF.accessors[i] = new Accessor(this.glTF.json.accessors[i], this.glTF.bufferViews[ this.glTF.json.accessors[i].bufferView ]); 1097 | } 1098 | } 1099 | 1100 | // load all materials 1101 | if (this.glTF.materials) { 1102 | for (i = 0, leni = this.glTF.materials.length; i < leni; i++) { 1103 | this.glTF.materials[i] = new Material(this.glTF.json.materials[i]); 1104 | } 1105 | } 1106 | 1107 | // load all samplers 1108 | if (this.glTF.samplers) { 1109 | for (i = 0, leni = this.glTF.samplers.length; i < leni; i++) { 1110 | this.glTF.samplers[i] = new Sampler(this.glTF.json.samplers[i]); 1111 | } 1112 | } 1113 | 1114 | // load all textures 1115 | if (this.glTF.textures) { 1116 | for (i = 0, leni = this.glTF.textures.length; i < leni; i++) { 1117 | this.glTF.textures[i] = new Texture(this.glTF.json.textures[i]); 1118 | } 1119 | } 1120 | 1121 | // mesh 1122 | for (i = 0, leni = this.glTF.meshes.length; i < leni; i++) { 1123 | this.glTF.meshes[i] = new Mesh(this.glTF.json.meshes[i], i); 1124 | } 1125 | 1126 | // node 1127 | for (i = 0, leni = this.glTF.nodes.length; i < leni; i++) { 1128 | this.glTF.nodes[i] = new Node(this.glTF.json.nodes[i], i); 1129 | } 1130 | 1131 | // node: hook up children 1132 | for (i = 0, leni = this.glTF.nodes.length; i < leni; i++) { 1133 | node = this.glTF.nodes[i]; 1134 | for (j = 0, lenj = node.children.length; j < lenj; j++) { 1135 | node.children[j] = this.glTF.nodes[ node.children[j] ]; 1136 | } 1137 | } 1138 | 1139 | // scene Bounding box 1140 | var nodeMatrix = new Array(this.glTF.nodes.length); 1141 | for(i = 0, leni = nodeMatrix.length; i < leni; i++) { 1142 | nodeMatrix[i] = __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].create(); 1143 | } 1144 | 1145 | function execUpdateTransform(n, parent) { 1146 | var tmpMat4 = nodeMatrix[n.nodeID]; 1147 | 1148 | if (parent !== null) { 1149 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].mul(tmpMat4, nodeMatrix[parent.nodeID], n.matrix); 1150 | } else { 1151 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["mat4"].copy(tmpMat4, n.matrix); 1152 | } 1153 | } 1154 | 1155 | function execUpdateBBox(n, parent){ 1156 | var tmpMat4 = nodeMatrix[n.nodeID]; 1157 | var parentBVH; 1158 | 1159 | if (parent !== null) { 1160 | parentBVH = parent.bvh; 1161 | } else { 1162 | parentBVH = scene.boundingBox; 1163 | } 1164 | 1165 | if (n.mesh) { 1166 | mesh = n.mesh; 1167 | if (mesh.boundingBox) { 1168 | 1169 | n.aabb = BoundingBox.getAABBFromOBB(mesh.boundingBox, tmpMat4); 1170 | 1171 | if (n.children.length === 0) { 1172 | // n.bvh = n.aabb; 1173 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].copy(n.bvh.min, n.aabb.min); 1174 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].copy(n.bvh.max, n.aabb.max); 1175 | } 1176 | } 1177 | } 1178 | 1179 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].min(parentBVH.min, parentBVH.min, n.bvh.min); 1180 | __WEBPACK_IMPORTED_MODULE_0_gl_matrix__["vec3"].max(parentBVH.max, parentBVH.max, n.bvh.max); 1181 | } 1182 | 1183 | 1184 | for (i = 0, leni = this.glTF.scenes.length; i < leni; i++) { 1185 | scene = this.glTF.scenes[i] = new Scene(this.glTF, this.glTF.json.scenes[i]); 1186 | 1187 | scene.boundingBox = new BoundingBox(); 1188 | 1189 | 1190 | for (j = 0, lenj = scene.nodes.length; j < lenj; j++) { 1191 | node = scene.nodes[j]; 1192 | // node.traverse(null, execUpdateBBox); 1193 | node.traverseTwoExecFun(null, execUpdateTransform, execUpdateBBox); 1194 | } 1195 | 1196 | scene.boundingBox.calculateTransform(); 1197 | } 1198 | 1199 | 1200 | for (j = 0, lenj = this.glTF.nodes.length; j < lenj; j++) { 1201 | node = this.glTF.nodes[j]; 1202 | if (node.bvh !== null) { 1203 | node.bvh.calculateTransform(); 1204 | } 1205 | } 1206 | 1207 | 1208 | 1209 | // load animations (when all accessors are loaded correctly) 1210 | if (this.glTF.animations) { 1211 | for (i = 0, leni = this.glTF.animations.length; i < leni; i++) { 1212 | this.glTF.animations[i] = new Animation(this.glTF, this.glTF.json.animations[i]); 1213 | } 1214 | } 1215 | 1216 | var joints; 1217 | // if (this.glTF.skins) { 1218 | if (this.glTF.json.skins) { 1219 | for (i = 0, leni = this.glTF.skins.length; i < leni; i++) { 1220 | this.glTF.skins[i] = new Skin(this.glTF, this.glTF.json.skins[i], i); 1221 | 1222 | 1223 | joints = this.glTF.skins[i].joints; 1224 | for (j = 0, lenj = joints.length; j < lenj; j++) { 1225 | // this.glTF.nodes[ joints[j] ].jointID = j; 1226 | joints[j].jointID = j; 1227 | } 1228 | } 1229 | } 1230 | 1231 | for (i = 0, leni = this.glTF.nodes.length; i < leni; i++) { 1232 | node = this.glTF.nodes[i]; 1233 | if (node.skin !== null) { 1234 | if (typeof node.skin == 'number') { 1235 | // usual skin, hook up 1236 | node.skin = this.glTF.skins[ node.skin ]; 1237 | } else { 1238 | // assume gl_avatar is in use 1239 | // do nothing 1240 | } 1241 | 1242 | } 1243 | } 1244 | 1245 | 1246 | }; 1247 | 1248 | 1249 | // TODO: get from gl context 1250 | var ComponentType2ByteSize = { 1251 | 5120: 1, // BYTE 1252 | 5121: 1, // UNSIGNED_BYTE 1253 | 5122: 2, // SHORT 1254 | 5123: 2, // UNSIGNED_SHORT 1255 | 5126: 4 // FLOAT 1256 | }; 1257 | 1258 | var Type2NumOfComponent = { 1259 | 'SCALAR': 1, 1260 | 'VEC2': 2, 1261 | 'VEC3': 3, 1262 | 'VEC4': 4, 1263 | 'MAT2': 4, 1264 | 'MAT3': 9, 1265 | 'MAT4': 16 1266 | }; 1267 | 1268 | 1269 | // ------ Scope limited private util functions--------------- 1270 | 1271 | 1272 | // for animation use 1273 | function _arrayBuffer2TypedArray(buffer, byteOffset, countOfComponentType, componentType) { 1274 | switch(componentType) { 1275 | // @todo: finish 1276 | case 5122: return new Int16Array(buffer, byteOffset, countOfComponentType); 1277 | case 5123: return new Uint16Array(buffer, byteOffset, countOfComponentType); 1278 | case 5124: return new Int32Array(buffer, byteOffset, countOfComponentType); 1279 | case 5125: return new Uint32Array(buffer, byteOffset, countOfComponentType); 1280 | case 5126: return new Float32Array(buffer, byteOffset, countOfComponentType); 1281 | default: return null; 1282 | } 1283 | } 1284 | 1285 | function _getAccessorData(accessor) { 1286 | return _arrayBuffer2TypedArray( 1287 | accessor.bufferView.data, 1288 | accessor.byteOffset, 1289 | accessor.count * Type2NumOfComponent[accessor.type], 1290 | accessor.componentType 1291 | ); 1292 | } 1293 | 1294 | function _getBaseUri(uri) { 1295 | 1296 | // https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Core/getBaseUri.js 1297 | 1298 | var basePath = ''; 1299 | var i = uri.lastIndexOf('/'); 1300 | if(i !== -1) { 1301 | basePath = uri.substring(0, i + 1); 1302 | } 1303 | 1304 | return basePath; 1305 | } 1306 | 1307 | function _loadJSON(src, callback) { 1308 | 1309 | // native json loading technique from @KryptoniteDove: 1310 | // http://codepen.io/KryptoniteDove/post/load-json-file-locally-using-pure-javascript 1311 | 1312 | var xobj = new XMLHttpRequest(); 1313 | xobj.overrideMimeType("application/json"); 1314 | xobj.open('GET', src, true); 1315 | xobj.onreadystatechange = function () { 1316 | if (xobj.readyState == 4 && // Request finished, response ready 1317 | xobj.status == "200") { // Status OK 1318 | callback(xobj.responseText, this); 1319 | } 1320 | }; 1321 | xobj.send(null); 1322 | } 1323 | 1324 | function _loadArrayBuffer(url, callback) { 1325 | var xobj = new XMLHttpRequest(); 1326 | xobj.responseType = 'arraybuffer'; 1327 | xobj.open('GET', url, true); 1328 | xobj.onreadystatechange = function () { 1329 | if (xobj.readyState == 4 && // Request finished, response ready 1330 | xobj.status == "200") { // Status OK 1331 | var arrayBuffer = xobj.response; 1332 | if (arrayBuffer && callback) { 1333 | callback(arrayBuffer); 1334 | } 1335 | } 1336 | }; 1337 | xobj.send(null); 1338 | } 1339 | 1340 | function _loadImage(url, iid, onload) { 1341 | var img = new Image(); 1342 | img.crossOrigin = "Anonymous"; 1343 | img.src = url; 1344 | img.onload = function() { 1345 | onload(img, iid); 1346 | }; 1347 | } 1348 | 1349 | // export { MinimalGLTFLoader }; 1350 | 1351 | 1352 | /***/ }), 1353 | /* 1 */ 1354 | /***/ (function(module, exports) { 1355 | 1356 | module.exports = __WEBPACK_EXTERNAL_MODULE_1__; 1357 | 1358 | /***/ }) 1359 | /******/ ]); 1360 | }); -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #cccccc; 3 | font-family: Monospace; 4 | font-size: 13px; 5 | text-align: center; 6 | background-color: #050505; 7 | margin: 0px; 8 | overflow: hidden; 9 | } 10 | 11 | #info { 12 | position: absolute; 13 | top: 0px; 14 | width: 100%; 15 | padding: 5px; 16 | } 17 | 18 | #description { 19 | position: absolute; 20 | top: 20px; 21 | width: 100%; 22 | padding: 5px; 23 | } 24 | 25 | .float { 26 | float: left; 27 | top: 10px; 28 | } 29 | 30 | a { 31 | color: #0080ff; 32 | } 33 | -------------------------------------------------------------------------------- /examples/occlusion-culling-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Occlusion culling with hierarchy demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 | 21 | 22 | 23 |
Occlusion culling with hierarchy demo
24 |

25 | https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter06.html 26 |

27 |
28 | Draw Bounding Box:
29 | Bounding Box type: 34 |
35 | 36 | 52 | 53 | 67 | 68 | 90 | 91 | 108 | 109 | 128 | 129 | 155 | 156 | 177 | 178 | 179 | 827 | 828 | 829 | 830 | 831 | -------------------------------------------------------------------------------- /examples/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #cccccc; 3 | font-family: Monospace; 4 | font-size: 13px; 5 | text-align: center; 6 | background-color: #050505; 7 | margin: 0px; 8 | overflow: hidden; 9 | } 10 | 11 | #info { 12 | position: absolute; 13 | top: 0px; 14 | width: 100%; 15 | padding: 5px; 16 | } 17 | 18 | #description { 19 | position: absolute; 20 | top: 20px; 21 | width: 100%; 22 | padding: 5px; 23 | } 24 | 25 | .float { 26 | float: left; 27 | top: 10px; 28 | } 29 | 30 | a { 31 | color: #0080ff; 32 | } 33 | -------------------------------------------------------------------------------- /examples/utility.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | window.getShaderSource = function(id) { 5 | return document.getElementById(id).textContent.replace(/^\s+|\s+$/g, ''); 6 | }; 7 | 8 | function createShader(gl, source, type) { 9 | var shader = gl.createShader(type); 10 | gl.shaderSource(shader, source); 11 | gl.compileShader(shader); 12 | return shader; 13 | } 14 | 15 | window.createProgram = function(gl, vertexShaderSource, fragmentShaderSource) { 16 | var program = gl.createProgram(); 17 | var vshader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER); 18 | var fshader = createShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER); 19 | gl.attachShader(program, vshader); 20 | gl.deleteShader(vshader); 21 | gl.attachShader(program, fshader); 22 | gl.deleteShader(fshader); 23 | gl.linkProgram(program); 24 | 25 | var log = gl.getProgramInfoLog(program); 26 | if (log) { 27 | console.log(log); 28 | } 29 | 30 | log = gl.getShaderInfoLog(vshader); 31 | if (log) { 32 | console.log(log); 33 | } 34 | 35 | log = gl.getShaderInfoLog(fshader); 36 | if (log) { 37 | console.log(log); 38 | } 39 | 40 | return program; 41 | }; 42 | 43 | window.loadImage = function(url, onload) { 44 | var img = new Image(); 45 | img.src = url; 46 | img.onload = function() { 47 | onload(img); 48 | }; 49 | return img; 50 | }; 51 | 52 | window.loadImages = function(urls, onload) { 53 | var imgs = []; 54 | var imgsToLoad = urls.length; 55 | 56 | function onImgLoad() { 57 | if (--imgsToLoad <= 0) { 58 | onload(imgs); 59 | } 60 | } 61 | 62 | for (var i = 0; i < imgsToLoad; ++i) { 63 | imgs.push(loadImage(urls[i], onImgLoad)); 64 | } 65 | }; 66 | 67 | window.loadObj = function(url, onload) { 68 | var xhr = new XMLHttpRequest(); 69 | xhr.open('GET', url, true); 70 | xhr.responseType = 'text'; 71 | xhr.onload = function(e) { 72 | var mesh = new OBJ.Mesh(this.response); 73 | onload(mesh); 74 | }; 75 | xhr.send(); 76 | }; 77 | })(); 78 | -------------------------------------------------------------------------------- /examples/webgl2-renderer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Minimal-glTF-Loader renderer example 6 | 7 | 8 | 20 | 21 | 22 | 23 |
Minimal-glTF-Loader renderer example WebGL 2 Renderer Example
24 |

25 | An example viewer of the model loaded by Minimal-glTF-Loader 26 |

27 |
28 | glTF Models:
52 | Animations:
53 | Play All Animations:
54 | Draw Bounding Box:
55 | Bounding Box type: 60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /examples/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | // var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 5 | 6 | module.exports = { 7 | entry: { 8 | app: './src/index.js' 9 | }, 10 | resolve: { 11 | alias: { 12 | // Lib: path.resolve(__dirname, 'demo/lib/'), 13 | // Shaders: path.resolve(__dirname, 'demo/lib/src/shaders') 14 | } 15 | }, 16 | output: { 17 | filename: '[name].js', 18 | path: path.resolve(__dirname, '../build'), 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.css$/, 24 | use: [ 25 | 'style-loader', 26 | 'css-loader' 27 | ] 28 | }, 29 | { 30 | test: /\.(png|jpg|gif)$/, 31 | use: [ 32 | 'file-loader' 33 | ] 34 | }, 35 | { 36 | test: /\.(glsl|vs|fs)$/, 37 | use: [ 38 | 'shader-loader' 39 | ] 40 | } 41 | ], 42 | // loaders: [ 43 | // { 44 | // test: /\.glsl$/, 45 | // loader: "webpack-glsl" 46 | // }, 47 | // ] 48 | }, 49 | plugins: [ 50 | // new webpack.optimize.UglifyJsPlugin({ 51 | // compress: { 52 | // warnings: false, 53 | // drop_console: false, 54 | // } 55 | // }) 56 | // new UglifyJSPlugin() 57 | 58 | // new HtmlWebpackPlugin({ 59 | // title: "glAvatar demo" 60 | // }) 61 | ], 62 | devServer: { 63 | contentBase: path.join(__dirname, "demo"), 64 | port: 7777 65 | } 66 | }; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | var jshint = require('gulp-jshint'); 5 | var uglify = require('gulp-uglify'); 6 | var rename = require("gulp-rename"); 7 | var gulp = require('gulp'); 8 | 9 | var jsHintFiles = [ 10 | '**/*.js', 11 | '**/*.html', 12 | '!node_modules/**', 13 | '!src/third-party/**', 14 | '!build/**' 15 | ]; 16 | 17 | // gulp.task('default', function() { 18 | // // place code for your default task here 19 | // }); 20 | 21 | gulp.task('default', ['jsHint']); 22 | 23 | gulp.task('jsHint', function() { 24 | return gulp.src(jsHintFiles) 25 | .pipe(jshint.extract('auto')) 26 | .pipe(jshint()) 27 | .pipe(jshint.reporter('jshint-stylish')) 28 | .pipe(jshint.reporter('fail')); 29 | }); 30 | 31 | gulp.task('jsHint-watch', function() { 32 | gulp.watch(jsHintFiles).on('change', function(event) { 33 | gulp.src(event.path) 34 | .pipe(jshint.extract('auto')) 35 | .pipe(jshint()) 36 | .pipe(jshint.reporter('jshint-stylish')); 37 | }); 38 | }); 39 | 40 | gulp.task('compress', function() { 41 | return gulp.src('src/minimal-gltf-loader.js') 42 | .pipe(uglify()) 43 | .pipe(rename({suffix: '.min'})) 44 | .pipe(gulp.dest('build')); 45 | }); 46 | })(); 47 | -------------------------------------------------------------------------------- /img/drone-pbr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/img/drone-pbr.png -------------------------------------------------------------------------------- /img/drone.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/img/drone.gif -------------------------------------------------------------------------------- /img/helmet-pbr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/img/helmet-pbr.png -------------------------------------------------------------------------------- /img/skin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/img/skin.gif -------------------------------------------------------------------------------- /img/viewer-screenshot-buggy-bbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/img/viewer-screenshot-buggy-bbox.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minimal-gltf-loader", 3 | "version": "0.1.0", 4 | "description": "A minimal JavaScript glTF Loader without need of 3D engines like Three.js.", 5 | "main": "build/minimal-gltf-loader.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "scripts": { 10 | "test": "gulp jsHint", 11 | "build-examples": "webpack --config examples/webpack.config.js", 12 | "build-examples-watch": "webpack --config examples/webpack.config.js --watch", 13 | "build": "webpack", 14 | "build-watch": "webpack --watch", 15 | "dev-server": "node_modules/.bin/webpack-dev-server" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/shrekshao/minimal-gltf-loader.git" 20 | }, 21 | "keywords": [ 22 | "glTF", 23 | "WebGL" 24 | ], 25 | "author": "shrekshao", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/shrekshao/minimal-gltf-loader/issues" 29 | }, 30 | "homepage": "https://github.com/shrekshao/minimal-gltf-loader#readme", 31 | "devDependencies": { 32 | "css-loader": "^0.28.7", 33 | "file-loader": "^0.11.2", 34 | "gulp": "^3.9.1", 35 | "gulp-jshint": "^2.0.1", 36 | "gulp-rename": "^1.2.2", 37 | "gulp-uglify": "^1.5.3", 38 | "jshint": "^2.9.2", 39 | "jshint-stylish": "^2.2.0", 40 | "shader-loader": "^1.3.1", 41 | "style-loader": "^0.18.2", 42 | "webpack": "^3.5.6", 43 | "webpack-glsl-loader": "^1.0.1" 44 | }, 45 | "dependencies": { 46 | "gl-matrix": "^3.4.3" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/minimal-gltf-loader.js: -------------------------------------------------------------------------------- 1 | import {vec3, vec4, quat, mat4} from 'gl-matrix'; 2 | 3 | var MinimalGLTFLoader = MinimalGLTFLoader || {}; 4 | 5 | var globalUniformBlockID = 0; 6 | 7 | var curLoader = null; // @tmp, might be unsafe if loading multiple model at the same time 8 | 9 | var NUM_MAX_JOINTS = 65; 10 | 11 | // Data classes 12 | var Scene = MinimalGLTFLoader.Scene = function (gltf, s) { 13 | this.name = s.name !== undefined ? s.name : null; 14 | this.nodes = new Array(s.nodes.length); // root node object of this scene 15 | for (var i = 0, len = s.nodes.length; i < len; i++) { 16 | this.nodes[i] = gltf.nodes[s.nodes[i]]; 17 | } 18 | 19 | this.extensions = s.extensions !== undefined ? s.extensions : null; 20 | this.extras = s.extras !== undefined ? s.extras : null; 21 | 22 | 23 | this.boundingBox = null; 24 | }; 25 | 26 | /** 27 | * 28 | * @param {vec3} min 29 | * @param {vec3} max 30 | */ 31 | var BoundingBox = MinimalGLTFLoader.BoundingBox = function (min, max, isClone) { 32 | // this.min = min; 33 | // this.max = max; 34 | min = min || vec3.fromValues(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY); 35 | max = max || vec3.fromValues(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY); 36 | 37 | if (isClone === undefined || isClone === true) { 38 | this.min = vec3.clone(min); 39 | this.max = vec3.clone(max); 40 | } else { 41 | this.min = min; 42 | this.max = max; 43 | } 44 | 45 | 46 | this.transform = mat4.create(); 47 | }; 48 | 49 | BoundingBox.prototype.updateBoundingBox = function (bbox) { 50 | vec3.min(this.min, this.min, bbox.min); 51 | vec3.max(this.max, this.max, bbox.max); 52 | }; 53 | 54 | BoundingBox.prototype.calculateTransform = function () { 55 | // transform from a unit cube whose min = (0, 0, 0) and max = (1, 1, 1) 56 | 57 | // scale 58 | this.transform[0] = this.max[0] - this.min[0]; 59 | this.transform[5] = this.max[1] - this.min[1]; 60 | this.transform[10] = this.max[2] - this.min[2]; 61 | // translate 62 | this.transform[12] = this.min[0]; 63 | this.transform[13] = this.min[1]; 64 | this.transform[14] = this.min[2]; 65 | }; 66 | 67 | BoundingBox.getAABBFromOBB = (function() { 68 | var transformRight = vec3.create(); 69 | var transformUp = vec3.create(); 70 | var transformBackward = vec3.create(); 71 | 72 | var tmpVec3a = vec3.create(); 73 | var tmpVec3b = vec3.create(); 74 | 75 | return (function (obb, matrix) { 76 | vec3.set(transformRight, matrix[0], matrix[1], matrix[2]); 77 | vec3.set(transformUp, matrix[4], matrix[5], matrix[6]); 78 | vec3.set(transformBackward, matrix[8], matrix[9], matrix[10]); 79 | 80 | var min = vec3.fromValues(matrix[12], matrix[13], matrix[14]); // init with matrix translation 81 | var max = vec3.clone(min); 82 | 83 | vec3.scale(tmpVec3a, transformRight, obb.min[0]); 84 | vec3.scale(tmpVec3b, transformRight, obb.max[0]); 85 | vec3.min(transformRight, tmpVec3a, tmpVec3b); 86 | vec3.add(min, min, transformRight); 87 | vec3.max(transformRight, tmpVec3a, tmpVec3b); 88 | vec3.add(max, max, transformRight); 89 | 90 | vec3.scale(tmpVec3a, transformUp, obb.min[1]); 91 | vec3.scale(tmpVec3b, transformUp, obb.max[1]); 92 | vec3.min(transformUp, tmpVec3a, tmpVec3b); 93 | vec3.add(min, min, transformUp); 94 | vec3.max(transformUp, tmpVec3a, tmpVec3b); 95 | vec3.add(max, max, transformUp); 96 | 97 | vec3.scale(tmpVec3a, transformBackward, obb.min[2]); 98 | vec3.scale(tmpVec3b, transformBackward, obb.max[2]); 99 | vec3.min(transformBackward, tmpVec3a, tmpVec3b); 100 | vec3.add(min, min, transformBackward); 101 | vec3.max(transformBackward, tmpVec3a, tmpVec3b); 102 | vec3.add(max, max, transformBackward); 103 | 104 | var bbox = new BoundingBox(min, max, false); 105 | bbox.calculateTransform(); 106 | return bbox; 107 | }); 108 | })(); 109 | 110 | 111 | 112 | var Accessor = MinimalGLTFLoader.Accessor = function (a, bufferViewObject) { 113 | this.bufferView = bufferViewObject; 114 | this.componentType = a.componentType; // required 115 | this.byteOffset = a.byteOffset !== undefined ? a.byteOffset : 0; 116 | this.byteStride = bufferViewObject.byteStride; 117 | this.normalized = a.normalized !== undefined ? a.normalized : false; 118 | this.count = a.count; // required 119 | this.type = a.type; // required 120 | this.size = Type2NumOfComponent[this.type]; 121 | 122 | this.min = a.min; // @tmp assume required for now (for bbox) 123 | this.max = a.max; // @tmp assume required for now (for bbox) 124 | 125 | this.extensions = a.extensions !== undefined ? a.extensions : null; 126 | this.extras = a.extras !== undefined ? a.extras : null; 127 | }; 128 | 129 | Accessor.prototype.prepareVertexAttrib = function(location, gl) { 130 | gl.vertexAttribPointer( 131 | location, 132 | this.size, 133 | this.componentType, 134 | this.normalized, 135 | this.byteStride, 136 | this.byteOffset 137 | ); 138 | gl.enableVertexAttribArray(location); 139 | }; 140 | 141 | var BufferView = MinimalGLTFLoader.BufferView = function(bf, bufferData) { 142 | this.byteLength = bf.byteLength; //required 143 | this.byteOffset = bf.byteOffset !== undefined ? bf.byteOffset : 0; 144 | this.byteStride = bf.byteStride !== undefined ? bf.byteStride : 0; 145 | this.target = bf.target !== undefined ? bf.target : null; 146 | 147 | this.data = bufferData.slice(this.byteOffset, this.byteOffset + this.byteLength); 148 | 149 | this.extensions = bf.extensions !== undefined ? bf.extensions : null; 150 | this.extras = bf.extras !== undefined ? bf.extras : null; 151 | 152 | // runtime stuffs ------------- 153 | this.buffer = null; // gl buffer 154 | }; 155 | 156 | BufferView.prototype.createBuffer = function(gl) { 157 | this.buffer = gl.createBuffer(); 158 | }; 159 | 160 | BufferView.prototype.bindData = function(gl) { 161 | if (this.target) { 162 | gl.bindBuffer(this.target, this.buffer); 163 | gl.bufferData(this.target, this.data, gl.STATIC_DRAW); 164 | gl.bindBuffer(this.target, null); 165 | return true; 166 | } 167 | return false; 168 | }; 169 | 170 | 171 | var Camera = MinimalGLTFLoader.Camera = function(c) { 172 | this.name = c.name !== undefined ? c.name : null; 173 | this.type = c.type; // required 174 | 175 | this.othographic = c.othographic === undefined ? null : c.othographic; // every attribute inside is required (excluding extensions) 176 | this.perspective = c.perspective === undefined ? null : { 177 | yfov: c.perspective.yfov, 178 | znear: c.perspective.znear, 179 | zfar: c.perspective.zfar !== undefined ? c.perspective.zfar : null, 180 | aspectRatio: c.perspective.aspectRatio !== undefined ? c.perspective.aspectRatio : null 181 | }; 182 | 183 | this.extensions = c.extensions !== undefined ? c.extensions : null; 184 | this.extras = c.extras !== undefined ? c.extras : null; 185 | }; 186 | 187 | 188 | 189 | var Node = MinimalGLTFLoader.Node = function (n, nodeID) { 190 | this.name = n.name !== undefined ? n.name : null; 191 | this.nodeID = nodeID; 192 | // TODO: camera 193 | this.camera = n.camera !== undefined ? n.camera : null; 194 | 195 | this.matrix = mat4.create(); 196 | if (n.hasOwnProperty('matrix')) { 197 | for(var i = 0; i < 16; ++i) { 198 | this.matrix[i] = n.matrix[i]; 199 | } 200 | 201 | this.translation = vec3.create(); 202 | mat4.getTranslation(this.translation, this.matrix); 203 | 204 | this.rotation = quat.create(); 205 | mat4.getRotation(this.rotation, this.matrix); 206 | 207 | this.scale = vec3.create(); 208 | mat4.getScaling(this.scale, this.matrix); 209 | } else { 210 | // this.translation = null; 211 | // this.rotation = null; 212 | // this.scale = null; 213 | this.getTransformMatrixFromTRS(n.translation, n.rotation, n.scale); 214 | } 215 | 216 | 217 | 218 | 219 | this.children = n.children || []; // init as id, then hook up to node object later 220 | this.mesh = n.mesh !== undefined ? curLoader.glTF.meshes[n.mesh] : null; 221 | 222 | this.skin = n.skin !== undefined ? n.skin : null; // init as id, then hook up to skin object later 223 | 224 | if (n.extensions !== undefined) { 225 | if (n.extensions.gl_avatar !== undefined && curLoader.enableGLAvatar === true) { 226 | var linkedSkinID = curLoader.skeletonGltf.json.extensions.gl_avatar.skins[ n.extensions.gl_avatar.skin.name ]; 227 | var linkedSkin = curLoader.skeletonGltf.skins[linkedSkinID]; 228 | this.skin = new SkinLink(curLoader.glTF, linkedSkin, n.extensions.gl_avatar.skin.inverseBindMatrices); 229 | } 230 | } 231 | 232 | 233 | 234 | // TODO: morph targets weights 235 | this.weights = n.weights !== undefined ? n.weights : null; 236 | 237 | 238 | this.extensions = n.extensions !== undefined ? n.extensions : null; 239 | this.extras = n.extras !== undefined ? n.extras : null; 240 | 241 | // runtime stuffs-------------- 242 | 243 | this.aabb = null; // axis aligned bounding box, not need to apply node transform to aabb 244 | this.bvh = new BoundingBox(); 245 | }; 246 | 247 | Node.prototype.traverse = function(parent, executeFunc) { 248 | executeFunc(this, parent); 249 | for (var i = 0, len = this.children.length; i < len; i++) { 250 | this.children[i].traverse(this, executeFunc); 251 | } 252 | }; 253 | 254 | Node.prototype.traversePostOrder = function(parent, executeFunc) { 255 | for (var i = 0, len = this.children.length; i < len; i++) { 256 | this.children[i].traversePostOrder(this, executeFunc); 257 | } 258 | executeFunc(this, parent); 259 | }; 260 | 261 | Node.prototype.traverseTwoExecFun = function(parent, execFunPre, execFunPos) { 262 | execFunPre(this, parent); 263 | for (var i = 0, len = this.children.length; i < len; i++) { 264 | this.children[i].traverseTwoExecFun(this, execFunPre, execFunPos); 265 | } 266 | execFunPos(this, parent); 267 | }; 268 | 269 | var TRSMatrix = mat4.create(); 270 | 271 | Node.prototype.getTransformMatrixFromTRS = function(translation, rotation, scale) { 272 | 273 | this.translation = translation !== undefined ? vec3.fromValues(translation[0], translation[1], translation[2]) : vec3.fromValues(0, 0, 0); 274 | this.rotation = rotation !== undefined ? vec4.fromValues(rotation[0], rotation[1], rotation[2], rotation[3]) : vec4.fromValues(0, 0, 0, 1); 275 | this.scale = scale !== undefined ? vec3.fromValues(scale[0], scale[1], scale[2]) : vec3.fromValues(1, 1, 1); 276 | 277 | this.updateMatrixFromTRS(); 278 | }; 279 | 280 | Node.prototype.updateMatrixFromTRS = function() { 281 | mat4.fromRotationTranslation(TRSMatrix, this.rotation, this.translation); 282 | mat4.scale(this.matrix, TRSMatrix, this.scale); 283 | }; 284 | 285 | 286 | 287 | var Mesh = MinimalGLTFLoader.Mesh = function (m, meshID) { 288 | this.meshID = meshID; 289 | this.name = m.name !== undefined ? m.name : null; 290 | 291 | this.primitives = []; // required 292 | 293 | 294 | 295 | // bounding box (runtime stuff) 296 | this.boundingBox = null; 297 | 298 | var p, primitive, accessor; 299 | 300 | for (var i = 0, len = m.primitives.length; i < len; ++i) { 301 | p = m.primitives[i]; 302 | primitive = new Primitive(curLoader.glTF, p); 303 | this.primitives.push(primitive); 304 | 305 | // bounding box related 306 | if (primitive.boundingBox) { 307 | if (!this.boundingBox) { 308 | this.boundingBox = new BoundingBox(); 309 | } 310 | this.boundingBox.updateBoundingBox(primitive.boundingBox); 311 | } 312 | } 313 | 314 | if (this.boundingBox) { 315 | this.boundingBox.calculateTransform(); 316 | } 317 | 318 | 319 | // TODO: weights for morph targets 320 | this.weights = m.weights !== undefined ? m.weights : null; 321 | 322 | this.extensions = m.extensions !== undefined ? m.extensions : null; 323 | this.extras = m.extras !== undefined ? m.extras : null; 324 | 325 | }; 326 | 327 | var Primitive = MinimalGLTFLoader.Primitive = function (gltf, p) { 328 | // , required 329 | // get hook up with accessor object in _postprocessing 330 | this.attributes = p.attributes; 331 | this.indices = p.indices !== undefined ? p.indices : null; // accessor id 332 | 333 | var attname; 334 | if (p.extensions !== undefined) { 335 | if (p.extensions.gl_avatar !== undefined && curLoader.enableGLAvatar === true) { 336 | if (p.extensions.gl_avatar.attributes) { 337 | for ( attname in p.extensions.gl_avatar.attributes ) { 338 | this.attributes[attname] = p.extensions.gl_avatar.attributes[attname]; 339 | } 340 | } 341 | } 342 | } 343 | 344 | 345 | if (this.indices !== null) { 346 | this.indicesComponentType = gltf.json.accessors[this.indices].componentType; 347 | this.indicesLength = gltf.json.accessors[this.indices].count; 348 | this.indicesOffset = (gltf.json.accessors[this.indices].byteOffset || 0); 349 | } else { 350 | // assume 'POSITION' is there 351 | this.drawArraysCount = gltf.json.accessors[this.attributes.POSITION].count; 352 | this.drawArraysOffset = (gltf.json.accessors[this.attributes.POSITION].byteOffset || 0); 353 | } 354 | 355 | 356 | // hook up accessor object 357 | for ( attname in this.attributes ) { 358 | this.attributes[attname] = gltf.accessors[ this.attributes[attname] ]; 359 | } 360 | 361 | 362 | this.material = p.material !== undefined ? gltf.materials[p.material] : null; 363 | 364 | 365 | this.mode = p.mode !== undefined ? p.mode : 4; // default: gl.TRIANGLES 366 | 367 | 368 | 369 | // morph related 370 | this.targets = p.targets; 371 | 372 | 373 | this.extensions = p.extensions !== undefined ? p.extensions : null; 374 | this.extras = p.extras !== undefined ? p.extras : null; 375 | 376 | 377 | // ----gl run time related 378 | this.vertexArray = null; //vao 379 | 380 | this.vertexBuffer = null; 381 | this.indexBuffer = null; 382 | 383 | 384 | this.shader = null; 385 | 386 | 387 | this.boundingBox = null; 388 | if (this.attributes.POSITION !== undefined) { 389 | var accessor = this.attributes.POSITION; 390 | if (accessor.max) { 391 | // @todo: handle cases where no min max are provided 392 | 393 | // assume vec3 394 | if (accessor.type === 'VEC3') { 395 | this.boundingBox = new BoundingBox( 396 | vec3.fromValues(accessor.min[0], accessor.min[1], accessor.min[2]), 397 | vec3.fromValues(accessor.max[0], accessor.max[1], accessor.max[2]), 398 | false 399 | ); 400 | this.boundingBox.calculateTransform(); 401 | 402 | 403 | 404 | } 405 | 406 | } 407 | } 408 | }; 409 | 410 | 411 | var Texture = MinimalGLTFLoader.Texture = function (t) { 412 | this.name = t.name !== undefined ? t.name : null; 413 | this.sampler = t.sampler !== undefined ? curLoader.glTF.samplers[t.sampler] : null; 414 | this.source = t.source !== undefined ? curLoader.glTF.images[t.source] : null; 415 | 416 | this.extensions = t.extensions !== undefined ? t.extensions : null; 417 | this.extras = t.extras !== undefined ? t.extras : null; 418 | 419 | // runtime 420 | this.texture = null; 421 | }; 422 | 423 | Texture.prototype.createTexture = function(gl) { 424 | this.texture = gl.createTexture(); 425 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 426 | gl.texImage2D( 427 | gl.TEXTURE_2D, // assumed 428 | 0, // Level of details 429 | // gl.RGB, // Format 430 | // gl.RGB, 431 | gl.RGBA, // Format 432 | gl.RGBA, 433 | gl.UNSIGNED_BYTE, // Size of each channel 434 | this.source 435 | ); 436 | gl.generateMipmap(gl.TEXTURE_2D); 437 | gl.bindTexture(gl.TEXTURE_2D, null); 438 | }; 439 | 440 | var Sampler = MinimalGLTFLoader.Sampler = function (s) { 441 | this.name = s.name !== undefined ? s.name : null; 442 | this.magFilter = s.magFilter !== undefined ? s.magFilter : null; 443 | this.minFilter = s.minFilter !== undefined ? s.minFilter : null; 444 | this.wrapS = s.wrapS !== undefined ? s.wrapS : 10497; 445 | this.wrapT = s.wrapT !== undefined ? s.wrapT : 10497; 446 | 447 | this.extensions = s.extensions !== undefined ? s.extensions : null; 448 | this.extras = s.extras !== undefined ? s.extras : null; 449 | 450 | this.sampler = null; 451 | }; 452 | 453 | Sampler.prototype.createSampler = function(gl) { 454 | this.sampler = gl.createSampler(); 455 | if (this.minFilter) { 456 | gl.samplerParameteri(this.sampler, gl.TEXTURE_MIN_FILTER, this.minFilter); 457 | } else { 458 | gl.samplerParameteri(this.sampler, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR); 459 | } 460 | if (this.magFilter) { 461 | gl.samplerParameteri(this.sampler, gl.TEXTURE_MAG_FILTER, this.magFilter); 462 | } else { 463 | gl.samplerParameteri(this.sampler, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 464 | } 465 | gl.samplerParameteri(this.sampler, gl.TEXTURE_WRAP_S, this.wrapS); 466 | gl.samplerParameteri(this.sampler, gl.TEXTURE_WRAP_T, this.wrapT); 467 | }; 468 | 469 | // Sampler.prototype.bindSampler = function(i, gl) { 470 | // gl.bindSampler(i, this.sampler); 471 | // } 472 | 473 | var TextureInfo = MinimalGLTFLoader.TextureInfo = function (json) { 474 | this.index = json.index; 475 | this.texCoord = json.texCoord !== undefined ? json.texCoord : 0 ; 476 | 477 | this.extensions = json.extensions !== undefined ? json.extensions : null; 478 | this.extras = json.extras !== undefined ? json.extras : null; 479 | }; 480 | 481 | var PbrMetallicRoughness = MinimalGLTFLoader.PbrMetallicRoughness = function (json) { 482 | this.baseColorFactor = json.baseColorFactor !== undefined ? json.baseColorFactor : [1, 1, 1, 1]; 483 | this.baseColorTexture = json.baseColorTexture !== undefined ? new TextureInfo(json.baseColorTexture): null; 484 | this.metallicFactor = json.metallicFactor !== undefined ? json.metallicFactor : 1 ; 485 | this.roughnessFactor = json.roughnessFactor !== undefined ? json.roughnessFactor : 1 ; 486 | this.metallicRoughnessTexture = json.metallicRoughnessTexture !== undefined ? new TextureInfo(json.metallicRoughnessTexture): null; 487 | 488 | this.extensions = json.extensions !== undefined ? json.extensions : null; 489 | this.extras = json.extras !== undefined ? json.extras : null; 490 | }; 491 | 492 | var NormalTextureInfo = MinimalGLTFLoader.NormalTextureInfo = function (json) { 493 | this.index = json.index; 494 | this.texCoord = json.texCoord !== undefined ? json.texCoord : 0 ; 495 | this.scale = json.scale !== undefined ? json.scale : 1 ; 496 | 497 | this.extensions = json.extensions !== undefined ? json.extensions : null; 498 | this.extras = json.extras !== undefined ? json.extras : null; 499 | }; 500 | 501 | var OcclusionTextureInfo = MinimalGLTFLoader.OcclusionTextureInfo = function (json) { 502 | this.index = json.index; 503 | this.texCoord = json.texCoord !== undefined ? json.texCoord : 0 ; 504 | this.strength = json.strength !== undefined ? json.strength : 1 ; 505 | 506 | this.extensions = json.extensions !== undefined ? json.extensions : null; 507 | this.extras = json.extras !== undefined ? json.extras : null; 508 | }; 509 | 510 | var Material = MinimalGLTFLoader.Material = function (m) { 511 | this.name = m.name !== undefined ? m.name : null; 512 | 513 | this.pbrMetallicRoughness = m.pbrMetallicRoughness !== undefined ? new PbrMetallicRoughness( m.pbrMetallicRoughness ) : new PbrMetallicRoughness({ 514 | baseColorFactor: [1, 1, 1, 1], 515 | metallicFactor: 1, 516 | metallicRoughnessTexture: 1 517 | }); 518 | // this.normalTexture = m.normalTexture !== undefined ? m.normalTexture : null; 519 | this.normalTexture = m.normalTexture !== undefined ? new NormalTextureInfo(m.normalTexture) : null; 520 | this.occlusionTexture = m.occlusionTexture !== undefined ? new OcclusionTextureInfo(m.occlusionTexture) : null; 521 | this.emissiveTexture = m.emissiveTexture !== undefined ? new TextureInfo(m.emissiveTexture) : null; 522 | 523 | this.emissiveFactor = m.emissiveFactor !== undefined ? m.emissiveFactor : [0, 0, 0]; 524 | this.alphaMode = m.alphaMode !== undefined ? m.alphaMode : "OPAQUE"; 525 | this.alphaCutoff = m.alphaCutoff !== undefined ? m.alphaCutoff : 0.5; 526 | this.doubleSided = m.doubleSided || false; 527 | 528 | this.extensions = m.extensions !== undefined ? m.extensions : null; 529 | this.extras = m.extras !== undefined ? m.extras : null; 530 | }; 531 | 532 | 533 | var Skin = MinimalGLTFLoader.Skin = function (gltf, s, skinID) { 534 | this.name = s.name !== undefined ? s.name : null; 535 | this.skinID = skinID; 536 | 537 | this.joints = new Array(s.joints.length); // required 538 | var i, len; 539 | for (i = 0, len = this.joints.length; i < len; i++) { 540 | this.joints[i] = gltf.nodes[s.joints[i]]; 541 | } 542 | 543 | this.skeleton = s.skeleton !== undefined ? gltf.nodes[s.skeleton] : null; 544 | this.inverseBindMatrices = s.inverseBindMatrices !== undefined ? gltf.accessors[s.inverseBindMatrices] : null; 545 | 546 | this.extensions = s.extensions !== undefined ? s.extensions : null; 547 | this.extras = s.extras !== undefined ? s.extras : null; 548 | 549 | 550 | // @tmp: runtime stuff should be taken care of renderer 551 | // since glTF model should only store info 552 | // runtime can have multiple instances of this glTF models 553 | this.uniformBlockID = globalUniformBlockID++; 554 | 555 | if (this.inverseBindMatrices) { 556 | // should be a mat4 557 | this.inverseBindMatricesData = _getAccessorData(this.inverseBindMatrices); 558 | // this.inverseBindMatricesMat4 = mat4.fromValues(this.inverseBindMatricesData); 559 | 560 | this.inverseBindMatrix = []; // for calculation 561 | this.jointMatrixUniformBuffer = null; 562 | // this.jointMatrixUnidormBufferData = _arrayBuffer2TypedArray( 563 | // this.inverseBindMatricesData, 564 | // 0, 565 | // this.inverseBindMatricesData.length, 566 | // this.inverseBindMatrices.componentType 567 | // ); // for copy to UBO 568 | 569 | // @tmp: fixed length to coordinate with shader, for copy to UBO 570 | this.jointMatrixUnidormBufferData = new Float32Array(NUM_MAX_JOINTS * 16); 571 | 572 | for (i = 0, len = this.inverseBindMatricesData.length; i < len; i += 16) { 573 | this.inverseBindMatrix.push(mat4.fromValues( 574 | this.inverseBindMatricesData[i], 575 | this.inverseBindMatricesData[i + 1], 576 | this.inverseBindMatricesData[i + 2], 577 | this.inverseBindMatricesData[i + 3], 578 | this.inverseBindMatricesData[i + 4], 579 | this.inverseBindMatricesData[i + 5], 580 | this.inverseBindMatricesData[i + 6], 581 | this.inverseBindMatricesData[i + 7], 582 | this.inverseBindMatricesData[i + 8], 583 | this.inverseBindMatricesData[i + 9], 584 | this.inverseBindMatricesData[i + 10], 585 | this.inverseBindMatricesData[i + 11], 586 | this.inverseBindMatricesData[i + 12], 587 | this.inverseBindMatricesData[i + 13], 588 | this.inverseBindMatricesData[i + 14], 589 | this.inverseBindMatricesData[i + 15] 590 | )); 591 | } 592 | } 593 | 594 | }; 595 | 596 | var SkinLink = MinimalGLTFLoader.SkinLink = function (gltf, linkedSkin, inverseBindMatricesAccessorID) { 597 | this.isLink = true; 598 | 599 | if (!gltf.skins) { 600 | gltf.skins = []; 601 | } 602 | gltf.skins.push(this); 603 | 604 | this.name = linkedSkin.name; 605 | // this.skinID = linkedSkin.skinID; // use this for uniformblock id 606 | // this.skinID = gltf.skins.length - 1; 607 | // this.skinID = curLoader.skeletonGltf.skins.length + gltf.skins.length - 1; 608 | this.skinID = gltf.skins.length - 1; 609 | 610 | this.joints = linkedSkin.joints; 611 | 612 | this.skeleton = linkedSkin.skeleton; 613 | this.inverseBindMatrices = inverseBindMatricesAccessorID !== undefined ? gltf.accessors[inverseBindMatricesAccessorID] : null; 614 | 615 | // @tmp: runtime stuff should be taken care of renderer 616 | // since glTF model should only store info 617 | // runtime can have multiple instances of this glTF models 618 | this.uniformBlockID = globalUniformBlockID++; 619 | if (this.inverseBindMatrices) { 620 | // should be a mat4 621 | this.inverseBindMatricesData = _getAccessorData(this.inverseBindMatrices); 622 | // this.inverseBindMatricesMat4 = mat4.fromValues(this.inverseBindMatricesData); 623 | 624 | this.inverseBindMatrix = []; // for calculation 625 | this.jointMatrixUniformBuffer = null; 626 | // this.jointMatrixUnidormBufferData = _arrayBuffer2TypedArray( 627 | // this.inverseBindMatricesData, 628 | // 0, 629 | // this.inverseBindMatricesData.length, 630 | // this.inverseBindMatrices.componentType 631 | // ); // for copy to UBO 632 | 633 | // @tmp: fixed length to coordinate with shader, for copy to UBO 634 | this.jointMatrixUnidormBufferData = new Float32Array(NUM_MAX_JOINTS * 16); 635 | 636 | for (var i = 0, len = this.inverseBindMatricesData.length; i < len; i += 16) { 637 | this.inverseBindMatrix.push(mat4.fromValues( 638 | this.inverseBindMatricesData[i], 639 | this.inverseBindMatricesData[i + 1], 640 | this.inverseBindMatricesData[i + 2], 641 | this.inverseBindMatricesData[i + 3], 642 | this.inverseBindMatricesData[i + 4], 643 | this.inverseBindMatricesData[i + 5], 644 | this.inverseBindMatricesData[i + 6], 645 | this.inverseBindMatricesData[i + 7], 646 | this.inverseBindMatricesData[i + 8], 647 | this.inverseBindMatricesData[i + 9], 648 | this.inverseBindMatricesData[i + 10], 649 | this.inverseBindMatricesData[i + 11], 650 | this.inverseBindMatricesData[i + 12], 651 | this.inverseBindMatricesData[i + 13], 652 | this.inverseBindMatricesData[i + 14], 653 | this.inverseBindMatricesData[i + 15] 654 | )); 655 | } 656 | } 657 | 658 | 659 | 660 | }; 661 | 662 | 663 | 664 | 665 | // animation has no potential plan for progressive rendering I guess 666 | // so everything happens after all buffers are loaded 667 | 668 | var Target = MinimalGLTFLoader.Target = function (t) { 669 | this.nodeID = t.node !== undefined ? t.node : null ; //id, to be hooked up to object later 670 | this.path = t.path; //required, string 671 | 672 | this.extensions = t.extensions !== undefined ? t.extensions : null; 673 | this.extras = t.extras !== undefined ? t.extras : null; 674 | }; 675 | 676 | var Channel = MinimalGLTFLoader.Channel = function (c, animation) { 677 | this.sampler = animation.samplers[c.sampler]; //required 678 | this.target = new Target(c.target); //required 679 | 680 | this.extensions = c.extensions !== undefined ? c.extensions : null; 681 | this.extras = c.extras !== undefined ? c.extras : null; 682 | }; 683 | 684 | var AnimationSampler = MinimalGLTFLoader.AnimationSampler = function (gltf, s) { 685 | this.input = gltf.accessors[s.input]; //required, accessor object 686 | this.output = gltf.accessors[s.output]; //required, accessor object 687 | 688 | this.inputTypedArray = _getAccessorData(this.input); 689 | this.outputTypedArray = _getAccessorData(this.output); 690 | 691 | 692 | // "LINEAR" 693 | // "STEP" 694 | // "CATMULLROMSPLINE" 695 | // "CUBICSPLINE" 696 | this.interpolation = s.interpolation !== undefined ? s.interpolation : 'LINEAR' ; 697 | 698 | 699 | this.extensions = s.extensions !== undefined ? s.extensions : null; 700 | this.extras = s.extras !== undefined ? s.extras : null; 701 | 702 | // ------- extra runtime info ----------- 703 | // runtime status thing 704 | this.curIdx = 0; 705 | // this.curValue = 0; 706 | this.curValue = vec4.create(); 707 | this.endT = this.inputTypedArray[this.inputTypedArray.length - 1]; 708 | this.inputMax = this.endT - this.inputTypedArray[0]; 709 | }; 710 | 711 | var animationOutputValueVec4a = vec4.create(); 712 | var animationOutputValueVec4b = vec4.create(); 713 | 714 | AnimationSampler.prototype.getValue = function (t) { 715 | if (t > this.endT) { 716 | t -= this.inputMax * Math.ceil((t - this.endT) / this.inputMax); 717 | this.curIdx = 0; 718 | } 719 | 720 | var len = this.inputTypedArray.length; 721 | while (this.curIdx <= len - 2 && t >= this.inputTypedArray[this.curIdx + 1]) { 722 | this.curIdx++; 723 | } 724 | 725 | 726 | if (this.curIdx >= len - 1) { 727 | // loop 728 | t -= this.inputMax; 729 | this.curIdx = 0; 730 | } 731 | 732 | // @tmp: assume no stride 733 | var count = Type2NumOfComponent[this.output.type]; 734 | 735 | var v4lerp = count === 4 ? quat.slerp: vec4.lerp; 736 | 737 | var i = this.curIdx; 738 | var o = i * count; 739 | var on = o + count; 740 | 741 | var u = Math.max( 0, t - this.inputTypedArray[i] ) / (this.inputTypedArray[i+1] - this.inputTypedArray[i]); 742 | 743 | for (var j = 0; j < count; j++ ) { 744 | animationOutputValueVec4a[j] = this.outputTypedArray[o + j]; 745 | animationOutputValueVec4b[j] = this.outputTypedArray[on + j]; 746 | } 747 | 748 | switch(this.interpolation) { 749 | case 'LINEAR': 750 | v4lerp(this.curValue, animationOutputValueVec4a, animationOutputValueVec4b, u); 751 | break; 752 | 753 | default: 754 | break; 755 | } 756 | }; 757 | 758 | 759 | 760 | var Animation = MinimalGLTFLoader.Animation = function (gltf, a) { 761 | this.name = a.name !== undefined ? a.name : null; 762 | 763 | var i, len; 764 | 765 | 766 | 767 | this.samplers = []; // required, array of animation sampler 768 | 769 | for (i = 0, len = a.samplers.length; i < len; i++) { 770 | this.samplers[i] = new AnimationSampler(gltf, a.samplers[i]); 771 | } 772 | 773 | this.channels = []; //required, array of channel 774 | 775 | for (i = 0, len = a.channels.length; i < len; i++) { 776 | this.channels[i] = new Channel(a.channels[i], this); 777 | } 778 | 779 | this.extensions = a.extensions !== undefined ? a.extensions : null; 780 | this.extras = a.extras !== undefined ? a.extras : null; 781 | }; 782 | 783 | 784 | /** 785 | * 786 | */ 787 | var glTFModel = MinimalGLTFLoader.glTFModel = function (gltf) { 788 | this.json = gltf; 789 | this.defaultScene = gltf.scene !== undefined ? gltf.scene : 0; 790 | 791 | this.version = Number(gltf.asset.version); 792 | 793 | if (gltf.accessors) { 794 | this.accessors = new Array(gltf.accessors.length); 795 | } 796 | 797 | if (gltf.bufferViews) { 798 | this.bufferViews = new Array(gltf.bufferViews.length); 799 | } 800 | 801 | if (gltf.scenes) { 802 | this.scenes = new Array(gltf.scenes.length); // store Scene object 803 | } 804 | 805 | if (gltf.nodes) { 806 | this.nodes = new Array(gltf.nodes.length); // store Node object 807 | } 808 | 809 | if (gltf.meshes) { 810 | this.meshes = new Array(gltf.meshes.length); // store mesh object 811 | } 812 | 813 | if (gltf.materials) { 814 | this.materials = new Array(gltf.materials.length); // store material object 815 | } 816 | 817 | if (gltf.textures) { 818 | this.textures = new Array(gltf.textures.length); 819 | } 820 | 821 | if (gltf.samplers) { 822 | this.samplers = new Array(gltf.samplers.length); 823 | } 824 | 825 | if (gltf.images) { 826 | this.images = new Array(gltf.images.length); 827 | } 828 | 829 | 830 | if (gltf.skins) { 831 | this.skins = new Array(gltf.skins.length); 832 | } 833 | 834 | if (gltf.animations) { 835 | this.animations = new Array(gltf.animations.length); 836 | } 837 | 838 | if (gltf.cameras) { 839 | this.cameras = new Array(gltf.cameras.length); 840 | } 841 | 842 | this.extensions = gltf.extensions !== undefined ? gltf.extensions : null; 843 | this.extras = gltf.extras !== undefined ? gltf.extras : null; 844 | 845 | }; 846 | 847 | 848 | 849 | var gl; 850 | 851 | var glTFLoader = MinimalGLTFLoader.glTFLoader = function (glContext) { 852 | gl = glContext !== undefined ? glContext : null; 853 | this._init(); 854 | this.glTF = null; 855 | 856 | this.enableGLAvatar = false; 857 | this.linkSkeletonGltf = null; 858 | }; 859 | 860 | glTFLoader.prototype._init = function() { 861 | this._loadDone = false; 862 | 863 | this._bufferRequested = 0; 864 | this._bufferLoaded = 0; 865 | this._buffers = []; 866 | this._bufferTasks = {}; 867 | 868 | this._shaderRequested = 0; 869 | this._shaderLoaded = 0; 870 | 871 | this._imageRequested = 0; 872 | this._imageLoaded = 0; 873 | 874 | this._pendingTasks = 0; 875 | this._finishedPendingTasks = 0; 876 | 877 | this.onload = null; 878 | 879 | curLoader = this; 880 | }; 881 | 882 | 883 | glTFLoader.prototype._checkComplete = function () { 884 | if (this._bufferRequested == this._bufferLoaded && 885 | // this._shaderRequested == this._shaderLoaded && 886 | this._imageRequested == this._imageLoaded 887 | // && other resources finish loading 888 | ) { 889 | this._loadDone = true; 890 | } 891 | 892 | if (this._loadDone && this._pendingTasks == this._finishedPendingTasks) { 893 | 894 | this._postprocess(); 895 | 896 | this.onload(this.glTF); 897 | } 898 | }; 899 | 900 | glTFLoader.prototype.loadGLTF_GL_Avatar_Skin = function (uri, skeletonGltf, callback) { 901 | this.enableGLAvatar = true; 902 | this.skeletonGltf = skeletonGltf; 903 | 904 | this.loadGLTF(uri, callback); 905 | }; 906 | 907 | /** 908 | * load a glTF model 909 | * 910 | * @param {String} uri uri of the .glTF file. Other resources (bins, images) are assumed to be in the same base path 911 | * @param {Function} callback the onload callback function 912 | */ 913 | glTFLoader.prototype.loadGLTF = function (uri, callback) { 914 | 915 | this._init(); 916 | 917 | this.onload = callback || function(glTF) { 918 | console.log('glTF model loaded.'); 919 | console.log(glTF); 920 | }; 921 | 922 | 923 | this.baseUri = _getBaseUri(uri); 924 | 925 | var loader = this; 926 | 927 | _loadJSON(uri, function (response) { 928 | // Parse JSON string into object 929 | var json = JSON.parse(response); 930 | 931 | loader.glTF = new glTFModel(json); 932 | 933 | var bid; 934 | 935 | var loadArrayBufferCallback = function (resource) { 936 | 937 | loader._buffers[bid] = resource; 938 | loader._bufferLoaded++; 939 | if (loader._bufferTasks[bid]) { 940 | var i,len; 941 | for (i = 0, len = loader._bufferTasks[bid].length; i < len; ++i) { 942 | (loader._bufferTasks[bid][i])(resource); 943 | } 944 | } 945 | loader._checkComplete(); 946 | 947 | }; 948 | 949 | // Launch loading resources task: buffers, etc. 950 | if (json.buffers) { 951 | for (bid in json.buffers) { 952 | 953 | loader._bufferRequested++; 954 | 955 | _loadArrayBuffer(loader.baseUri + json.buffers[bid].uri, loadArrayBufferCallback); 956 | 957 | } 958 | } 959 | 960 | // load images 961 | var loadImageCallback = function (img, iid) { 962 | loader._imageLoaded++; 963 | loader.glTF.images[iid] = img; 964 | loader._checkComplete(); 965 | }; 966 | 967 | var iid; 968 | 969 | if (json.images) { 970 | for (iid in json.images) { 971 | loader._imageRequested++; 972 | _loadImage(loader.baseUri + json.images[iid].uri, iid, loadImageCallback); 973 | } 974 | } 975 | 976 | loader._checkComplete(); 977 | }); 978 | }; 979 | 980 | 981 | glTFLoader.prototype._postprocess = function () { 982 | // if there's no plan for progressive loading (streaming) 983 | // than simply everything should be placed here 984 | 985 | // console.log('finish loading all assets, do a second pass postprocess'); 986 | 987 | curLoader = this; 988 | 989 | var i, leni, j, lenj; 990 | 991 | var scene, s; 992 | var node; 993 | var mesh, primitive, accessor; 994 | 995 | // cameras 996 | if (this.glTF.cameras) { 997 | for (i = 0, leni = this.glTF.cameras.length; i < leni; i++) { 998 | this.glTF.cameras[i] = new Camera(this.glTF.json.cameras[i]); 999 | } 1000 | } 1001 | 1002 | // bufferviews 1003 | if (this.glTF.bufferViews) { 1004 | for (i = 0, leni = this.glTF.bufferViews.length; i < leni; i++) { 1005 | this.glTF.bufferViews[i] = new BufferView(this.glTF.json.bufferViews[i], this._buffers[ this.glTF.json.bufferViews[i].buffer ]); 1006 | } 1007 | } 1008 | 1009 | // accessors 1010 | if (this.glTF.accessors) { 1011 | for (i = 0, leni = this.glTF.accessors.length; i < leni; i++) { 1012 | this.glTF.accessors[i] = new Accessor(this.glTF.json.accessors[i], this.glTF.bufferViews[ this.glTF.json.accessors[i].bufferView ]); 1013 | } 1014 | } 1015 | 1016 | // load all materials 1017 | if (this.glTF.materials) { 1018 | for (i = 0, leni = this.glTF.materials.length; i < leni; i++) { 1019 | this.glTF.materials[i] = new Material(this.glTF.json.materials[i]); 1020 | } 1021 | } 1022 | 1023 | // load all samplers 1024 | if (this.glTF.samplers) { 1025 | for (i = 0, leni = this.glTF.samplers.length; i < leni; i++) { 1026 | this.glTF.samplers[i] = new Sampler(this.glTF.json.samplers[i]); 1027 | } 1028 | } 1029 | 1030 | // load all textures 1031 | if (this.glTF.textures) { 1032 | for (i = 0, leni = this.glTF.textures.length; i < leni; i++) { 1033 | this.glTF.textures[i] = new Texture(this.glTF.json.textures[i]); 1034 | } 1035 | } 1036 | 1037 | // mesh 1038 | for (i = 0, leni = this.glTF.meshes.length; i < leni; i++) { 1039 | this.glTF.meshes[i] = new Mesh(this.glTF.json.meshes[i], i); 1040 | } 1041 | 1042 | // node 1043 | for (i = 0, leni = this.glTF.nodes.length; i < leni; i++) { 1044 | this.glTF.nodes[i] = new Node(this.glTF.json.nodes[i], i); 1045 | } 1046 | 1047 | // node: hook up children 1048 | for (i = 0, leni = this.glTF.nodes.length; i < leni; i++) { 1049 | node = this.glTF.nodes[i]; 1050 | for (j = 0, lenj = node.children.length; j < lenj; j++) { 1051 | node.children[j] = this.glTF.nodes[ node.children[j] ]; 1052 | } 1053 | } 1054 | 1055 | // scene Bounding box 1056 | var nodeMatrix = new Array(this.glTF.nodes.length); 1057 | for(i = 0, leni = nodeMatrix.length; i < leni; i++) { 1058 | nodeMatrix[i] = mat4.create(); 1059 | } 1060 | 1061 | function execUpdateTransform(n, parent) { 1062 | var tmpMat4 = nodeMatrix[n.nodeID]; 1063 | 1064 | if (parent !== null) { 1065 | mat4.mul(tmpMat4, nodeMatrix[parent.nodeID], n.matrix); 1066 | } else { 1067 | mat4.copy(tmpMat4, n.matrix); 1068 | } 1069 | } 1070 | 1071 | function execUpdateBBox(n, parent){ 1072 | var tmpMat4 = nodeMatrix[n.nodeID]; 1073 | var parentBVH; 1074 | 1075 | if (parent !== null) { 1076 | parentBVH = parent.bvh; 1077 | } else { 1078 | parentBVH = scene.boundingBox; 1079 | } 1080 | 1081 | if (n.mesh) { 1082 | mesh = n.mesh; 1083 | if (mesh.boundingBox) { 1084 | 1085 | n.aabb = BoundingBox.getAABBFromOBB(mesh.boundingBox, tmpMat4); 1086 | 1087 | if (n.children.length === 0) { 1088 | // n.bvh = n.aabb; 1089 | vec3.copy(n.bvh.min, n.aabb.min); 1090 | vec3.copy(n.bvh.max, n.aabb.max); 1091 | } 1092 | } 1093 | } 1094 | 1095 | vec3.min(parentBVH.min, parentBVH.min, n.bvh.min); 1096 | vec3.max(parentBVH.max, parentBVH.max, n.bvh.max); 1097 | } 1098 | 1099 | 1100 | for (i = 0, leni = this.glTF.scenes.length; i < leni; i++) { 1101 | scene = this.glTF.scenes[i] = new Scene(this.glTF, this.glTF.json.scenes[i]); 1102 | 1103 | scene.boundingBox = new BoundingBox(); 1104 | 1105 | 1106 | for (j = 0, lenj = scene.nodes.length; j < lenj; j++) { 1107 | node = scene.nodes[j]; 1108 | // node.traverse(null, execUpdateBBox); 1109 | node.traverseTwoExecFun(null, execUpdateTransform, execUpdateBBox); 1110 | } 1111 | 1112 | scene.boundingBox.calculateTransform(); 1113 | } 1114 | 1115 | 1116 | for (j = 0, lenj = this.glTF.nodes.length; j < lenj; j++) { 1117 | node = this.glTF.nodes[j]; 1118 | if (node.bvh !== null) { 1119 | node.bvh.calculateTransform(); 1120 | } 1121 | } 1122 | 1123 | 1124 | 1125 | // load animations (when all accessors are loaded correctly) 1126 | if (this.glTF.animations) { 1127 | for (i = 0, leni = this.glTF.animations.length; i < leni; i++) { 1128 | this.glTF.animations[i] = new Animation(this.glTF, this.glTF.json.animations[i]); 1129 | } 1130 | } 1131 | 1132 | var joints; 1133 | // if (this.glTF.skins) { 1134 | if (this.glTF.json.skins) { 1135 | for (i = 0, leni = this.glTF.skins.length; i < leni; i++) { 1136 | this.glTF.skins[i] = new Skin(this.glTF, this.glTF.json.skins[i], i); 1137 | 1138 | 1139 | joints = this.glTF.skins[i].joints; 1140 | for (j = 0, lenj = joints.length; j < lenj; j++) { 1141 | // this.glTF.nodes[ joints[j] ].jointID = j; 1142 | joints[j].jointID = j; 1143 | } 1144 | } 1145 | } 1146 | 1147 | for (i = 0, leni = this.glTF.nodes.length; i < leni; i++) { 1148 | node = this.glTF.nodes[i]; 1149 | if (node.skin !== null) { 1150 | if (typeof node.skin == 'number') { 1151 | // usual skin, hook up 1152 | node.skin = this.glTF.skins[ node.skin ]; 1153 | } else { 1154 | // assume gl_avatar is in use 1155 | // do nothing 1156 | } 1157 | 1158 | } 1159 | } 1160 | 1161 | 1162 | }; 1163 | 1164 | 1165 | // TODO: get from gl context 1166 | var ComponentType2ByteSize = { 1167 | 5120: 1, // BYTE 1168 | 5121: 1, // UNSIGNED_BYTE 1169 | 5122: 2, // SHORT 1170 | 5123: 2, // UNSIGNED_SHORT 1171 | 5126: 4 // FLOAT 1172 | }; 1173 | 1174 | var Type2NumOfComponent = { 1175 | 'SCALAR': 1, 1176 | 'VEC2': 2, 1177 | 'VEC3': 3, 1178 | 'VEC4': 4, 1179 | 'MAT2': 4, 1180 | 'MAT3': 9, 1181 | 'MAT4': 16 1182 | }; 1183 | 1184 | 1185 | // ------ Scope limited private util functions--------------- 1186 | 1187 | 1188 | // for animation use 1189 | function _arrayBuffer2TypedArray(buffer, byteOffset, countOfComponentType, componentType) { 1190 | switch(componentType) { 1191 | // @todo: finish 1192 | case 5122: return new Int16Array(buffer, byteOffset, countOfComponentType); 1193 | case 5123: return new Uint16Array(buffer, byteOffset, countOfComponentType); 1194 | case 5124: return new Int32Array(buffer, byteOffset, countOfComponentType); 1195 | case 5125: return new Uint32Array(buffer, byteOffset, countOfComponentType); 1196 | case 5126: return new Float32Array(buffer, byteOffset, countOfComponentType); 1197 | default: return null; 1198 | } 1199 | } 1200 | 1201 | function _getAccessorData(accessor) { 1202 | return _arrayBuffer2TypedArray( 1203 | accessor.bufferView.data, 1204 | accessor.byteOffset, 1205 | accessor.count * Type2NumOfComponent[accessor.type], 1206 | accessor.componentType 1207 | ); 1208 | } 1209 | 1210 | function _getBaseUri(uri) { 1211 | 1212 | // https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Core/getBaseUri.js 1213 | 1214 | var basePath = ''; 1215 | var i = uri.lastIndexOf('/'); 1216 | if(i !== -1) { 1217 | basePath = uri.substring(0, i + 1); 1218 | } 1219 | 1220 | return basePath; 1221 | } 1222 | 1223 | function _loadJSON(src, callback) { 1224 | 1225 | // native json loading technique from @KryptoniteDove: 1226 | // http://codepen.io/KryptoniteDove/post/load-json-file-locally-using-pure-javascript 1227 | 1228 | var xobj = new XMLHttpRequest(); 1229 | xobj.overrideMimeType("application/json"); 1230 | xobj.open('GET', src, true); 1231 | xobj.onreadystatechange = function () { 1232 | if (xobj.readyState == 4 && // Request finished, response ready 1233 | xobj.status == "200") { // Status OK 1234 | callback(xobj.responseText, this); 1235 | } 1236 | }; 1237 | xobj.send(null); 1238 | } 1239 | 1240 | function _loadArrayBuffer(url, callback) { 1241 | var xobj = new XMLHttpRequest(); 1242 | xobj.responseType = 'arraybuffer'; 1243 | xobj.open('GET', url, true); 1244 | xobj.onreadystatechange = function () { 1245 | if (xobj.readyState == 4 && // Request finished, response ready 1246 | xobj.status == "200") { // Status OK 1247 | var arrayBuffer = xobj.response; 1248 | if (arrayBuffer && callback) { 1249 | callback(arrayBuffer); 1250 | } 1251 | } 1252 | }; 1253 | xobj.send(null); 1254 | } 1255 | 1256 | function _loadImage(url, iid, onload) { 1257 | var img = new Image(); 1258 | img.crossOrigin = "Anonymous"; 1259 | img.src = url; 1260 | img.onload = function() { 1261 | onload(img, iid); 1262 | }; 1263 | } 1264 | 1265 | // export { MinimalGLTFLoader }; 1266 | export { glTFLoader }; -------------------------------------------------------------------------------- /src/shaders/fs-bbox.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | #define FRAG_COLOR_LOCATION 0 3 | 4 | precision highp float; 5 | precision highp int; 6 | 7 | layout(location = FRAG_COLOR_LOCATION) out vec4 color; 8 | 9 | void main() 10 | { 11 | color = vec4(1.0, 0.0, 0.0, 1.0); 12 | } -------------------------------------------------------------------------------- /src/shaders/fs-cube-map.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | precision highp int; 4 | 5 | uniform samplerCube u_environment; 6 | 7 | in vec3 texcoord; 8 | 9 | out vec4 color; 10 | 11 | void main() 12 | { 13 | color = texture(u_environment, texcoord); 14 | } -------------------------------------------------------------------------------- /src/shaders/fs-pbr-master.glsl: -------------------------------------------------------------------------------- 1 | #define FRAG_COLOR_LOCATION 0 2 | 3 | // reference: https://github.com/KhronosGroup/glTF-WebGL-PBR/blob/master/shaders/pbr-frag.glsl 4 | 5 | precision highp float; 6 | precision highp int; 7 | 8 | // IBL 9 | uniform samplerCube u_DiffuseEnvSampler; 10 | uniform samplerCube u_SpecularEnvSampler; 11 | uniform sampler2D u_brdfLUT; 12 | 13 | // Metallic-roughness material 14 | 15 | // base color 16 | uniform vec4 u_baseColorFactor; 17 | #ifdef HAS_BASECOLORMAP 18 | uniform sampler2D u_baseColorTexture; 19 | #endif 20 | 21 | // normal map 22 | #ifdef HAS_NORMALMAP 23 | uniform sampler2D u_normalTexture; 24 | uniform float u_normalTextureScale; 25 | #endif 26 | 27 | // emmisve map 28 | #ifdef HAS_EMISSIVEMAP 29 | uniform sampler2D u_emissiveTexture; 30 | uniform vec3 u_emissiveFactor; 31 | #endif 32 | 33 | // metal roughness 34 | #ifdef HAS_METALROUGHNESSMAP 35 | uniform sampler2D u_metallicRoughnessTexture; 36 | #endif 37 | uniform float u_metallicFactor; 38 | uniform float u_roughnessFactor; 39 | 40 | // occlusion texture 41 | #ifdef HAS_OCCLUSIONMAP 42 | uniform sampler2D u_occlusionTexture; 43 | uniform float u_occlusionStrength; 44 | #endif 45 | 46 | in vec3 v_position; 47 | in vec3 v_normal; 48 | in vec2 v_uv; 49 | 50 | layout(location = FRAG_COLOR_LOCATION) out vec4 frag_color; 51 | 52 | struct PBRInfo 53 | { 54 | float NdotL; // cos angle between normal and light direction 55 | float NdotV; // cos angle between normal and view direction 56 | float NdotH; // cos angle between normal and half vector 57 | float LdotH; // cos angle between light direction and half vector 58 | float VdotH; // cos angle between view direction and half vector 59 | float perceptualRoughness; // roughness value, as authored by the model creator (input to shader) 60 | float metalness; // metallic value at the surface 61 | vec3 reflectance0; // full reflectance color (normal incidence angle) 62 | vec3 reflectance90; // reflectance color at grazing angle 63 | float alphaRoughness; // roughness mapped to a more linear change in the roughness (proposed by [2]) 64 | vec3 diffuseColor; // color contribution from diffuse lighting 65 | vec3 specularColor; // color contribution from specular lighting 66 | }; 67 | 68 | 69 | // vec3 applyNormalMap(vec3 geomnor, vec3 normap) { 70 | // normap = normap * 2.0 - 1.0; 71 | // vec3 up = normalize(vec3(0.01, 1, 0.01)); 72 | // vec3 surftan = normalize(cross(geomnor, up)); 73 | // vec3 surfbinor = cross(geomnor, surftan); 74 | // return normap.y * surftan * u_normalTextureScale + normap.x * surfbinor * u_normalTextureScale + normap.z * geomnor; 75 | // } 76 | 77 | const float M_PI = 3.141592653589793; 78 | const float c_MinRoughness = 0.04; 79 | 80 | 81 | // vec3 getNormal() 82 | // { 83 | 84 | // #ifdef HAS_NORMALMAP 85 | // #ifdef HAS_TANGENTS 86 | // vec3 n = texture(u_normalTexture, v_uv).rgb; 87 | // n = normalize(v_TBN * (2.0 * n - 1.0) - vec3(u_normalTextureScale, u_normalTextureScale, 1.0)); 88 | // #else 89 | // vec3 n = applyNormalMap( v_normal, texture(u_normalTexture, v_uv).rgb ); 90 | // #endif 91 | // #else 92 | // vec3 n = v_normal; 93 | // #endif 94 | // return n; 95 | 96 | // #endif 97 | // } 98 | 99 | // Find the normal for this fragment, pulling either from a predefined normal map 100 | // or from the interpolated mesh normal and tangent attributes. 101 | vec3 getNormal() 102 | { 103 | 104 | // #ifdef HAS_NORMALMAP 105 | // vec3 n = applyNormalMap( v_normal, texture(u_normalTexture, v_uv).rgb ); 106 | // #else 107 | // vec3 n = v_normal; 108 | // #endif 109 | // return n; 110 | 111 | 112 | // Retrieve the tangent space matrix 113 | // #ifndef HAS_TANGENTS 114 | vec3 pos_dx = dFdx(v_position); 115 | vec3 pos_dy = dFdy(v_position); 116 | vec3 tex_dx = dFdx(vec3(v_uv, 0.0)); 117 | vec3 tex_dy = dFdy(vec3(v_uv, 0.0)); 118 | vec3 t = (tex_dy.t * pos_dx - tex_dx.t * pos_dy) / (tex_dx.s * tex_dy.t - tex_dy.s * tex_dx.t); 119 | 120 | vec3 ng = v_normal; 121 | // #ifdef HAS_NORMALS 122 | // vec3 ng = normalize(v_normal); 123 | // #else 124 | // vec3 ng = cross(pos_dx, pos_dy); 125 | // #endif 126 | 127 | t = normalize(t - ng * dot(ng, t)); 128 | vec3 b = normalize(cross(ng, t)); 129 | mat3 tbn = mat3(t, b, ng); 130 | // #else // HAS_TANGENTS 131 | // mat3 tbn = v_TBN; 132 | // #endif 133 | 134 | // TODO: TANGENTS 135 | 136 | #ifdef HAS_NORMALMAP 137 | vec3 n = texture(u_normalTexture, v_uv).rgb; 138 | n = normalize(tbn * ((2.0 * n - 1.0) * vec3(u_normalTextureScale, u_normalTextureScale, 1.0))); 139 | #else 140 | vec3 n = tbn[2].xyz; 141 | #endif 142 | 143 | return n; 144 | } 145 | 146 | vec3 getIBLContribution(PBRInfo pbrInputs, vec3 n, vec3 reflection) 147 | { 148 | // float mipCount = 9.0; // resolution of 512x512 149 | // float mipCount = 10.0; // resolution of 1024x1024 150 | float mipCount = 10.0; // resolution of 256x256 151 | float lod = (pbrInputs.perceptualRoughness * mipCount); 152 | // retrieve a scale and bias to F0. See [1], Figure 3 153 | vec3 brdf = texture(u_brdfLUT, vec2(pbrInputs.NdotV, 1.0 - pbrInputs.perceptualRoughness)).rgb; 154 | vec3 diffuseLight = texture(u_DiffuseEnvSampler, n).rgb; 155 | 156 | // #ifdef USE_TEX_LOD 157 | vec3 specularLight = texture(u_SpecularEnvSampler, reflection, lod).rgb; 158 | // #else 159 | // vec3 specularLight = texture(u_SpecularEnvSampler, reflection).rgb; 160 | // #endif 161 | 162 | vec3 diffuse = diffuseLight * pbrInputs.diffuseColor; 163 | vec3 specular = specularLight * (pbrInputs.specularColor * brdf.x + brdf.y); 164 | 165 | // // For presentation, this allows us to disable IBL terms 166 | // diffuse *= u_ScaleIBLAmbient.x; 167 | // specular *= u_ScaleIBLAmbient.y; 168 | 169 | return diffuse + specular; 170 | } 171 | 172 | // Basic Lambertian diffuse 173 | // Implementation from Lambert's Photometria https://archive.org/details/lambertsphotome00lambgoog 174 | // See also [1], Equation 1 175 | vec3 diffuse(PBRInfo pbrInputs) 176 | { 177 | return pbrInputs.diffuseColor / M_PI; 178 | } 179 | 180 | 181 | // The following equation models the Fresnel reflectance term of the spec equation (aka F()) 182 | // Implementation of fresnel from [4], Equation 15 183 | vec3 specularReflection(PBRInfo pbrInputs) 184 | { 185 | return pbrInputs.reflectance0 + (pbrInputs.reflectance90 - pbrInputs.reflectance0) * pow(clamp(1.0 - pbrInputs.VdotH, 0.0, 1.0), 5.0); 186 | } 187 | 188 | 189 | // This calculates the specular geometric attenuation (aka G()), 190 | // where rougher material will reflect less light back to the viewer. 191 | // This implementation is based on [1] Equation 4, and we adopt their modifications to 192 | // alphaRoughness as input as originally proposed in [2]. 193 | float geometricOcclusion(PBRInfo pbrInputs) 194 | { 195 | float NdotL = pbrInputs.NdotL; 196 | float NdotV = pbrInputs.NdotV; 197 | float r = pbrInputs.alphaRoughness; 198 | 199 | float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL))); 200 | float attenuationV = 2.0 * NdotV / (NdotV + sqrt(r * r + (1.0 - r * r) * (NdotV * NdotV))); 201 | return attenuationL * attenuationV; 202 | } 203 | 204 | 205 | // The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D()) 206 | // Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz 207 | // Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3. 208 | float microfacetDistribution(PBRInfo pbrInputs) 209 | { 210 | float roughnessSq = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness; 211 | float f = (pbrInputs.NdotH * roughnessSq - pbrInputs.NdotH) * pbrInputs.NdotH + 1.0; 212 | return roughnessSq / (M_PI * f * f); 213 | } 214 | 215 | 216 | 217 | 218 | 219 | 220 | void main() 221 | { 222 | float perceptualRoughness = u_roughnessFactor; 223 | float metallic = u_metallicFactor; 224 | 225 | #ifdef HAS_METALROUGHNESSMAP 226 | // Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel. 227 | // This layout intentionally reserves the 'r' channel for (optional) occlusion map data 228 | vec4 mrSample = texture(u_metallicRoughnessTexture, v_uv); 229 | perceptualRoughness = mrSample.g * perceptualRoughness; 230 | metallic = mrSample.b * metallic; 231 | #endif 232 | perceptualRoughness = clamp(perceptualRoughness, c_MinRoughness, 1.0); 233 | metallic = clamp(metallic, 0.0, 1.0); 234 | // Roughness is authored as perceptual roughness; as is convention, 235 | // convert to material roughness by squaring the perceptual roughness [2]. 236 | float alphaRoughness = perceptualRoughness * perceptualRoughness; 237 | 238 | 239 | // The albedo may be defined from a base texture or a flat color 240 | #ifdef HAS_BASECOLORMAP 241 | vec4 baseColor = texture(u_baseColorTexture, v_uv) * u_baseColorFactor; 242 | #else 243 | vec4 baseColor = u_baseColorFactor; 244 | #endif 245 | 246 | 247 | 248 | vec3 f0 = vec3(0.04); 249 | vec3 diffuseColor = baseColor.rgb * (vec3(1.0) - f0); 250 | diffuseColor *= 1.0 - metallic; 251 | vec3 specularColor = mix(f0, baseColor.rgb, metallic); 252 | 253 | // Compute reflectance. 254 | float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b); 255 | 256 | 257 | // For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect. 258 | // For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%. 259 | float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0); 260 | vec3 specularEnvironmentR0 = specularColor.rgb; 261 | vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0) * reflectance90; 262 | 263 | 264 | vec3 n = getNormal(); // normal at surface point 265 | // vec3 v = vec3( 0.0, 0.0, 1.0 ); // Vector from surface point to camera 266 | vec3 v = normalize(-v_position); // Vector from surface point to camera 267 | // vec3 l = normalize(u_LightDirection); // Vector from surface point to light 268 | vec3 l = normalize(vec3( 1.0, 1.0, 1.0 )); // Vector from surface point to light 269 | // vec3 l = vec3( 0.0, 0.0, 1.0 ); // Vector from surface point to light 270 | vec3 h = normalize(l+v); // Half vector between both l and v 271 | vec3 reflection = -normalize(reflect(v, n)); 272 | 273 | float NdotL = clamp(dot(n, l), 0.001, 1.0); 274 | float NdotV = abs(dot(n, v)) + 0.001; 275 | float NdotH = clamp(dot(n, h), 0.0, 1.0); 276 | float LdotH = clamp(dot(l, h), 0.0, 1.0); 277 | float VdotH = clamp(dot(v, h), 0.0, 1.0); 278 | 279 | PBRInfo pbrInputs = PBRInfo( 280 | NdotL, 281 | NdotV, 282 | NdotH, 283 | LdotH, 284 | VdotH, 285 | perceptualRoughness, 286 | metallic, 287 | specularEnvironmentR0, 288 | specularEnvironmentR90, 289 | alphaRoughness, 290 | diffuseColor, 291 | specularColor 292 | ); 293 | 294 | // Calculate the shading terms for the microfacet specular shading model 295 | vec3 F = specularReflection(pbrInputs); 296 | float G = geometricOcclusion(pbrInputs); 297 | float D = microfacetDistribution(pbrInputs); 298 | 299 | // Calculation of analytical lighting contribution 300 | vec3 diffuseContrib = (1.0 - F) * diffuse(pbrInputs); 301 | vec3 specContrib = max(vec3(0.0), F * G * D / (4.0 * NdotL * NdotV)); 302 | // vec3 color = NdotL * u_LightColor * (diffuseContrib + specContrib); 303 | vec3 color = NdotL * (diffuseContrib + specContrib); // assume light color vec3(1, 1, 1) 304 | 305 | // Calculate lighting contribution from image based lighting source (IBL) 306 | // #ifdef USE_IBL 307 | color += getIBLContribution(pbrInputs, n, reflection); 308 | // #endif 309 | 310 | 311 | // Apply optional PBR terms for additional (optional) shading 312 | #ifdef HAS_OCCLUSIONMAP 313 | float ao = texture(u_occlusionTexture, v_uv).r; 314 | color = mix(color, color * ao, u_occlusionStrength); 315 | #endif 316 | 317 | #ifdef HAS_EMISSIVEMAP 318 | vec3 emissive = texture(u_emissiveTexture, v_uv).rgb * u_emissiveFactor; 319 | color += emissive; 320 | #endif 321 | 322 | // // This section uses mix to override final color for reference app visualization 323 | // // of various parameters in the lighting equation. 324 | // color = mix(color, F, u_ScaleFGDSpec.x); 325 | // color = mix(color, vec3(G), u_ScaleFGDSpec.y); 326 | // color = mix(color, vec3(D), u_ScaleFGDSpec.z); 327 | // color = mix(color, specContrib, u_ScaleFGDSpec.w); 328 | 329 | // color = mix(color, diffuseContrib, u_ScaleDiffBaseMR.x); 330 | // color = mix(color, baseColor.rgb, u_ScaleDiffBaseMR.y); 331 | // color = mix(color, vec3(metallic), u_ScaleDiffBaseMR.z); 332 | // color = mix(color, vec3(perceptualRoughness), u_ScaleDiffBaseMR.w); 333 | 334 | frag_color = vec4(color, baseColor.a); 335 | } -------------------------------------------------------------------------------- /src/shaders/vs-bbox.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | #define POSITION_LOCATION 0 3 | 4 | precision highp float; 5 | precision highp int; 6 | 7 | uniform mat4 u_MVP; 8 | 9 | layout(location = POSITION_LOCATION) in vec3 position; 10 | 11 | void main() 12 | { 13 | gl_Position = u_MVP * vec4(position, 1.0) ; 14 | } -------------------------------------------------------------------------------- /src/shaders/vs-cube-map.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | precision highp int; 4 | 5 | uniform mat4 u_MVP; 6 | 7 | layout(location = 0) in vec3 position; 8 | 9 | out vec3 texcoord; 10 | 11 | void main() 12 | { 13 | vec4 pos = u_MVP * vec4(position, 1.0); 14 | gl_Position = pos.xyww; 15 | texcoord = position; 16 | } -------------------------------------------------------------------------------- /src/shaders/vs-pbr-master.glsl: -------------------------------------------------------------------------------- 1 | #define POSITION_LOCATION 0 2 | #define NORMAL_LOCATION 1 3 | #define TEXCOORD_0_LOCATION 2 4 | #define JOINTS_0_LOCATION 3 5 | #define JOINTS_1_LOCATION 5 6 | #define WEIGHTS_0_LOCATION 4 7 | #define WEIGHTS_1_LOCATION 6 8 | #define TANGENT_LOCATION 7 9 | 10 | precision highp float; 11 | precision highp int; 12 | 13 | uniform mat4 u_MVP; 14 | uniform mat4 u_MV; 15 | uniform mat4 u_MVNormal; 16 | 17 | #ifdef HAS_SKIN 18 | uniform JointMatrix 19 | { 20 | mat4 matrix[65]; 21 | } u_jointMatrix; 22 | #endif 23 | 24 | layout(location = POSITION_LOCATION) in vec3 position; 25 | layout(location = NORMAL_LOCATION) in vec3 normal; 26 | layout(location = TEXCOORD_0_LOCATION) in vec2 uv; 27 | 28 | #ifdef HAS_SKIN 29 | layout(location = JOINTS_0_LOCATION) in vec4 joint0; 30 | layout(location = WEIGHTS_0_LOCATION) in vec4 weight0; 31 | #ifdef SKIN_VEC8 32 | layout(location = JOINTS_1_LOCATION) in vec4 joint1; 33 | layout(location = WEIGHTS_1_LOCATION) in vec4 weight1; 34 | #endif 35 | #endif 36 | 37 | 38 | // #ifdef HAS_TANGENTS 39 | // layout(location = TANGENT_LOCATION) in vec4 tangent; 40 | 41 | // out vec3 v_tangentW; 42 | // out vec3 v_bitangentW; 43 | // #endif 44 | 45 | 46 | out vec3 v_position; 47 | out vec3 v_normal; 48 | out vec2 v_uv; 49 | 50 | void main() 51 | { 52 | 53 | #ifdef HAS_SKIN 54 | mat4 skinMatrix = 55 | weight0.x * u_jointMatrix.matrix[int(joint0.x)] + 56 | weight0.y * u_jointMatrix.matrix[int(joint0.y)] + 57 | weight0.z * u_jointMatrix.matrix[int(joint0.z)] + 58 | weight0.w * u_jointMatrix.matrix[int(joint0.w)]; 59 | #ifdef SKIN_VEC8 60 | skinMatrix += 61 | weight1.x * u_jointMatrix.matrix[int(joint1.x)] + 62 | weight1.y * u_jointMatrix.matrix[int(joint1.y)] + 63 | weight1.z * u_jointMatrix.matrix[int(joint1.z)] + 64 | weight1.w * u_jointMatrix.matrix[int(joint1.w)]; 65 | #endif 66 | #endif 67 | 68 | v_uv = uv; 69 | 70 | #ifdef HAS_SKIN 71 | v_normal = normalize(( u_MVNormal * transpose(inverse(skinMatrix)) * vec4(normal, 0)).xyz); 72 | vec4 pos = u_MV * skinMatrix * vec4(position, 1.0); 73 | gl_Position = u_MVP * skinMatrix * vec4(position, 1.0); 74 | #else 75 | v_normal = normalize((u_MVNormal * vec4(normal, 0)).xyz); 76 | vec4 pos = u_MV * vec4(position, 1.0); 77 | gl_Position = u_MVP * vec4(position, 1.0); 78 | #endif 79 | 80 | v_position = vec3(pos.xyz) / pos.w; 81 | 82 | 83 | } -------------------------------------------------------------------------------- /textures/brdfLUT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/brdfLUT.png -------------------------------------------------------------------------------- /textures/environment/diffuse/bakedDiffuse_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/diffuse/bakedDiffuse_01.jpg -------------------------------------------------------------------------------- /textures/environment/diffuse/bakedDiffuse_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/diffuse/bakedDiffuse_02.jpg -------------------------------------------------------------------------------- /textures/environment/diffuse/bakedDiffuse_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/diffuse/bakedDiffuse_03.jpg -------------------------------------------------------------------------------- /textures/environment/diffuse/bakedDiffuse_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/diffuse/bakedDiffuse_04.jpg -------------------------------------------------------------------------------- /textures/environment/diffuse/bakedDiffuse_05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/diffuse/bakedDiffuse_05.jpg -------------------------------------------------------------------------------- /textures/environment/diffuse/bakedDiffuse_06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/diffuse/bakedDiffuse_06.jpg -------------------------------------------------------------------------------- /textures/environment/nx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/nx.jpg -------------------------------------------------------------------------------- /textures/environment/ny.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/ny.jpg -------------------------------------------------------------------------------- /textures/environment/nz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/nz.jpg -------------------------------------------------------------------------------- /textures/environment/px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/px.jpg -------------------------------------------------------------------------------- /textures/environment/py.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/py.jpg -------------------------------------------------------------------------------- /textures/environment/pz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/environment/pz.jpg -------------------------------------------------------------------------------- /textures/papermill/diffuse/diffuse_back_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/diffuse/diffuse_back_0.jpg -------------------------------------------------------------------------------- /textures/papermill/diffuse/diffuse_bottom_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/diffuse/diffuse_bottom_0.jpg -------------------------------------------------------------------------------- /textures/papermill/diffuse/diffuse_front_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/diffuse/diffuse_front_0.jpg -------------------------------------------------------------------------------- /textures/papermill/diffuse/diffuse_left_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/diffuse/diffuse_left_0.jpg -------------------------------------------------------------------------------- /textures/papermill/diffuse/diffuse_right_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/diffuse/diffuse_right_0.jpg -------------------------------------------------------------------------------- /textures/papermill/diffuse/diffuse_top_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/diffuse/diffuse_top_0.jpg -------------------------------------------------------------------------------- /textures/papermill/environment_back_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/environment_back_0.jpg -------------------------------------------------------------------------------- /textures/papermill/environment_bottom_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/environment_bottom_0.jpg -------------------------------------------------------------------------------- /textures/papermill/environment_front_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/environment_front_0.jpg -------------------------------------------------------------------------------- /textures/papermill/environment_left_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/environment_left_0.jpg -------------------------------------------------------------------------------- /textures/papermill/environment_right_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/environment_right_0.jpg -------------------------------------------------------------------------------- /textures/papermill/environment_top_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shrekshao/minimal-gltf-loader/9e42301099bfae07d60f69e49d1bb3cf34092ec7/textures/papermill/environment_top_0.jpg -------------------------------------------------------------------------------- /third-party-license/glMatrix: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | // var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 5 | 6 | module.exports = { 7 | entry: { 8 | lib: './src/minimal-gltf-loader.js' 9 | }, 10 | resolve: { 11 | alias: { 12 | // Lib: path.resolve(__dirname, 'demo/lib/'), 13 | // Shaders: path.resolve(__dirname, 'demo/lib/src/shaders') 14 | } 15 | }, 16 | output: { 17 | filename: 'minimal-gltf-loader.js', 18 | path: path.resolve(__dirname, 'build'), 19 | library: 'MinimalGLTFLoader', 20 | libraryTarget: 'umd' 21 | }, 22 | externals: { 23 | 'gl-matrix': { 24 | commonjs: 'gl-matrix', 25 | commonjs2: 'gl-matrix', 26 | amd: 'gl-matrix' 27 | } 28 | }, 29 | module: { 30 | // rules: [ 31 | 32 | // ] 33 | // loaders: [ 34 | // { 35 | // test: /\.glsl$/, 36 | // loader: "webpack-glsl" 37 | // }, 38 | // ] 39 | }, 40 | plugins: [ 41 | // new webpack.optimize.UglifyJsPlugin({ 42 | // compress: { 43 | // warnings: false, 44 | // drop_console: false, 45 | // } 46 | // }) 47 | // new UglifyJSPlugin() 48 | 49 | // new HtmlWebpackPlugin({ 50 | // title: "glAvatar demo" 51 | // }) 52 | ], 53 | devServer: { 54 | contentBase: path.join(__dirname, "demo"), 55 | port: 7777 56 | } 57 | }; --------------------------------------------------------------------------------