├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── robots.txt ├── src ├── js │ ├── index.js │ └── vendor │ │ └── draco │ │ ├── README.md │ │ ├── draco_decoder.js │ │ ├── draco_decoder.wasm │ │ ├── draco_encoder.js │ │ ├── draco_wasm_wrapper.js │ │ └── gltf │ │ ├── draco_decoder.js │ │ ├── draco_decoder.wasm │ │ ├── draco_encoder.js │ │ └── draco_wasm_wrapper.js ├── sass │ └── style.scss └── static │ ├── audio │ └── fire_compressed.mp3 │ ├── fonts │ ├── wedgie_regular-webfont.woff │ └── wedgie_regular-webfont.woff2 │ ├── html │ └── index.html │ ├── images │ ├── favicons │ │ └── favicon.ico │ ├── flame-texture.png │ ├── grass-texture.jpg │ ├── lightning-texture.png │ ├── shine-texture.png │ └── smoke-texture.png │ └── models │ └── dragon │ ├── dracoDragon.gltf │ ├── scene.bin │ └── scene.gltf ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | 3 | defaults 4 | not IE 11 5 | not IE_Mob 11 6 | maintained node versions -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["prettier"], 3 | rules: { 4 | "no-unused-vars": 1, 5 | "eslint linebreak-style": [0, "error", "windows"], 6 | }, 7 | env: { 8 | es6: true, 9 | browser: true, 10 | node: true 11 | }, 12 | parserOptions: { 13 | sourceType: 'module' 14 | }, 15 | extends: ['eslint:recommended',"plugin:prettier/recommended"] 16 | }; 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'node' 4 | script: npm run build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ethan Soo Hon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # threejs-es6-webpack-barebones-boilerplate 2 | 3 | 4 | [![Build Status](https://travis-ci.com/ethanny2/threejs-es6-webpack-barebones-boilerplate.svg?branch=master)](https://travis-ci.com/ethanny2/threejs-es6-webpack-barebones-boilerplate) 5 | [![GitHub issues](https://img.shields.io/github/issues/ethanny2/threejs-es6-webpack-barebones-boilerplate)](https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/issues) 6 | [![GitHub forks](https://img.shields.io/github/forks/ethanny2/threejs-es6-webpack-barebones-boilerplate)](https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/network) 7 | [![GitHub stars](https://img.shields.io/github/stars/ethanny2/threejs-es6-webpack-barebones-boilerplate)](https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/stargazers) 8 | [![GitHub license](https://img.shields.io/github/license/ethanny2/threejs-es6-webpack-barebones-boilerplate)](https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/blob/master/LICENSE) 9 | [![Twitter Badge](https://img.shields.io/badge/chat-twitter-blue.svg)](https://twitter.com/ArrayLikeObj) 10 | 11 | ![Default scene](https://i.imgur.com/NuDNzFG.png) 12 | ## About 13 | 14 | **This project was created to provide a good starting point for learners who are new to [webpack](https://webpack.js.org/), JavaScript bundlers, and 15 | [three.js](https://threejs.org/)**. I wanted to create an optimal development and production environment that was simple to understand and hassle-free to set up for experts and novices alike. Project key points: 16 | 17 | 1. Unobtrusive - No unnecessary abstractions or imposed design patterns. Quickly get a three.js project up and running. 18 | 19 | 2. Beginner Friendly - Well documented with numerous explanations of concepts and of code. 20 | 21 | 3. Static Site Workflow - This boilerplate provides a streamlined workflow and configuration that also works well for making static sites without three.js. 22 | 23 | ## Prerequisites 24 | 25 | To install and run this boilerplate you must have and [node.js](https://nodejs.org/) and [git](https://git-scm.com/) on your computer before running. 26 | 27 | # Project Structure 28 | :open_file_folder: 29 | 30 | ``` 31 | |-- dist --> Build output files; where files are built for production 32 | |-- src --> Source files; where you do development 33 | |-- js 34 | | |-- index.js --> Singular entry point for webpack 35 | | |-- scripts --> Any supplementary scripts or entry points go here 36 | | |-- vendor --> Any vendor code (e.g code you didn't write) 37 | | --SPE.js 38 | |-- sass --> write your css, sass, or scss files. All are formats are supported 39 | |-- style.scss 40 | |-- static --> Where to place all of your static assets and html templates 41 | |-- audio 42 | |-- fonts 43 | |-- html 44 | |-- images 45 | | |-- favicons 46 | |-- models 47 | |-- dragon 48 | |-- webpack.common.js 49 | |-- webpack.dev.js 50 | |-- webpack.prod.js 51 | |-- package-lock.json 52 | |-- package.json 53 | |-- postcss.config.js 54 | |-- .eslintrc.js 55 | |-- .browserslistrc 56 | |-- .gitignore 57 | |-- robots.txt 58 | |-- .travis.yml 59 | ``` 60 | 61 | # Usage/NPM Scripts 62 | :scroll: 63 | 64 | ## Installation steps: 65 | 66 | ``` 67 | 1. git clone https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate.git your-project 68 | 2. cd your-project 69 | 3. npm install 70 | ``` 71 | 72 | ## NPM Scripts 73 | 74 | Removes/cleans every file in the dist directory. This is used before each build to remove all the previous build's files. The [rimraf](https://www.npmjs.com/package/rimraf) npm package gives cross-platform support for the UNIX rm -rf (remove files from a directory recursively). 75 | 76 | ``` 77 | "clean": "rimraf dist/*" 78 | ``` 79 | 80 | Runs the npm [eslint](https://www.npmjs.com/package/eslint) package on the current directory. (represented on UNIX by . ). ESlint will identify any incorrect patterns (expressed as either a warning or an error) and print to your terminal. 81 | 82 | ``` 83 | "lint": "eslint ." 84 | ``` 85 | 86 | Runs ESlint on the current directory again, but this time fixable errors (errors that have to do with formatting rules) are fixed and saved. _Errors that deal with code quality (e.g unused variables, implicit global variables) must be fixed manually_. 87 | 88 | ``` 89 | "lint-fix": "eslint --fix ." 90 | ``` 91 | 92 | Sequentially runs the clean script then specifies the configuration file (--config) to start the webpack production build. These webpack commands are available via the already included [webpack-cli](https://github.com/webpack/webpack-cli). 93 | 94 | ``` 95 | "build": " npm run clean && webpack --config webpack.prod.js" 96 | ``` 97 | 98 | Starts a live reloading development  server at localhost:9000. Hot module reloading is enabled for changes in any .css/.sass/.scss file. **Both the development and production configs both produce identical file structures in the dist directory, but the output files for development  are not written to disk, they are served from memory for better speed**. This is why the dist directory appears empty when in the development environment. 99 | To stop the server press CTRL+C on Windows or CMD+C on Macs. 100 | 101 | ``` 102 | "start-dev": "webpack-dev-server --open --config webpack.dev.js" 103 | ``` 104 | 105 | Example using [gLTF Pipeline](https://github.com/AnalyticalGraphicsInc/gltf-pipeline) CLI and [Draco Compression](https://google.github.io/draco/) to compress a .gltf file 106 | .This results in a smaller single .gLTF file with textures included. (Change args to compress new models) 107 | 108 | ``` 109 | "compress-draco": "gltf-pipeline -i ./src/static/models/dragon/scene.gltf -o ./src/static/models/dragon/dracoDragon.gltf -d", 110 | ``` 111 | Example using [gLTF Pipeline](https://github.com/AnalyticalGraphicsInc/gltf-pipeline) CLI to convert a .gltf file to a .glb file. This results in a single .glb file but doesn't result in a file size reduction. (Change args to compress new models) 112 | ``` 113 | "compress-glb": "gltf-pipeline -i ./src/static/models/dragon/scene.gltf -o ./src/static/models/dragon/scene.glb" 114 | ``` 115 | 116 | # Table of Contents 117 | :book: 118 | 119 | - [Glossary](#glossary) 120 | - [Configuration and Utility Files](#configuration-and-utility-files) 121 | - [Development Environment](#development-environment) 122 | - [Production Environment](#production-environment) 123 | - [three.js Scene and Examples](#threejs-scene-and-examples) 124 | - [Learning Resources](#learning-resources) 125 | - [Credits](#credits) 126 | 127 | # Glossary 128 | :speech_balloon: 129 | 130 | - [Polyfill](https://developer.mozilla.org/en-US/docs/Glossary/Polyfill): Piece of code (js file in the context of the web) used to provide modern functionality to older browsers that don't natively support it. 131 | - [JavaScript bundlers](https://medium.com/@gimenete/how-javascript-bundlers-work-1fc0d0caf2da): Tools that gather your javascript files and dependencies into (usually) one js file. 132 | - [webpack](https://webpack.js.org/): A static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles. 133 | - [Transpilation](https://scotch.io/tutorials/javascript-transpilers-what-they-are-why-we-need-them): Type of compiler that takes source code written in a specific programming language and produces the equivalent source code in the same or a different programming language. 134 | - [Tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking): Is a term (in the context of JavaScript) used to describe the removal of dead/unused code. Bundlers such as webpack and Rollup automatically remove dead code when bundling multiple JavaScript files. 135 | - [gLTF](https://wiki.fileformat.com/3d/gltf/): A one-size fits all file format that stores 3D model information in a JSON format. This results in a reduction of the size of the 3D asset and the time needed to unpack those assets. (Format optimized for web) 136 | - [glb](https://wiki.fileformat.com/3d/glb/): A binary file format representation of 3D models saved in the GL Transmission Format (glTF). This binary format stores all of the .gLTF's assets (JSON, .bin, textures) in the binary blob. 137 | - [Draco compression](https://google.github.io/draco/): An open source library for compression and decompressing 3D assets. 138 | - [Threejs](https://github.com/mrdoob/three.js/): A 3D JavaScript library that abstracts obtuse webGL concepts into streamlined code base. 139 | - [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API): A JavaScript API for rendering high-performance interactive 3D and 2D graphics within any compatible web browser without the use of plug-ins 140 | - [Minification](https://developer.mozilla.org/en-US/docs/Glossary/minification): The process of removing unnecessary or redundant data without affecting how a resource is processed by the browser 141 | - [Production Environment](https://dev.to/flippedcoding/difference-between-development-stage-and-production-d0p): An environment that represents the finished website live on the internet. Usually this environment prioritizes minification, compression and optimization of assets to produce the best site. 142 | - [Development Environment](https://dev.to/flippedcoding/difference-between-development-stage-and-production-d0p): An environment that resides locally on your computer where you can test features and code. Usually has some mechanism in place to run the project on localhost with live reloading. 143 | - [Loaders](https://webpack.js.org/concepts/): Community written packages that make it possible to add file types other than .js to webpack's dependency graph building 144 | - [Regular Expression](https://developer.mozilla.org/en-US/docs/Glossary/Regular_expression): Rules that govern which sequence of character come up in a search. (Also known as regex) 145 | - [Glob](https://gulpjs.com/docs/en/getting-started/explaining-globs): A string of literal and/or wildcard characters used to match file paths. Globing is the act of locating files on a filesystem using one or more globs. 146 | - [CommonJS](https://flaviocopes.com/commonjs/): Standard module specification used in Node.js used to break JavaScript programs into smaller more managable pieces called modules. (e.g require(...) is the CommonJS format) 147 | - [ES Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules): Standard module specification used/supported by most modern browsers. webpack recognizes both CommonJS and ES Module syntax. 148 | - [Script defer attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script): Boolean attribute set on script tags that indicates to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded. 149 | - [Source maps](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map): A file that maps from the transformed source to the original source, enabling the browser to reconstruct the original source and present the reconstructed original in the debugger. (Babel/transpilation and minification are two examples of processes that transform your code) 150 | - [Vendor prefixes](https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix): Prefixes specified by browser vendors to enable the use of experimental modern/cutting-edge CSS features. Developers should wait to include the un-prefixed property until browser behavior is standardized 151 | - [Chunk](https://webpack.js.org/guides/code-splitting/): A name used by webpack to represents a module of code that is the result code-splitting your program 152 | - [Linter](https://sourcelevel.io/blog/what-is-a-linter-and-why-your-team-should-use-it): Is a tool that analyzes source code to flag programming errors, bugs, stylistic errors, and suspicious constructs 153 | - [Compression](https://developer.mozilla.org/en-US/docs/Web/HTTP/Compression): A method used in software development to decrease the size and redundancy of a particular file to make it more compact and efficient to serve over the web. 154 | - [HTTP/1](https://developer.mozilla.org/en-US/docs/Glossary/HTTP): Network protocol that enables transfer of hypermedia documents on the Web, typically between a browser and a server so that humans can read them. Characterized by making multiple requests per asset needed (not so efficient) 155 | - [HTTP 2](https://developer.mozilla.org/en-US/docs/Glossary/HTTP_2): A major revision of the HTTP network protocol. The primary goals for HTTP/2 are to reduce latency by enabling full request and response multiplexing, minimize protocol overhead via efficient compression of HTTP header fields. 156 | - [Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/): Exchanges, adds, or removes modules while an application is running, without a full reload. This is particularly useful for a development environment where you can maintain application state while changing the application source. 157 | - [Tweening](https://www.webopedia.com/TERM/T/tweening.html): Short for in-betweening, the process of generating intermediate frames between two images to give the appearance that the first image evolves smoothly into the second image. (e.g Animation of an object/mesh from one position to another) 158 | 159 | # Configuration and Utility Files 160 | :wrench: 161 | 162 | ## .browserslistrc 163 | 164 | [Browserlist](https://github.com/browserslist/browserslist) is a configuration settings file that is shared across multiple different tools used to build the front-end of websites. In this configuration file you can specify which browsers you wish to support. In this project the NPM packages [Autoprefixer](https://github.com/postcss/autoprefixer) and [Babel](https://github.com/babel/babel/tree/master/packages/babel-preset-env) are reliant on this configuration file to produce an output that is usable on the browsers specified in this file. 165 | 166 | For instance the contents of my file specify -> (not IE 11), so any features needed to support IE 11 browsers that would normally be added by Autoprefixer and Babel are excluded during the build process. 167 | 168 | Each line in this file represents a new browser query: 169 | 170 | - "defaults" is provided by Browserlist and represents a reasonable starting configuration for most projects 171 | - "maintained node versions" means _target all nodejs versions still maintained by the Node.js foundation_. 172 | 173 | The full list and how to make construct advanced queries can be found on the Browserlist github page. 174 | 175 | ``` 176 | defaults 177 | not IE 11 178 | not IE_Mob 11 179 | maintained node versions 180 | ``` 181 | 182 | 183 | ## .eslintrc.js 184 | 185 | [ESLint](https://eslint.org/) is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs. Through the use of this configuration file you can specify rules that propagate one of the following things... 186 | 187 | - **Nothing** (0): ESLint ignores any rule with 0 188 | - **Warning** (1): ESLint prints warning to terminal if rule is broken 189 | - **Error** (2): ESLint creates an error and the program will not compile if this rule is broken 190 | 191 | In this configuration I set the rule "no-unused-vars" to 1, so it will warn if there are any unused variables but it will still allow my program to compile. "eslint linebreak-style" is a fix for Windows computers. Normally this eslint configuration will propagate an error every time you hit the enter key on a computer running Windows because the enter key returns CRLF, whereas on UNIX environments hitting enter will just produce an LF character. This line supresses that error on Windows. [Read More](https://github.com/diegohaz/arc/issues/171) 192 | 193 | ``` 194 | ... 195 | rules: { 196 | "no-unused-vars": 1, 197 | "eslint linebreak-style": [0, "error", "windows"], 198 | } 199 | ... 200 | ``` 201 | 202 | While you could definitely have ESLint handle both formatting styles and code-quality rules a very common configuration (and the one used by this project) is using ESLint in combination with another code formatter called [Prettier](https://prettier.io/). Prettier handles the formatting of your code, and ESlint handles the code-quality rules. You can read more about these rules at the following [link](https://prettier.io/docs/en/comparison.html). 203 | 204 | However if you have both ESLint and Prettier installed ESLint will attempt to both handle formatting styles and code-quality rules by default; this may led to an annoying situation where your prettier formatter is breaking your ESLint or vice-versa. In order to prevent this we use a combination of [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) and [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier). 205 | 206 | The extends fields allows you to use a predefined/shared set of ESlint rules from another configuration. This is where the majority of the ESLint rules come from as I have only explicity declared 2 rules as seen above. The order of this array is processed from right to left so it will gather rules from "plugin:prettier/recommended" first then extend/supplement that rule set with the rules found in "eslint:recommended". [Read more](https://stackoverflow.com/questions/46544082/it-this-the-correct-way-of-extending-eslint-rules) 207 | 208 | ``` 209 | ... 210 | plugins: ["prettier"] 211 | ... 212 | extends: ["eslint:recommended","plugin:prettier/recommended"] 213 | ``` 214 | 215 | ## postcss.config.js 216 | 217 | This configuration file is used by the [postcss-loader](https://github.com/postcss/postcss-loader) NPM package. Postcss-loader is a webpack loader that is used to transform your CSS while adding a number of beneficial features such as increased readability, vendor-prefixes and polyfills. [Read more](https://postcss.org/) 218 | 219 | In this case we want to add vendor-prefixes automatically to our CSS code so we specify the postcss-loader to use the [Autoprefixer](https://github.com/postcss/autoprefixer) in this configuration file. 220 | 221 | ``` 222 | plugins: [require("autoprefixer")] 223 | ``` 224 | 225 | ## robots.txt 226 | 227 | A common file included on websites that specifies which pages of your site can be accessed/indexed by a web crawler (bot that indexes content for search engines). Below we allow all web crawlers (User-agent) to access our site with the wild card (\*) symbol. Additionally, we allow all web crawlers to access all parts of our site; note this is the configuration is used by default and doesn't even require a robots.txt file. [Read more](https://support.google.com/webmasters/answer/6062596?hl=en) 228 | 229 | ``` 230 | User-agent: * 231 | Allow: / 232 | ``` 233 | 234 | ## webpack.common.js 235 | 236 | webpack is a static module bundler for JavaScript applications. It uses configuration files to determine how to bundle your files and build its [dependency graph](https://webpack.js.org/concepts/dependency-graph/). As per the webpack documentation, it is best to have different files for both development and production environment as the needs/goals of these builds greatly differ. Of course, in order to keep the config files as DRY as possible the files are separated into their respective environments and the settings shared between the two environments are placed in this common file. Later the settings from this file will be combined separately with both the dev and production config files through the use of [webpack-merge](https://github.com/survivejs/webpack-merge). 237 | 238 | ### __Entry/Output__ 239 | 240 | Here there is a singular entry point of the program (where the bundle starts) at index.js and singular bundle produced in dist/js called [name].bundle.js which will contain most of your JS code needed to run your application. 241 | 242 | ``` 243 | const entry = path.resolve(__dirname, "./src/js/index.js"); 244 | entry: { 245 | main: entry 246 | }, 247 | output: { 248 | filename: "js/[name].bundle.js", 249 | path: path.resolve(__dirname, "dist") 250 | } 251 | ``` 252 | 253 | Many people also specify an entry point for vendor code (nodeModules and other code) as well but according to the official webpack documentation... 254 | 255 | _In webpack version < 4 it was common to add vendors as a separate entry point to compile it as a separate file (in combination with the CommonsChunkPlugin). 256 | This is discouraged in webpack 4. Instead, the optimization.splitChunks option takes care of separating vendors and app modules and creating a separate file. Do not create an entry for vendors or other stuff that is not the starting point of execution._ [Read more](https://webpack.js.org/concepts/entry-points/) 257 | 258 | ### __Loaders and Rules__ 259 | 260 | By default webpack is only able to create dependency graphs and bundle JavaScript files; in order to load all your assets through webpack (images, audio, 3d models, css, html) we rely on the rich ecosystem of community written webpack loaders available on NPM. **Note: webpack loaders are evaluated from the bottom up. This means the last loader (furthest toward the bottom) specified in a particular ruleset is the first to run.** 261 | 262 | In this a regular expression (in test) targets all .js files (except node_modules) in the project and runs it through these loaders. 263 | + [eslint-loader](https://github.com/webpack-contrib/eslint-loader) lints all .js files 264 | + babel (which requires 3 NPM packages to function [babel-loader](https://www.npmjs.com/package/babel-loader/v/8.0.0-beta.1) ,[@babel/core](https://www.npmjs.com/package/@babel/core) and [@babel/preset-env](https://www.npmjs.com/package/@babel/preset-env)) transpiles ES6 code to ES5 so older browsers can use your code as well. 265 | + Enabling the caching options on both loaders greatly speeds up build times in both prod and dev. 266 | 267 | ``` 268 | ... 269 | { 270 | test: /\.m?js$/i, 271 | exclude: nodePath, 272 | use: [ 273 | // Transplies from ES6 to ES5. 274 | { 275 | loader: "babel-loader", 276 | options: { 277 | cacheDirectory: true, 278 | presets: ["@babel/preset-env"], 279 | cacheCompression: true 280 | } 281 | }, 282 | // Lint javascript before transpiling 283 | { 284 | loader: "eslint-loader", 285 | options: { 286 | cache: true 287 | } 288 | } 289 | ] 290 | } 291 | ... 292 | ``` 293 | 294 | This rule is used to process CSS/SASS styles. The regex matches all SASS, SCSS and CSS files so you may write your styles in any of those formats. 295 | + [sass-loader](https://www.npmjs.com/package/sass-loader) compiles your SASS/SCSS code (if applicable) to CSS. 296 | + postcss-loader is used to add vendor-prefixes to your CSS. 297 | + [css-loader](https://www.npmjs.com/package/css-loader) translates CSS to CommonJS webpack can understand; this loader interprets @import and url() like import/require() and will resolve them. 298 | + Finally [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) is extract CSS into separate files if you wish. 299 | + hmr enables styles to be updated on the dev server without having to reload the page. 300 | + public path specifies what path the loader should use to resolve assets that are used within CSS files (e.g import() a font in a stylesheet) after the project is built.(*This is not the path to your static assets in src/*) 301 | + sourceMap is enabled for most loaders for easier debugging 302 | ``` 303 | { 304 | test: /\.(sa|sc|c)ss$/i, 305 | use: [ 306 | { 307 | loader: MiniCssExtractPlugin.loader, 308 | options: { 309 | // Path all assets AFTER build process 310 | publicPath: "../", 311 | hmr: true 312 | } 313 | }, 314 | // Translates CSS into CommonJS 315 | { 316 | loader: "css-loader", 317 | options: { 318 | sourceMap: true 319 | } 320 | }, 321 | // Adds vendor prefixes with Autoprefixer 322 | "postcss-loader", 323 | { 324 | // Compiles SASS to CSS 325 | loader: "sass-loader", 326 | options: { 327 | sourceMap: true 328 | } 329 | } 330 | ] 331 | } 332 | ``` 333 | ### __Plugins__ 334 | Another feature of webpack that enables you to add helpful tooling to your build process. 335 | 336 | 337 | [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) is a plugin used to generate html files either through your templating engine of choice or from an html file you supply. This plugin also automatically places your bundled .js output file, and CSS output file in the html file automatically. _This plugin alone will NOT allow you to include images in your html file (img src=... ) that are detected by the webpack build process._ This is accomplished through a combination of html-loader and file-loader. 338 | + fileName: is the name of the output file 339 | + template: is where to find the html template use as a base. 340 | + favicon: sets the favicon of the generated page. If you want more options to generate multiple favicons try the [favicons-webpack-plugin](https://www.npmjs.com/package/favicons-webpack-plugin). _Be aware including this plugin adds a lot of overhead to build times._ 341 | + inject: tells the plugin where to place the bundled JavaScript files; in this case we place them in the head as opposed to right before the end of the body tag for performance gain reasons (in combination with the "defer" attribute on script tags). 342 | ``` 343 | new HtmlWebpackPlugin({ 344 | title: "Threejs ES6 Simple Boilerplate", 345 | filename: "index.html", 346 | template: "./src/static/html/index.html", 347 | favicon: "./src/static/images/favicons/favicon.ico", 348 | inject: "head" 349 | }), 350 | //new HtmlWebpackPlugin({...}) to make more html pages 351 | ``` 352 | [preload-webpack-plugin](https://github.com/GoogleChromeLabs/preload-webpack-plugin) is a plugin that injects rel="preload" attributes into certain link tags created by the HtmlWebpackPlugin. This is used on fonts exclusively as they are most conducive to preload and support good user experience and preventing FOUC (flash of un-styled content/text). [Read more](https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content). 353 | 354 | + rel: indicates what attribute we wish to add. (can also be prefetch) 355 | + fileWhiteList: array of values of which file types to whitelist. One regex is used to match all font file types. 356 | + as: Is a key that returns the type of resource (font,script are possible vals) that will be targeted by this plugin. This function uses a regex to check all font types and return "font" if any are matched. 357 | + include: "allAssets" ensures this preloads fonts also resolved using file-loader. 358 | [Read more](https://github.com/GoogleChromeLabs/preload-webpack-plugin/issues/89) 359 | 360 | ``` 361 | new PreloadWebpackPlugin({ 362 | rel: "preload", 363 | as(entry) { 364 | if (/\.(woff|woff2|ttf|otf)$/.test(entry)) return "font"; 365 | }, 366 | fileWhitelist: [/\.(woff|woff2|ttf|otf)$/], 367 | //Includes all assets; needs more clarification 368 | include: "allAssets" 369 | }) 370 | ``` 371 | [script-ext-html-webpack-plugin](https://github.com/numical/script-ext-html-webpack-plugin) similar to PreloadWebpackPlugin but this one adds the defer attribute to the script tags in your html files. In conjunction with placing the all bundled .js files in head this increases performance. [Read more](https://flaviocopes.com/javascript-async-defer/) 372 | ``` 373 | new ScriptExtHtmlWebpackPlugin({ 374 | defaultAttribute: "defer" 375 | }), 376 | ``` 377 | 378 | ### __Optimizations and Chunking__ 379 | 380 | #### Chunks/ Code Splitting 381 | To avoid the issue of duplicate code and dependencies webpack has a feature that allows you to break you code up into chunks (also known as code splitting). As of webpack version < 4 code splitting into separate chunks is achieved through optimization.splitChunks property of a webpack config file. 382 | 383 | *Side Note: In webpack < 4 there are already intelligent defaults in place for creating chunks that fit web performance best practices. [Learn more](https://webpack.js.org/plugins/split-chunks-plugin/) 384 | #### Caching 385 | If a new build produces a new file name that differs from the version in the user's cache it will trigger a redownload of that content. Once your site has been deployed to a server a common technique is to add a hash within the file name (e.g script.7e2c49a622975ebd9b7e.js) and then change the hash value _ONLY IF_ the contents of the file changed. 386 | 387 | This ensures the visitor will only download files that change from build to build and have the rest served from their cache for better performance. Using unique hashes to explicitly force the user to redownload certain files is called _cache-busting_ and is very easy to configure and use in webpack 4. In combination with code splitting this is a very powerful tool to keep your live site fast for returning visitors. [Read more](https://webpack.js.org/guides/caching/) 388 | 389 | 390 | #### Output substitutions 391 | To automatically generate unique hashes for files webpack uses a feature called [**substitutions**](https://webpack.js.org/configuration/output/#outputfilename) that are able to add templating to the output of file names. There are several substitutions available, but the ones used in this project are 392 | + [name]: Interpreted as the name of the file used to create the output 393 | + [ext]: Interpreted as the extension of the file used to create the output 394 | + [hash]: Interpreted as a unique hash that changes each time any file in the build changes 395 | + [contenthash]: Interpreted as a unique hash that only changes if the content of a file does 396 | 397 | [hash vs contenthash vs chunkhash](https://medium.com/@sahilkkrazy/hash-vs-chunkhash-vs-contenthash-e94d38a32208) 398 | 399 | 400 | webpack has a default behavior that will change the [contenthash] even when nothing in that particular file has changed. This is because every time you build webpack generates an unique piece of code that contains the manifest and the runtime environment responsible for linking the code together. If left on this means that the end-user will ultimately redownload whole files even when the content is identical to the one in their cache. In order to prevent this we use the following settings 401 | + runtimeChunk:"single" -> Tells webpack to take the runtime code needed build project and offload it into a runtime .js file so on subsequent builds if no content changed their contenthashes will remain the same. 402 | + moduleIds:"hashed" -> Without this option set webpack will increment the module.id field of each all chunks when a build is triggered; this often leads to the vendor/node_modules folder chunk changing its contenthash value (even though no vendor code was changed or added) and triggering a large redownload of data for the user. 403 | + splitChunks: In conjunction with cacheGroups is how to perform chunking in webpack. In the example below I name a chunk styles and target all .css via a regex. 404 | 405 | ``` 406 | ... 407 | optimization: { 408 | runtimeChunk: "single", 409 | moduleIds: "hashed", 410 | splitChunks: { 411 | cacheGroups: { 412 | styles: { 413 | name: "styles", 414 | test: /\.css$/, 415 | chunks: "all", 416 | enforce: true 417 | } 418 | } 419 | } 420 | } 421 | ... 422 | ``` 423 | 424 | # Development Environment 425 | :computer: 426 | ### __Overview__ 427 | The development environment builds to dist/. It includes a development server with live-reload, loaders for static files, and basic chunking optimizations. Hashing is not needed instead files are output with this simple substitution [name].[ext] 428 | 429 | ### __Loaders__ 430 | + [file-loader](https://webpack.js.org/loaders/file-loader/): is used to load most static assets including images, html files, fonts, 3D models and audio. Add to the regex to suit your own needs. 431 | + esModule: false -> This setting is critical to getting this library to work with html-loader. This turns off file-loaders ES2015 module syntax and reverts it to CommonJS so it will correctly parse image src attributes with html-loader. [Read More](https://stackoverflow.com/questions/59070216/webpack-file-loader-outputs-object-module) html-loader doesn't yet support es module syntax and it is a known [issue](https://github.com/webpack-contrib/html-loader/issues/203). 432 | 433 | ``` 434 | { 435 | test: /\.(png|svg|jpe?g|gif|ico)$/i, 436 | use: { 437 | loader: "file-loader", 438 | options: { 439 | outputPath: "images/", 440 | name: "[name].[ext]", 441 | esModule: false 442 | } 443 | } 444 | } 445 | ``` 446 | 447 | ### __Dev Server__ 448 | Spins up a development server at the specified port using [webpack-dev-server](https://webpack.js.org/configuration/dev-server/). 449 | + hot:true -> Enables hot module reloading of the server 450 | + compress:true -> Serves development build via compressed formats for better performance. 451 | + contentBase -> Specifies path to build/output directory. 452 | 453 | ``` 454 | devServer: { 455 | contentBase: "./dist", 456 | compress: true, 457 | hot: true, 458 | port: 9000 459 | } 460 | ``` 461 | 462 | ### __Plugins__ 463 | Styles are loaded in the webpack.common.js config file and the mini-extract-css-plugin is used to configure the output of the .css file created by the development build. 464 | ``` 465 | new MiniCssExtractPlugin({ 466 | filename: "css/style.css", 467 | chunkFilename: "css/style.[id].css" 468 | }) 469 | ``` 470 | ### __Optimizations__ 471 | The node_modules folder is separated and chunked into a group called vendor to prevent redownload. This is done by default in webpack 4 but included for clarity's sake. 472 | 473 | ``` 474 | ... 475 | cacheGroups: { 476 | vendor: { 477 | test: /[\\/]node_modules[\\/]/, 478 | name: "vendor", 479 | chunks: "all", 480 | reuseExistingChunk: true 481 | }, 482 | } 483 | ... 484 | ``` 485 | 486 | # Production Environment 487 | :tada: 488 | ### __Overview__ 489 | The production environment builds to dist/. There are a number of optimization in place that help produce the fastest most efficient production site such as... compression of image files, minification of .html, .js , .css files, gzip compression of all files, and automatic removal of unused css styles through out the whole project. Each file is output with a [contenthash] substitution in order to facilitate proper hashing on file names that only change if a particular file's content does. 490 | 491 | ### __Loaders__ 492 | + [file-loader](https://webpack.js.org/loaders/file-loader/): Loads in the same manner as the development environment and is used to include most static assets including images, html files, fonts, 3D models and audio. Add to the regex to suit your own needs. 493 | 494 | ``` 495 | { 496 | test: /\.(png|svg|jpe?g|gif|ico)$/i, 497 | use: { 498 | loader: "file-loader", 499 | options: { 500 | outputPath: "images/", 501 | name: "[name].[contenthash].[ext]", 502 | esModule: false 503 | } 504 | } 505 | } 506 | ``` 507 | 508 | ### __Plugins__ 509 | + mini-css-extract-loader: Styles are loaded in the webpack.common.js config file and the mini-extract-css-plugin is used to configure the output of the .css file created by the production build. 510 | ``` 511 | new MiniCssExtractPlugin({ 512 | filename: "css/style.css", 513 | chunkFilename: "css/style.[id].css" 514 | }) 515 | ``` 516 | + [imagemin-webpack-plugin](https://www.npmjs.com/package/imagemin-webpack-plugin): Used to compress your image files that are produced by your production build process. By default this plugin has a set a default compression methods that compresses gif, png and jpg files. The [imagemin-mozjpeg](https://www.npmjs.com/package/imagemin-mozjpeg) package is a plugin for Imagemin that offers a wide range of options for compression quality of jpeg images. 517 | ``` 518 | new ImageminPlugin({ 519 | optipng: { 520 | optimizationLevel: 6 521 | }, 522 | plugins: [ 523 | imageminMozjpeg({ 524 | quality: 100, 525 | progressive: true 526 | }) 527 | ] 528 | }) 529 | ``` 530 | + [compression-webpack-plugin](https://www.npmjs.com/package/compression-webpack-plugin): Used to gzip all files that match the specified test regex. In this case all html, css, and js files are gziped. _Note both the normal version of the file and the zipped version are produced. When served over a network that accepts gzipped content the zipped version will be sent_. 531 | ``` 532 | new CompressionPlugin({ 533 | test: /\.(html|css|js)(\?.*)?$/i 534 | }) 535 | ``` 536 | + [purgecss-webpack-plugin](https://www.npmjs.com/package/purgecss-webpack-plugin): [Purgecss](https://www.purgecss.com/) is a tool that detects any unused css code in your project and deletes it so you are not serving unnecessary styles; this is particularly helpful when using a styling framework like Bootstrap that includes a lot of extraneous code. 537 | + paths: Is used in conjunction with [glob](https://www.npmjs.com/package/glob) to specify a pattern that searches the entire project for css to potentially delete. 538 | ``` 539 | new PurgecssPlugin({ 540 | paths: glob.sync("src/**/*", { nodir: true }) 541 | }) 542 | ``` 543 | _Note: These plugins are included in the optimization.minimizer section because they deal with minification of code._ 544 | + [terser-webpack-plugin](https://github.com/webpack-contrib/terser-webpack-plugin): Is a tool used to minify all .js files (target by default) in the project. Running cache, and parallel speeds up the minification process. 545 | ``` 546 | new TerserJSPlugin({ 547 | cache: true, 548 | parallel: true, 549 | sourceMap: true 550 | }) 551 | ``` 552 | + [optimize-css-assets-webpack-plugin](https://www.npmjs.com/package/optimize-css-assets-webpack-plugin): A webpack plugin to optimize and minimize CSS assets. Set of defaults is used so no configuration is needed. 553 | ``` 554 | new OptimizeCSSAssetsPlugin({}) 555 | ``` 556 | 557 | ### __Optimizations__ 558 | In the production environment is really where chunking optimizations matter. A common tactic (and the one used in the development environment) is to create a custom chunk for all node_modules code. This is a good first step to reducing the end-users bundle size as they will only redownload those node_modules dependencies if any of the code within them changes. However, with this configuration the user has to download the _whole vendor chunk if any of the files in node_modules changes_. This could incur redownloading of data that is identical to the one in the end user's cache. 559 | 560 | To fix this we take it a step further we create a chunk for each individual NPM package within the node_modules/ folder so that if any dependency is altered it ensures the return visitor of the site only download the NPM packages that changed. 561 | 562 | This results in a js file for each NPM package included in the dependencies of your project. In the past where most websites were served via HTTP/1 more requests meant an overall slower site and a longer load time. Today most site serve requests via [HTTP/2](https://kinsta.com/learn/what-is-http2/) which allows parallel multiplexed requests and is generally fast regardless of the number of requests. 563 | [Source](https://medium.com/hackernoon/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758) 564 | [Source2](https://calendar.perfplanet.com/2019/bundling-javascript-for-performance-best-practices/) 565 | 566 | 567 | ``` 568 | ... 569 | cacheGroups: { 570 | vendor: { 571 | test: /[\\/]node_modules[\\/]/, 572 | reuseExistingChunk: true, 573 | name(module) { 574 | const packageName = module.context.match( 575 | /[\\/]node_modules[\\/](.*?)([\\/]|$)/ 576 | )[1]; 577 | return `vendor/npm.${packageName.replace("@", "")}`; 578 | } 579 | }, 580 | } 581 | ... 582 | ``` 583 | 584 | 585 | # three.js Scene and Examples 586 | :three: 587 | 588 | ## Basic Scene Setup 589 | There are already a number of good resources detailing how to setup a basic three.js scene. In this project I used [discoverthreejs.com](https://discoverthreejs.com/book/first-steps/first-scene/)'s code to create the scene and cube mesh. Similarly the [offical three.js documentation](https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene) has a great guide for setting up a scene. For beginners I recommend reading both. 590 | 591 | ## Imports 592 | At the top of the file is where you import all dependencies and vendor code you need in your program. webpack is able to recognize a wide variety of different module formats (AMD, CommonJS etc...) but for source files I choose ES2015 module syntax. 593 | ### Library/Vendor Code 594 | _Note: Since the three.js npm package is based on the module version many features must be explicitly exported to be used as they are not included in the main module file._ These are features are found in the node_modules/three/examples/... path. Many tutorials online are written using the non-module version of three.js where loaders are included under the THREE namespace (e.g THREE.GLTFLoader(...)), so if there is an error always check if you imported the specific component first. 595 | [Read more](https://threejs.org/docs/#manual/en/introduction/Import-via-modules) 596 | 597 | Internally the [GLTFLoader](https://threejs.org/docs/#examples/en/loaders/GLTFLoader) and [DRACOLoader](https://threejs.org/docs/#examples/en/loaders/DRACOLoader) rely on ES6 promises to work. The promise polyfill is included to support these loaders on IE11. 598 | 599 | ``` 600 | import * as THREE from "three"; 601 | import TWEEN from "@tweenjs/tween.js"; 602 | import { WEBGL } from "three/examples/jsm/WebGL.js"; 603 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; 604 | import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js"; 605 | import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; 606 | import * as Stats from "stats.js"; 607 | import "promise-polyfill/src/polyfill"; 608 | import { SPE } from "./vendor/SPE.js"; 609 | ``` 610 | ### Static Assets 611 | Thanks to the included loaders we are now able to parse nearly any type of static resource and add it to the dependency graph. Below audio, images and a 3D model are parsed via fileLoader so we are able to import static resources in the same way as any other ES2015 module. 612 | ``` 613 | import fireSfx from "../static/audio/fire_compressed.mp3"; 614 | import modelTexture from "../static/images/grass-texture.jpg"; 615 | import particleTexture from "../static/images/lightning-texture.png"; 616 | import dragonModel from "../static/models/dragon/dracoDragon.gltf"; 617 | ``` 618 | Here we import our html and style files. We do this to make webpack aware of these resources (they are added to the dependency graph) so that html-loader can be parse img src tags from our html file. 619 | ``` 620 | import "../sass/style.scss"; 621 | import "../static/html/index.html"; 622 | ``` 623 | now in index.html ... the src attribute of img tags are able to be replaced by html-loader. 624 | ``` 625 | 626 | ``` 627 | ## Loading Screen 628 | Loaders in three.js load models asynchronously. This means that the other components of the scene are not blocked by the loading of a model. However, loading a 3D model may take some time and a solution many sites opt for is to display a loading screen until the model is fully loaded. The loading screen code from this boilerplate is from the [three.jsforum](https://discourse.threejs.org/t/basic-loading-screen/2332) and the loading animation is from [this codepen](https://codepen.io/ZyrianovViacheslav/pen/wWVrLQ). There are 3 step when creating a loading screen in three.js: 629 | 1. Add the loading screen html markup and styles. On initial load have the load screen cover the entire viewport. 630 | ``` 631 |
632 |
633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 |
643 |
644 | ... 645 | .loader{ 646 | z-index:2; 647 | $loader-size: 8; 648 | $animation-duration: 2s; 649 | $animation-speed: 10; 650 | ... 651 | ``` 652 | 2. Set up a [LoadingManager](https://threejs.org/docs/#api/en/loaders/managers/LoadingManager) and give it callbacks for what happens for its onLoad() event. This callback is triggered when the model is fully loaded; indicating its time to remove the loading screen by lowering its opcaity. (fade-out class) 653 | ``` 654 | manager = new THREE.LoadingManager(); 655 | manager.onLoad = function() { 656 | const loadingScreen = document.getElementById("loader-wrap"); 657 | loadingScreen.classList.add("fade-out"); 658 | loadingScreen.addEventListener("transitionend", onTransitionEnd); 659 | }; 660 | ``` 661 | 3. Pass the created manager instance into the constructor of your loader 662 | ``` 663 | loader = new GLTFLoader(manager); 664 | ``` 665 | ## Tween.js + three.js 666 | It is entirely possible to animate your three.js scene by incrementing values in the update() function of the render loop. 667 | ``` 668 | function update() { 669 | mesh.rotation.z += 0.01; 670 | mesh.rotation.x += 0.01; 671 | mesh.rotation.y += 0.01; 672 | } 673 | ``` 674 | This is good for simple animations but for a more robust animation experience Tween.js is a library that pairs well with three.js. A tween (from in-between) is a concept that allows you to change the values of the properties of an object in a smooth way. Tween.js works well with three.js because many of the Objects in your scene have their properties (Object3D .position, .rotation, .scale etc...) expressed via a three.js class called [vector3](https://threejs.org/docs/#api/en/math/Vector3). 675 | 676 | Tweens take in 3D vectors by default so we can easily animate the rotation or a similar property. 677 | + Here a tween is set on the cube's (called mesh) rotation. It is rotated over a course of 8s (duration = 8000) from the position (x:0, y:0, z:0) to (x:Math.PI, y:Math.PI, z:Math.PI) a full rotation. 678 | + After starting the tween, be sure to call TWEEN.update(); in update() so the tween is updated on every frame 679 | ``` 680 | cubeRotateTweenA = new TWEEN.Tween(mesh.rotation) 681 | .to(rotateCoords, duration) 682 | .easing(TWEEN.Easing.Linear.None) 683 | .onUpdate(() => { 684 | }); 685 | // Start the tween 686 | cubeRotateTweenA.start(); 687 | cubeRotateTweenA.repeat(Infinity); 688 | 689 | function update(){ 690 | ... 691 | TWEEN.update(); 692 | ... 693 | } 694 | ``` 695 | For the full options see the [tween documentation user's guide](https://github.com/tweenjs/tween.js/blob/master/docs/user_guide.md). 696 | 697 | ## Loading a Model 698 | This boilerplate currently only supports the loading of 3D models that can be contained into a single file. For example many .gLTF files can come with upwards of 3 files needed to properly render the model. 699 | + scene.gltf (JSON file that describes structure and composition of scene) 700 | + scene.bin (Binary data that contains geometry or animation data) 701 | + textures (Image files that contain texture data for the model) 702 | 703 | [Read More about GLTF](https://www.khronos.org/files/gltf20-reference-guide.pdf) 704 | 705 | In webpack there really is not an elegant way to associate all 3 files with each other while having each of the files added to webpack's dependency graph. One solution would be to just statically copy the necessary files using the [CopyWebpackplugin](https://webpack.js.org/plugins/copy-webpack-plugin/) and just reference them as static resources. This works but the assets are not added to the dependency graph which is the main feature for bundling these assets with webpack. 706 | 707 | Instead its better to just combine all the necessary files into a single file (if possible for your 3D model format). For .gltf files they are able to be combined into a single .glb file which includes the textures and binary data for the .gltf model. Similarly compressing a .gltf file with Draco compression results in a single .gltf file that has the binary data and textures embedded into that single file. Because the .gltf format is optimized for the web _you should generally always be using .glb or draco compressed .gltf models_ 708 | 709 | ### Gltf-pipeline compression 710 | [gltf-pipeline](https://github.com/AnalyticalGraphicsInc/gltf-pipeline) is a tool made for changing .gltf files to .glb files (size doesn't change; but model is contained in a single .glb file) and compressing .gltf files to draco compressed .gltf files. The package offers a node.js API but there seems to be [issues](https://github.com/AnalyticalGraphicsInc/cesium/issues/8452) with the latest release that prevent the API from working correctly on newer versions of node. 711 | 712 | The CLI-tool works correctly and here is a sample command to convert .gltf -> draco compressed .gltf 713 | + -i input file: model.gltf 714 | + -o output file: modelDraco.gltf 715 | + -d Runs draco compression 716 | ``` 717 | gltf-pipeline -i model.gltf -o modelDraco.gltf -d 718 | ``` 719 | To get an idea of how well this compression works I have included both the uncompressed scene.gltf and scene.bin and the resultant draco compressed file. 720 | 721 | ##### Uncompressed dragon 722 | _Nearly 30MB_ 723 | ``` 724 | scene.bin (~30,000kb) + scene.gltf(~20kb) = 30,020kb 725 | ``` 726 | ##### Draco Compressed dragon 727 | _1.3MB!_ 728 | ``` 729 | dracoDragon.gltf(~1300kb) = 1300kb 730 | ``` 731 | ### Loading a draco compressed .gltf 732 | In three.js the draco loader is used for loading pure .drc (draco) files. In order to load a draco compressed .gtlf we need instances of both GLTFLoader and DRACOLoader. 733 | 734 | 735 | 736 | _You must pass the correct decoding algorithm path to the draco loader in order to correctly display the model._ This decoding files are located at the path location node_modules/three/examples/js/libs/draco. webpack doesn't currently have a way to import a whole directory so the best solution is use CopyWebpackPlugin to copy the draco decoding directory on builds in both dev/prod and link to the path statically. 737 | ``` 738 | new CopyPlugin([ 739 | { 740 | from: path.resolve(__dirname, "./src/js/vendor/draco"), 741 | to: "js/vendor/draco" 742 | } 743 | ]) 744 | ``` 745 | 746 | 747 | Once both loaders have been instantiated; pass the draco loader to be used internally by the gltf loader via the .setDRACOLoader() method. Now you may load the model normally. 748 | ``` 749 | loader = new GLTFLoader(manager); 750 | dracoLoader = new DRACOLoader(); 751 | dracoLoader.setDecoderPath(dracoDecodePath); 752 | loader.setDRACOLoader(dracoLoader); 753 | ``` 754 | #### Adding a texture to a loaded 3D model 755 | In order to apply your own custom textures to your loaded models you first need to create a new instance of TextureLoader() and pass in your image. In order to correctly orient the texture on a .gltf model 756 | ``` 757 | texture = new THREE.TextureLoader().load(modelTexture); 758 | // these settings are needed to correctly apply a texture to a .gLTF model 759 | texture.encoding = THREE.sRGBEncoding; 760 | texture.flipY = false; 761 | texture.wrapS = THREE.RepeatWrapping; 762 | texture.wrapT = THREE.RepeatWrapping; 763 | ``` 764 | [Read more](https://stackoverflow.com/questions/54033037/need-help-on-imported-gltf-model-with-changing-texture-of-it') 765 | 766 | Once the .gltf is loaded we use Object3D's [Traverse](https://threejs.org/docs/#api/en/core/Object3D.traverse) method that takes a callback and executes it on all of the model's descendants. We specifically look at each descendant and if it is a mesh we change the map and metalness property of [MeshStandardMaterial](https://threejs.org/docs/#api/en/materials/MeshStandardMaterial) 767 | ``` 768 | gltf.scene.traverse(function(child) { 769 | if (child instanceof THREE.Mesh) { 770 | //child.material is an instance of MeshStandardMaterial 771 | child.material.map = texture; 772 | child.material.metalness = 1; 773 | } 774 | }); 775 | ``` 776 | ### [Shader Particle Engine](http://squarefeet.github.io/ShaderParticleEngine/) 777 | Since three.js doesn't have a native particle system implementation I included this particle library as good starting point for learning particles. This library is rather dated and a [rewrite](https://github.com/squarefeet/ShaderParticleEngine/issues/50) was in the works at one point... Running the engine will produce console warnings similar to 778 | ``` 779 | THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead 780 | ``` 781 | but the core functionality of the particle system still works great. This library was intended to be included after three.js as a script tag so that the THREE namespace would be in the global scope. This works well for the script tags, but **the npm package/build of SPE doesn't work as Three is no longer in the global scope**. 782 | 783 | To solve this I just imported three into SPE.js and used ES2015 syntax to export the SPE class. So the module version of this library that works is located in src\js\vendor\SPE.js. This is the only file you need so you may remove the NPM dependency from package.json. 784 | 785 | The library has an excellent API and documentation and the example particle stream in this boilerplate is taken from [this example](https://github.com/squarefeet/ShaderParticleEngine/blob/master/examples/basic.html). 786 | 787 | That being said the library is open source and desperately in need a of ES6 rewrite if anyone wants to contribute. 788 | 789 | # Learning Resources 790 | :books: 791 | ### __three.js__ 792 | + [Threejs Resources Page](https://threejs.org/docs/#manual/en/introduction/Useful-links) 793 | + [Threejs in browser editor](https://threejs.org/editor/): Online app to experiment with Threejs 794 | + [Stanford 3D Scanning Repository](http://graphics.stanford.edu/data/3Dscanrep/): Free 3D models to experiment with 795 | + [Threejs loader](https://discoverthreejs.com/apps/loader/): Online app where you can test your glTF, FBX and dae 3D models. 796 | + [Gltf Viewer](https://gltf.insimo.com/): Online app where you can upload .glTF files and either convert them to .glb files or compress them with draco compression. 797 | + [Discover Threejs](https://discoverthreejs.com/): Good beginner resource and introduction to setting up a Threejs scene written by one of the libraries main contributors. (Not finished) 798 | + [Sketchfab](https://sketchfab.com/): Selection of free and paid 3D models in many formats. Models are usually licensed with [Attribution 4.0](https://creativecommons.org/licenses/by/4.0/) which means you are free to adapt and share the model as long as you give credit. 799 | + [Google poly](https://poly.google.com/): Another curated selection of free 3D models available on a wide variety of formats. 800 | + [Turbosquid](https://www.turbosquid.com/): Collection of paid and free 3D models to experiment with. 801 | + [Threejs fundamentals](https://threejsfundamentals.org/): Excellent (if not one of the only) resource to learn modern threejs. Topics range from beginner to advanced. 802 | ### __webpack__ 803 | + [webpack docs](https://webpack.js.org/guides/getting-started/): Great documentation and generally has everything you need to understand webpack. 804 | + [webpack beginner guide article](https://dev.to/pixelgoo/how-to-configure-webpack-from-scratch-for-a-basic-website-46a5): Article I found useful when learning webpack. 805 | + [More on webpack chunks](https://gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693): Good guide on webpack < 4 chunking mechanism 806 | + [webpack starter kits](https://webpack.js.org/starter-kits/): Collection of user created boilerplates curated by webpack that offer good starting webpack configs for a variety projects. 807 | 808 | # Credits 809 | :bookmark_tabs: 810 | + Favicon Source: [pikpng](https://www.pikpng.com/pngvi/hRxThh_3d-silver-cube-3d-transparent-cube-png-clipart/) 811 | + Dragon 3D Model Source: [Sketchfab](https://sketchfab.com/3d-models/dragon-f4b218fb3b0d49e9b3a27367850517b8). Originally from the Stanford 3D scanning repository. 812 | + Fire Audio Source: [freesoundeffects](https://www.freesoundeffects.com/free-track/crackling-fireplace-89305/) 813 | + Loading Screen Code: [Threejs Forum](https://discourse.threejs.org/t/basic-loading-screen/2332) 814 | + Wedgie Font: [1001freefonts](https://www.1001freefonts.com/wedgie.font) 815 | + Particle images pack: [opengameart](https://opengameart.org/content/particle-pack-80-sprites) 816 | 817 | 818 | If there is something you feel you can explain better, or other fixes to this project feel free to do a PR. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "threejs-es6-webpack-boilerplate", 3 | "version": "1.0.0", 4 | "author": "Ethan Soo Hon", 5 | "description": "Boilerplate for threejs es6 webpack workflow", 6 | "main": "index.js", 7 | "scripts": { 8 | "clean": "rimraf dist/*", 9 | "lint": "eslint .", 10 | "lint-fix": "eslint --fix .", 11 | "build": " npm run clean && webpack --config webpack.prod.js", 12 | "build-dll": "webpack --config webpack.vendor.config.js", 13 | "start-dev": "webpack-dev-server --open --config webpack.dev.js", 14 | "compress-draco": "gltf-pipeline -i ./src/static/models/dragon/scene.gltf -o ./src/static/models/dragon/dracoDragon.gltf -d", 15 | "compress-glb": "gltf-pipeline -i ./src/static/models/dragon/scene.gltf -o ./src/static/models/dragon/scene.glb" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate" 20 | }, 21 | "keywords": [ 22 | "Threejs" 23 | ], 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/issues" 27 | }, 28 | "homepage": "https://github.com/ethanny2/PersonalSite#readme", 29 | "devDependencies": { 30 | "@babel/core": "^7.11.4", 31 | "@babel/preset-env": "^7.11.0", 32 | "autoprefixer": "^9.8.6", 33 | "babel-loader": "^8.1.0", 34 | "compression-webpack-plugin": "^5.0.1", 35 | "copy-webpack-plugin": "^6.0.3", 36 | "css-loader": "^3.6.0", 37 | "eslint": "^6.8.0", 38 | "eslint-config-prettier": "^6.11.0", 39 | "eslint-loader": "^3.0.4", 40 | "eslint-plugin-prettier": "^3.1.4", 41 | "exports-loader": "^1.1.0", 42 | "file-loader": "^5.1.0", 43 | "gltf-pipeline": "^2.1.10", 44 | "html-loader": "^0.5.5", 45 | "html-webpack-plugin": "^3.2.0", 46 | "image-webpack-loader": "^6.0.0", 47 | "imagemin-mozjpeg": "^8.0.0", 48 | "imagemin-webpack-plugin": "^2.4.2", 49 | "imports-loader": "^1.1.0", 50 | "mini-css-extract-plugin": "^0.8.2", 51 | "node-sass": "^4.14.1", 52 | "optimize-css-assets-webpack-plugin": "^5.0.3", 53 | "postcss-loader": "^3.0.0", 54 | "preload-webpack-plugin": "^3.0.0-beta.4", 55 | "prettier": "^1.19.1", 56 | "purgecss-webpack-plugin": "^1.6.0", 57 | "rimraf": "^3.0.2", 58 | "sass-loader": "^8.0.2", 59 | "script-ext-html-webpack-plugin": "^2.1.4", 60 | "stats.js": "^0.17.0", 61 | "terser-webpack-plugin": "^2.3.8", 62 | "webpack": "^4.44.1", 63 | "webpack-cli": "^3.3.12", 64 | "webpack-dev-server": "^3.11.0", 65 | "webpack-merge": "^4.2.2" 66 | }, 67 | "dependencies": { 68 | "@tweenjs/tween.js": "^18.6.0", 69 | "promise-polyfill": "8.1.3", 70 | "shader-particle-engine": "git+https://github.com/blaze33/ShaderParticleEngine.git#d019c8bf3766997f39dccda943fcee9586e84d94", 71 | "three": "^0.111.0" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require("autoprefixer")] 3 | }; 4 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import TWEEN from "@tweenjs/tween.js"; 3 | import { WEBGL } from "three/examples/jsm/WebGL.js"; 4 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; 5 | import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js"; 6 | import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; 7 | import * as Stats from "stats.js"; 8 | import "promise-polyfill/src/polyfill"; 9 | /* 10 | Do not use/import the SPE node_module as it doesn't properly work without three in the global scope 11 | */ 12 | import SPE from "shader-particle-engine"; 13 | 14 | /* 15 | Importing static assets for use in your .js file 16 | */ 17 | import fireSfx from "../static/audio/fire_compressed.mp3"; 18 | import modelTexture from "../static/images/grass-texture.jpg"; 19 | import particleTexture from "../static/images/lightning-texture.png"; 20 | import dragonModel from "../static/models/dragon/dracoDragon.gltf"; 21 | 22 | /* 23 | Adding your style files and html files to webpacks dependency graph so it can be 24 | parsed by loaders 25 | */ 26 | import "../sass/style.scss"; 27 | import "../static/html/index.html"; 28 | 29 | // To enable caching across all loaders that use FileLoader 30 | THREE.Cache.enabled = true; 31 | 32 | let container; 33 | let camera; 34 | let renderer; 35 | let scene; 36 | let mesh; 37 | let stats; 38 | let controls; 39 | let loader; 40 | let dracoLoader; 41 | let texture; 42 | let emitter, particleGroup; 43 | let audioElem; 44 | let manager; 45 | const duration = 8000; 46 | let cubeRotateTweenA; 47 | // Used in cubeRotateTweenA to perform a full rotation from 0. 48 | const rotateCoords = new THREE.Vector3(Math.PI, Math.PI, Math.PI); 49 | const clock = new THREE.Clock(); 50 | // path to draco decoding files; necessary to use DracoLoader 51 | const dracoDecodePath = "./js/vendor/draco/"; 52 | 53 | function init() { 54 | // Create stats for performance monitoring 55 | stats = new Stats(); 56 | stats.showPanel(0, 1, 2); 57 | document.getElementById("stats").appendChild(stats.dom); 58 | 59 | // Setup loading manager and callbacks it uses to control loading screen 60 | manager = new THREE.LoadingManager(); 61 | manager.onLoad = function() { 62 | const loadingScreen = document.getElementById("loader-wrap"); 63 | loadingScreen.classList.add("fade-out"); 64 | loadingScreen.addEventListener("transitionend", onTransitionEnd); 65 | }; 66 | manager.onError = function(url) { 67 | console.log("There was an error loading " + url); 68 | }; 69 | 70 | // Get a reference to the container element that will hold our scene 71 | container = document.querySelector("#three-container"); 72 | scene = new THREE.Scene(); 73 | scene.background = new THREE.Color(0x8fbcd4); 74 | 75 | // every object is initially created at ( 0, 0, 0 ) 76 | // we'll move the camera back a bit so that we can view the scene 77 | const fov = 35; 78 | const aspect = container.clientWidth / container.clientHeight; 79 | const near = 0.1; 80 | const far = 100; 81 | camera = new THREE.PerspectiveCamera(fov, aspect, near, far); 82 | camera.position.set(0, 0, 10); 83 | 84 | // create the cube mesh 85 | const geometry = new THREE.BoxBufferGeometry(2, 2, 2); 86 | const material = new THREE.MeshStandardMaterial({ color: 0x800080 }); 87 | mesh = new THREE.Mesh(geometry, material); 88 | scene.add(mesh); 89 | 90 | // Create a directional light 91 | const light = new THREE.DirectionalLight(0xffffff, 3.0); 92 | light.position.set(10, 10, 10); 93 | scene.add(light); 94 | 95 | // Add helper grid 96 | var gridHelper = new THREE.GridHelper(20, 20); 97 | gridHelper.position.set(0, -2, 0); 98 | scene.add(gridHelper); 99 | 100 | // Add helper axis 101 | var axesHelper = new THREE.AxesHelper(5); 102 | scene.add(axesHelper); 103 | 104 | // Load in a texture for the dragon model 105 | texture = new THREE.TextureLoader().load(modelTexture); 106 | // these settings are needed to correctly apply a texture to a .gLTF model 107 | texture.encoding = THREE.sRGBEncoding; 108 | texture.flipY = false; 109 | texture.wrapS = THREE.RepeatWrapping; 110 | texture.wrapT = THREE.RepeatWrapping; 111 | 112 | // Set up gltf loader with the manager to track model loading 113 | loader = new GLTFLoader(manager); 114 | dracoLoader = new DRACOLoader(); 115 | dracoLoader.setDecoderPath(dracoDecodePath); 116 | loader.setDRACOLoader(dracoLoader); 117 | 118 | // Load the dracoCompressed .gLTF model 119 | loader.load( 120 | dragonModel, 121 | function(gltf) { 122 | // apply texture loaded via traverse() 123 | gltf.scene.traverse(function(child) { 124 | if (child instanceof THREE.Mesh) { 125 | //child.material is an instance of MeshStandardMaterial 126 | child.material.map = texture; 127 | child.material.metalness = 1; 128 | } 129 | }); 130 | // resize and move model, face dragon toward screen 131 | gltf.scene.scale.set(0.08, 0.08, 0.08); 132 | gltf.scene.rotation.set(0, Math.PI / 2, 0); 133 | gltf.scene.position.set(3, -0.5, 0); 134 | scene.add(gltf.scene); 135 | }, 136 | // called while loading is progressing 137 | function(xhr) { 138 | if (xhr.lengthComputable) { 139 | console.log("percent: " + (xhr.loaded / xhr.total) * 100); 140 | } 141 | }, 142 | // called when loading has errors 143 | function(error) { 144 | console.log("An error occured loading gltf model: " + error); 145 | } 146 | ); 147 | 148 | // Initialize the rotation tween from 0deg to 360deg 149 | cubeRotateTweenA = new TWEEN.Tween(mesh.rotation) 150 | .to(rotateCoords, duration) 151 | .easing(TWEEN.Easing.Linear.None) 152 | .onUpdate(() => { 153 | /* 154 | Not needed to implement; 155 | three.js renderer will look at all Object3D instances and their properties before 156 | rendering,you don't need to use an explicit onUpdate callback to increment the rotation. 157 | */ 158 | }); 159 | // Start the tween 160 | cubeRotateTweenA.start(); 161 | cubeRotateTweenA.repeat(Infinity); 162 | 163 | // create a WebGLRenderer and set its width and height 164 | renderer = new THREE.WebGLRenderer({ antialias: true }); 165 | renderer.setSize(container.clientWidth, container.clientHeight); 166 | renderer.outputEncoding = THREE.sRGBEncoding; 167 | renderer.setPixelRatio(window.devicePixelRatio); 168 | // add the automatically created element to the page 169 | container.appendChild(renderer.domElement); 170 | 171 | // set up orbit controls 172 | controls = new OrbitControls(camera, renderer.domElement); 173 | controls.enabled = true; 174 | // start the fire audio clip and loop 175 | audioElem = createSound(); 176 | // start the animation loop 177 | renderer.setAnimationLoop(() => { 178 | stats.begin(); 179 | update(); 180 | render(); 181 | stats.end(); 182 | }); 183 | } 184 | 185 | // perform any updates to the scene, called once per frame 186 | // avoid heavy computation here 187 | function update() { 188 | // Update tween in order to see rotation animation 189 | // OR just increment the rotation manually 190 | TWEEN.update(); 191 | /* 192 | mesh.rotation.z += 0.01; 193 | mesh.rotation.x += 0.01; 194 | mesh.rotation.y += 0.01; 195 | */ 196 | particleGroup.tick(clock.getDelta()); 197 | } 198 | 199 | // render, or 'draw a still image', of the scene 200 | function render() { 201 | renderer.render(scene, camera); 202 | } 203 | 204 | // a function that will be called every time the window gets resized. 205 | // It can get called a lot, so don't put any heavy computation in here! 206 | function onWindowResize() { 207 | // set the aspect ratio to match the new browser window aspect ratio 208 | camera.aspect = container.clientWidth / container.clientHeight; 209 | 210 | // update the camera's frustum 211 | camera.updateProjectionMatrix(); 212 | 213 | // update the size of the renderer AND the canvas 214 | renderer.setSize(container.clientWidth, container.clientHeight); 215 | } 216 | 217 | // Create particle group and emitter 218 | function initParticles() { 219 | particleGroup = new SPE.Group({ 220 | texture: { 221 | value: new THREE.TextureLoader().load(particleTexture) 222 | }, 223 | blending: THREE.AdditiveBlending, 224 | fog: true, 225 | maxParticleCount: 10000 226 | }); 227 | emitter = new SPE.Emitter({ 228 | maxAge: { 229 | value: 2 230 | }, 231 | position: { 232 | value: new THREE.Vector3(-3, 0, 0), 233 | spread: new THREE.Vector3(0, 3, 0) 234 | }, 235 | acceleration: { 236 | value: new THREE.Vector3(0, -10, 0), 237 | spread: new THREE.Vector3(10, 0, 10) 238 | }, 239 | velocity: { 240 | value: new THREE.Vector3(0, 25, 0), 241 | spread: new THREE.Vector3(5, 7.5, 10) 242 | }, 243 | color: { 244 | value: [new THREE.Color(0xfd2525), new THREE.Color(0xff3f0b)] 245 | }, 246 | wiggle: { 247 | spread: 3 248 | }, 249 | size: { 250 | value: 1 251 | }, 252 | particleCount: 2000 253 | }); 254 | particleGroup.addEmitter(emitter); 255 | scene.add(particleGroup.mesh); 256 | } 257 | function createSound() { 258 | let elem = new Audio(fireSfx); 259 | elem.loop = true; 260 | return elem; 261 | } 262 | function toggleSound() { 263 | if (audioElem.paused) { 264 | audioElem.play(); 265 | } else { 266 | audioElem.pause(); 267 | } 268 | } 269 | 270 | function onTransitionEnd(event) { 271 | event.target.remove(); 272 | } 273 | 274 | /* https://threejs.org/docs/#manual/en/introduction/WebGL-compatibility-check */ 275 | if (WEBGL.isWebGLAvailable()) { 276 | window.addEventListener("resize", onWindowResize); 277 | document.getElementById("audioBtn").addEventListener("click", toggleSound); 278 | init(); 279 | initParticles(); 280 | } else { 281 | const warning = WEBGL.getWebGLErrorMessage(); 282 | document.getElementById("three-container").appendChild(warning); 283 | } 284 | -------------------------------------------------------------------------------- /src/js/vendor/draco/README.md: -------------------------------------------------------------------------------- 1 | # Draco 3D Data Compression 2 | 3 | Draco is an open-source library for compressing and decompressing 3D geometric meshes and point clouds. It is intended to improve the storage and transmission of 3D graphics. 4 | 5 | [Website](https://google.github.io/draco/) | [GitHub](https://github.com/google/draco) 6 | 7 | ## Contents 8 | 9 | This folder contains three utilities: 10 | 11 | * `draco_decoder.js` — Emscripten-compiled decoder, compatible with any modern browser. 12 | * `draco_decoder.wasm` — WebAssembly decoder, compatible with newer browsers and devices. 13 | * `draco_wasm_wrapper.js` — JavaScript wrapper for the WASM decoder. 14 | 15 | Each file is provided in two variations: 16 | 17 | * **Default:** Latest stable builds, tracking the project's [master branch](https://github.com/google/draco). 18 | * **glTF:** Builds targeted by the [glTF mesh compression extension](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression), tracking the [corresponding Draco branch](https://github.com/google/draco/tree/gltf_2.0_draco_extension). 19 | 20 | Either variation may be used with `THREE.DRACOLoader`: 21 | 22 | ```js 23 | var dracoLoader = new THREE.DRACOLoader(); 24 | dracoLoader.setDecoderPath('path/to/decoders/'); 25 | dracoLoader.setDecoderConfig({type: 'js'}); // (Optional) Override detection of WASM support. 26 | ``` 27 | 28 | Further [documentation on GitHub](https://github.com/google/draco/tree/master/javascript/example#static-loading-javascript-decoder). 29 | 30 | ## License 31 | 32 | [Apache License 2.0](https://github.com/google/draco/blob/master/LICENSE) 33 | -------------------------------------------------------------------------------- /src/js/vendor/draco/draco_decoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/8eb71e36b91e598c12d0bea128ab1d8a7755133e/src/js/vendor/draco/draco_decoder.wasm -------------------------------------------------------------------------------- /src/js/vendor/draco/gltf/draco_decoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/8eb71e36b91e598c12d0bea128ab1d8a7755133e/src/js/vendor/draco/gltf/draco_decoder.wasm -------------------------------------------------------------------------------- /src/js/vendor/draco/gltf/draco_wasm_wrapper.js: -------------------------------------------------------------------------------- 1 | var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(d,k,f){d!=Array.prototype&&d!=Object.prototype&&(d[k]=f.value)};$jscomp.getGlobal=function(d){return"undefined"!=typeof window&&window===d?d:"undefined"!=typeof global&&null!=global?global:d};$jscomp.global=$jscomp.getGlobal(this); 2 | $jscomp.polyfill=function(d,k,f,u){if(k){f=$jscomp.global;d=d.split(".");for(u=0;u>>16&65535)*h+k*(f>>>16&65535)<<16>>>0)|0}},"es6","es3"); 3 | $jscomp.polyfill("Math.clz32",function(d){return d?d:function(d){d=Number(d)>>>0;if(0===d)return 32;var f=0;0===(d&4294901760)&&(d<<=16,f+=16);0===(d&4278190080)&&(d<<=8,f+=8);0===(d&4026531840)&&(d<<=4,f+=4);0===(d&3221225472)&&(d<<=2,f+=2);0===(d&2147483648)&&f++;return f}},"es6","es3");$jscomp.polyfill("Math.trunc",function(d){return d?d:function(d){d=Number(d);if(isNaN(d)||Infinity===d||-Infinity===d||0===d)return d;var f=Math.floor(Math.abs(d));return 0>d?-f:f}},"es6","es3"); 4 | $jscomp.SYMBOL_PREFIX="jscomp_symbol_";$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.Symbol=function(){var d=0;return function(k){return $jscomp.SYMBOL_PREFIX+(k||"")+d++}}(); 5 | $jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var d=$jscomp.global.Symbol.iterator;d||(d=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[d]&&$jscomp.defineProperty(Array.prototype,d,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(d){var k=0;return $jscomp.iteratorPrototype(function(){return k>0];b|=e;if(0==e&&!c)break;d++;if(c&&d==c)break}c||(c=d);e="";if(128>b){for(;0e?b+=String.fromCharCode(e):(e-=65536,b+=String.fromCharCode(55296|e>>10,56320|e&1023))}}else b+=String.fromCharCode(e)}}function ha(a,c){0< 16 | a%c&&(a+=c-a%c);return a}function r(){a.HEAP8=ia=new Int8Array(D);a.HEAP16=Ja=new Int16Array(D);a.HEAP32=E=new Int32Array(D);a.HEAPU8=W=new Uint8Array(D);a.HEAPU16=new Uint16Array(D);a.HEAPU32=new Uint32Array(D);a.HEAPF32=new Float32Array(D);a.HEAPF64=new Float64Array(D)}function B(e){for(;0>2]=e;e=la.buffer;for(var d=0;d>2],c.adjusted=e,(sa(p[d]),e)|0;e=E[e>>2]; 18 | return(sa(b),e)|0}function Z(e,c){w.varargs=c;try{var b=w.get(),p=w.get(),d=w.get();e=0;Z.buffers||(Z.buffers=[null,[],[]],Z.printChar=function(c,b){var e=Z.buffers[c];f(e);0===b||10===b?((1===c?a.print:a.printErr)(h(e,0)),e.length=0):e.push(b)});for(c=0;c>2],k=E[p+(8*c+4)>>2],l=0;l=e&&(e=65536+((e&1023)<<10)|a.charCodeAt(++b)&1023);127>=e?++c:c=2047>=e?c+2:65535>=e?c+3:2097151>=e?c+4:67108863>=e?c+5:c+6}c=Array(c+1);b=0;e=c.length;if(0=f&&(f=65536+((f&1023)<<10)|a.charCodeAt(++d)&1023);if(127>=f){if(b>=e)break;c[b++]=f}else{if(2047>=f){if(b+1>=e)break;c[b++]=192|f>>6}else{if(65535>=f){if(b+2>=e)break;c[b++]=224|f>>12}else{if(2097151>=f){if(b+ 22 | 3>=e)break;c[b++]=240|f>>18}else{if(67108863>=f){if(b+4>=e)break;c[b++]=248|f>>24}else{if(b+5>=e)break;c[b++]=252|f>>30;c[b++]=128|f>>24&63}c[b++]=128|f>>18&63}c[b++]=128|f>>12&63}c[b++]=128|f>>6&63}c[b++]=128|f&63}}c[b]=0}a=l.alloc(c,ia);l.copy(c,ia,a)}return a}function z(){throw"cannot construct a Status, no constructor in IDL";}function F(){this.ptr=Wa();t(F)[this.ptr]=this}function G(){this.ptr=Xa();t(G)[this.ptr]=this}function H(){this.ptr=Ya();t(H)[this.ptr]=this}function I(){this.ptr=Za(); 23 | t(I)[this.ptr]=this}function J(){this.ptr=$a();t(J)[this.ptr]=this}function n(){this.ptr=ab();t(n)[this.ptr]=this}function P(){this.ptr=bb();t(P)[this.ptr]=this}function x(){this.ptr=cb();t(x)[this.ptr]=this}function K(){this.ptr=db();t(K)[this.ptr]=this}function q(){this.ptr=eb();t(q)[this.ptr]=this}function L(){this.ptr=fb();t(L)[this.ptr]=this}function M(){this.ptr=gb();t(M)[this.ptr]=this}function V(){this.ptr=hb();t(V)[this.ptr]=this}function Q(){this.ptr=ib();t(Q)[this.ptr]=this}function g(){this.ptr= 24 | jb();t(g)[this.ptr]=this}function C(){this.ptr=kb();t(C)[this.ptr]=this}function X(){throw"cannot construct a VoidPtr, no constructor in IDL";}function N(){this.ptr=lb();t(N)[this.ptr]=this}function R(){this.ptr=mb();t(R)[this.ptr]=this}d=d||{};var a="undefined"!==typeof d?d:{},Qa=!1,Ra=!1;a.onRuntimeInitialized=function(){Qa=!0;if(Ra&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.onModuleParsed=function(){Ra=!0;if(Qa&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.isVersionSupported= 25 | function(a){if("string"!==typeof a)return!1;a=a.split(".");return 2>a.length||3=a[1]?!0:0!=a[0]||10>2]},getStr:function(){return u(w.get())},get64:function(){var a=w.get(),c=w.get();0<=a?f(0===c):f(-1===c);return a},getZero:function(){f(0===w.get())}},va={},Ha=1;ka=function(a){f(!Sa);var c=ba;ba=ba+ 40 | a+15&-16;return c}(4);Ca=ta=k(ba);ua=Ca+Fa;Da=k(ua);E[ka>>2]=Da;Sa=!0;a.wasmTableSize=468;a.wasmMaxTableSize=468;a.asmGlobalArg={};a.asmLibraryArg={abort:O,assert:f,enlargeMemory:function(){var e=a.usingWasm?65536:16777216,c=2147483648-e;if(E[ka>>2]>c)return!1;var b=A;for(A=Math.max(A,16777216);A>2];)A=536870912>=A?ha(2*A,e):Math.min(ha((3*A+2147483648)/4,e),c);e=a.reallocBuffer(A);if(!e||e.byteLength!=A)return A=b,!1;a.buffer=D=e;r();return!0},getTotalMemory:function(){return A},abortOnCannotGrowMemory:function(){O("Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value "+ 41 | A+", (2) compile with -s ALLOW_MEMORY_GROWTH=1 which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -s ABORTING_MALLOC=0 ")},invoke_ii:function(e,c){try{return a.dynCall_ii(e,c)}catch(b){if("number"!==typeof b&&"longjmp"!==b)throw b;a.setThrew(1,0)}},invoke_iii:function(e,c,b){try{return a.dynCall_iii(e,c,b)}catch(p){if("number"!==typeof p&&"longjmp"!==p)throw p;a.setThrew(1,0)}},invoke_iiii:function(e,c,b,d){try{return a.dynCall_iiii(e, 42 | c,b,d)}catch(S){if("number"!==typeof S&&"longjmp"!==S)throw S;a.setThrew(1,0)}},invoke_iiiiiii:function(e,c,b,d,f,g,h){try{return a.dynCall_iiiiiii(e,c,b,d,f,g,h)}catch(da){if("number"!==typeof da&&"longjmp"!==da)throw da;a.setThrew(1,0)}},invoke_v:function(e){try{a.dynCall_v(e)}catch(c){if("number"!==typeof c&&"longjmp"!==c)throw c;a.setThrew(1,0)}},invoke_vi:function(e,c){try{a.dynCall_vi(e,c)}catch(b){if("number"!==typeof b&&"longjmp"!==b)throw b;a.setThrew(1,0)}},invoke_vii:function(e,c,b){try{a.dynCall_vii(e, 43 | c,b)}catch(p){if("number"!==typeof p&&"longjmp"!==p)throw p;a.setThrew(1,0)}},invoke_viii:function(e,c,b,d){try{a.dynCall_viii(e,c,b,d)}catch(S){if("number"!==typeof S&&"longjmp"!==S)throw S;a.setThrew(1,0)}},invoke_viiii:function(e,c,b,d,f){try{a.dynCall_viiii(e,c,b,d,f)}catch(xa){if("number"!==typeof xa&&"longjmp"!==xa)throw xa;a.setThrew(1,0)}},invoke_viiiii:function(e,c,b,d,f,g){try{a.dynCall_viiiii(e,c,b,d,f,g)}catch(ca){if("number"!==typeof ca&&"longjmp"!==ca)throw ca;a.setThrew(1,0)}},invoke_viiiiii:function(e, 44 | c,b,d,f,g,h){try{a.dynCall_viiiiii(e,c,b,d,f,g,h)}catch(da){if("number"!==typeof da&&"longjmp"!==da)throw da;a.setThrew(1,0)}},__ZSt18uncaught_exceptionv:v,___cxa_allocate_exception:function(a){return Ka(a)},___cxa_begin_catch:function(a){var c=y.infos[a];c&&!c.caught&&(c.caught=!0,v.uncaught_exception--);c&&(c.rethrown=!1);y.caught.push(a);y.addRef(y.deAdjust(a));return a},___cxa_find_matching_catch:la,___cxa_pure_virtual:function(){oa=!0;throw"Pure virtual function called!";},___cxa_throw:function(a, 45 | c,b){y.infos[a]={ptr:a,adjusted:a,type:c,destructor:b,refcount:0,caught:!1,rethrown:!1};y.last=a;"uncaught_exception"in v?v.uncaught_exception++:v.uncaught_exception=1;throw a+" - Exception catching is disabled, this exception cannot be caught. Compile with -s DISABLE_EXCEPTION_CATCHING=0 or DISABLE_EXCEPTION_CATCHING=2 to catch.";},___gxx_personality_v0:function(){},___resumeException:function(a){y.last||(y.last=a);throw a+" - Exception catching is disabled, this exception cannot be caught. Compile with -s DISABLE_EXCEPTION_CATCHING=0 or DISABLE_EXCEPTION_CATCHING=2 to catch."; 46 | },___setErrNo:function(d){a.___errno_location&&(E[a.___errno_location()>>2]=d);return d},___syscall140:function(a,c){w.varargs=c;try{var b=w.getStreamFromFD();w.get();var d=w.get(),e=w.get(),f=w.get();FS.llseek(b,d,f);E[e>>2]=b.position;b.getdents&&0===d&&0===f&&(b.getdents=null);return 0}catch(ca){return"undefined"!==typeof FS&&ca instanceof FS.ErrnoError||O(ca),-ca.errno}},___syscall146:Z,___syscall6:function(a,c){w.varargs=c;try{var b=w.getStreamFromFD();FS.close(b);return 0}catch(p){return"undefined"!== 47 | typeof FS&&p instanceof FS.ErrnoError||O(p),-p.errno}},_abort:function(){a.abort()},_emscripten_memcpy_big:function(a,c,b){W.set(W.subarray(c,c+b),a);return a},_llvm_trap:function(){O("trap!")},_pthread_getspecific:function(a){return va[a]||0},_pthread_key_create:function(a,c){if(0==a)return 22;E[a>>2]=Ha;va[Ha]=0;Ha++;return 0},_pthread_once:ma,_pthread_setspecific:function(a,c){if(!(a in va))return 22;va[a]=c;return 0},flush_NO_FILESYSTEM:function(){var d=a._fflush;d&&d(0);if(d=Z.printChar){var c= 48 | Z.buffers;c[1].length&&d(1,10);c[2].length&&d(2,10)}},DYNAMICTOP_PTR:ka,tempDoublePtr:qb,ABORT:oa,STACKTOP:ta,STACK_MAX:ua};var Ua=a.asm(a.asmGlobalArg,a.asmLibraryArg,D);a.asm=Ua;a.___cxa_can_catch=function(){return a.asm.___cxa_can_catch.apply(null,arguments)};a.___cxa_is_pointer_type=function(){return a.asm.___cxa_is_pointer_type.apply(null,arguments)};var $a=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=function(){return a.asm._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0.apply(null, 49 | arguments)},rb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=function(){return a.asm._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1.apply(null,arguments)},sb=a._emscripten_bind_AttributeOctahedronTransform___destroy___0=function(){return a.asm._emscripten_bind_AttributeOctahedronTransform___destroy___0.apply(null,arguments)},tb=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=function(){return a.asm._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0.apply(null, 50 | arguments)},cb=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=function(){return a.asm._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0.apply(null,arguments)},ub=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=function(){return a.asm._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1.apply(null,arguments)},vb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=function(){return a.asm._emscripten_bind_AttributeQuantizationTransform___destroy___0.apply(null, 51 | arguments)},wb=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=function(){return a.asm._emscripten_bind_AttributeQuantizationTransform_min_value_1.apply(null,arguments)},xb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=function(){return a.asm._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0.apply(null,arguments)},yb=a._emscripten_bind_AttributeQuantizationTransform_range_0=function(){return a.asm._emscripten_bind_AttributeQuantizationTransform_range_0.apply(null, 52 | arguments)},bb=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=function(){return a.asm._emscripten_bind_AttributeTransformData_AttributeTransformData_0.apply(null,arguments)},zb=a._emscripten_bind_AttributeTransformData___destroy___0=function(){return a.asm._emscripten_bind_AttributeTransformData___destroy___0.apply(null,arguments)},Ab=a._emscripten_bind_AttributeTransformData_transform_type_0=function(){return a.asm._emscripten_bind_AttributeTransformData_transform_type_0.apply(null, 53 | arguments)},ib=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=function(){return a.asm._emscripten_bind_DecoderBuffer_DecoderBuffer_0.apply(null,arguments)},Bb=a._emscripten_bind_DecoderBuffer_Init_2=function(){return a.asm._emscripten_bind_DecoderBuffer_Init_2.apply(null,arguments)},Cb=a._emscripten_bind_DecoderBuffer___destroy___0=function(){return a.asm._emscripten_bind_DecoderBuffer___destroy___0.apply(null,arguments)},Db=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=function(){return a.asm._emscripten_bind_Decoder_DecodeBufferToMesh_2.apply(null, 54 | arguments)},Eb=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=function(){return a.asm._emscripten_bind_Decoder_DecodeBufferToPointCloud_2.apply(null,arguments)},jb=a._emscripten_bind_Decoder_Decoder_0=function(){return a.asm._emscripten_bind_Decoder_Decoder_0.apply(null,arguments)},Fb=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=function(){return a.asm._emscripten_bind_Decoder_GetAttributeByUniqueId_2.apply(null,arguments)},Gb=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3= 55 | function(){return a.asm._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3.apply(null,arguments)},Hb=a._emscripten_bind_Decoder_GetAttributeFloat_3=function(){return a.asm._emscripten_bind_Decoder_GetAttributeFloat_3.apply(null,arguments)},Ib=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=function(){return a.asm._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3.apply(null,arguments)},Jb=a._emscripten_bind_Decoder_GetAttributeIdByName_2=function(){return a.asm._emscripten_bind_Decoder_GetAttributeIdByName_2.apply(null, 56 | arguments)},Kb=a._emscripten_bind_Decoder_GetAttributeId_2=function(){return a.asm._emscripten_bind_Decoder_GetAttributeId_2.apply(null,arguments)},Lb=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=function(){return a.asm._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3.apply(null,arguments)},Mb=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=function(){return a.asm._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3.apply(null,arguments)},Nb=a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3= 57 | function(){return a.asm._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3.apply(null,arguments)},Ob=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=function(){return a.asm._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3.apply(null,arguments)},Pb=a._emscripten_bind_Decoder_GetAttributeMetadata_2=function(){return a.asm._emscripten_bind_Decoder_GetAttributeMetadata_2.apply(null,arguments)},Qb=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=function(){return a.asm._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3.apply(null, 58 | arguments)},Rb=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=function(){return a.asm._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3.apply(null,arguments)},Sb=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=function(){return a.asm._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3.apply(null,arguments)},Tb=a._emscripten_bind_Decoder_GetAttribute_2=function(){return a.asm._emscripten_bind_Decoder_GetAttribute_2.apply(null,arguments)},Ub=a._emscripten_bind_Decoder_GetEncodedGeometryType_1= 59 | function(){return a.asm._emscripten_bind_Decoder_GetEncodedGeometryType_1.apply(null,arguments)},Vb=a._emscripten_bind_Decoder_GetFaceFromMesh_3=function(){return a.asm._emscripten_bind_Decoder_GetFaceFromMesh_3.apply(null,arguments)},Wb=a._emscripten_bind_Decoder_GetMetadata_1=function(){return a.asm._emscripten_bind_Decoder_GetMetadata_1.apply(null,arguments)},Xb=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=function(){return a.asm._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2.apply(null, 60 | arguments)},Yb=a._emscripten_bind_Decoder_SkipAttributeTransform_1=function(){return a.asm._emscripten_bind_Decoder_SkipAttributeTransform_1.apply(null,arguments)},Zb=a._emscripten_bind_Decoder___destroy___0=function(){return a.asm._emscripten_bind_Decoder___destroy___0.apply(null,arguments)},gb=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=function(){return a.asm._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0.apply(null,arguments)},$b=a._emscripten_bind_DracoFloat32Array_GetValue_1= 61 | function(){return a.asm._emscripten_bind_DracoFloat32Array_GetValue_1.apply(null,arguments)},ac=a._emscripten_bind_DracoFloat32Array___destroy___0=function(){return a.asm._emscripten_bind_DracoFloat32Array___destroy___0.apply(null,arguments)},bc=a._emscripten_bind_DracoFloat32Array_size_0=function(){return a.asm._emscripten_bind_DracoFloat32Array_size_0.apply(null,arguments)},fb=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=function(){return a.asm._emscripten_bind_DracoInt16Array_DracoInt16Array_0.apply(null, 62 | arguments)},cc=a._emscripten_bind_DracoInt16Array_GetValue_1=function(){return a.asm._emscripten_bind_DracoInt16Array_GetValue_1.apply(null,arguments)},dc=a._emscripten_bind_DracoInt16Array___destroy___0=function(){return a.asm._emscripten_bind_DracoInt16Array___destroy___0.apply(null,arguments)},ec=a._emscripten_bind_DracoInt16Array_size_0=function(){return a.asm._emscripten_bind_DracoInt16Array_size_0.apply(null,arguments)},lb=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=function(){return a.asm._emscripten_bind_DracoInt32Array_DracoInt32Array_0.apply(null, 63 | arguments)},fc=a._emscripten_bind_DracoInt32Array_GetValue_1=function(){return a.asm._emscripten_bind_DracoInt32Array_GetValue_1.apply(null,arguments)},gc=a._emscripten_bind_DracoInt32Array___destroy___0=function(){return a.asm._emscripten_bind_DracoInt32Array___destroy___0.apply(null,arguments)},hc=a._emscripten_bind_DracoInt32Array_size_0=function(){return a.asm._emscripten_bind_DracoInt32Array_size_0.apply(null,arguments)},db=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=function(){return a.asm._emscripten_bind_DracoInt8Array_DracoInt8Array_0.apply(null, 64 | arguments)},ic=a._emscripten_bind_DracoInt8Array_GetValue_1=function(){return a.asm._emscripten_bind_DracoInt8Array_GetValue_1.apply(null,arguments)},jc=a._emscripten_bind_DracoInt8Array___destroy___0=function(){return a.asm._emscripten_bind_DracoInt8Array___destroy___0.apply(null,arguments)},kc=a._emscripten_bind_DracoInt8Array_size_0=function(){return a.asm._emscripten_bind_DracoInt8Array_size_0.apply(null,arguments)},Wa=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=function(){return a.asm._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0.apply(null, 65 | arguments)},lc=a._emscripten_bind_DracoUInt16Array_GetValue_1=function(){return a.asm._emscripten_bind_DracoUInt16Array_GetValue_1.apply(null,arguments)},mc=a._emscripten_bind_DracoUInt16Array___destroy___0=function(){return a.asm._emscripten_bind_DracoUInt16Array___destroy___0.apply(null,arguments)},nc=a._emscripten_bind_DracoUInt16Array_size_0=function(){return a.asm._emscripten_bind_DracoUInt16Array_size_0.apply(null,arguments)},Za=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=function(){return a.asm._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0.apply(null, 66 | arguments)},oc=a._emscripten_bind_DracoUInt32Array_GetValue_1=function(){return a.asm._emscripten_bind_DracoUInt32Array_GetValue_1.apply(null,arguments)},pc=a._emscripten_bind_DracoUInt32Array___destroy___0=function(){return a.asm._emscripten_bind_DracoUInt32Array___destroy___0.apply(null,arguments)},qc=a._emscripten_bind_DracoUInt32Array_size_0=function(){return a.asm._emscripten_bind_DracoUInt32Array_size_0.apply(null,arguments)},Ya=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=function(){return a.asm._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0.apply(null, 67 | arguments)},rc=a._emscripten_bind_DracoUInt8Array_GetValue_1=function(){return a.asm._emscripten_bind_DracoUInt8Array_GetValue_1.apply(null,arguments)},sc=a._emscripten_bind_DracoUInt8Array___destroy___0=function(){return a.asm._emscripten_bind_DracoUInt8Array___destroy___0.apply(null,arguments)},tc=a._emscripten_bind_DracoUInt8Array_size_0=function(){return a.asm._emscripten_bind_DracoUInt8Array_size_0.apply(null,arguments)},hb=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=function(){return a.asm._emscripten_bind_GeometryAttribute_GeometryAttribute_0.apply(null, 68 | arguments)},uc=a._emscripten_bind_GeometryAttribute___destroy___0=function(){return a.asm._emscripten_bind_GeometryAttribute___destroy___0.apply(null,arguments)},kb=a._emscripten_bind_Mesh_Mesh_0=function(){return a.asm._emscripten_bind_Mesh_Mesh_0.apply(null,arguments)},vc=a._emscripten_bind_Mesh___destroy___0=function(){return a.asm._emscripten_bind_Mesh___destroy___0.apply(null,arguments)},wc=a._emscripten_bind_Mesh_num_attributes_0=function(){return a.asm._emscripten_bind_Mesh_num_attributes_0.apply(null, 69 | arguments)},xc=a._emscripten_bind_Mesh_num_faces_0=function(){return a.asm._emscripten_bind_Mesh_num_faces_0.apply(null,arguments)},yc=a._emscripten_bind_Mesh_num_points_0=function(){return a.asm._emscripten_bind_Mesh_num_points_0.apply(null,arguments)},zc=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=function(){return a.asm._emscripten_bind_MetadataQuerier_GetDoubleEntry_2.apply(null,arguments)},Ac=a._emscripten_bind_MetadataQuerier_GetEntryName_2=function(){return a.asm._emscripten_bind_MetadataQuerier_GetEntryName_2.apply(null, 70 | arguments)},Bc=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=function(){return a.asm._emscripten_bind_MetadataQuerier_GetIntEntry_2.apply(null,arguments)},Cc=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=function(){return a.asm._emscripten_bind_MetadataQuerier_GetStringEntry_2.apply(null,arguments)},Dc=a._emscripten_bind_MetadataQuerier_HasDoubleEntry_2=function(){return a.asm._emscripten_bind_MetadataQuerier_HasDoubleEntry_2.apply(null,arguments)},Ec=a._emscripten_bind_MetadataQuerier_HasEntry_2= 71 | function(){return a.asm._emscripten_bind_MetadataQuerier_HasEntry_2.apply(null,arguments)},Fc=a._emscripten_bind_MetadataQuerier_HasIntEntry_2=function(){return a.asm._emscripten_bind_MetadataQuerier_HasIntEntry_2.apply(null,arguments)},Gc=a._emscripten_bind_MetadataQuerier_HasStringEntry_2=function(){return a.asm._emscripten_bind_MetadataQuerier_HasStringEntry_2.apply(null,arguments)},eb=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=function(){return a.asm._emscripten_bind_MetadataQuerier_MetadataQuerier_0.apply(null, 72 | arguments)},Hc=a._emscripten_bind_MetadataQuerier_NumEntries_1=function(){return a.asm._emscripten_bind_MetadataQuerier_NumEntries_1.apply(null,arguments)},Ic=a._emscripten_bind_MetadataQuerier___destroy___0=function(){return a.asm._emscripten_bind_MetadataQuerier___destroy___0.apply(null,arguments)},mb=a._emscripten_bind_Metadata_Metadata_0=function(){return a.asm._emscripten_bind_Metadata_Metadata_0.apply(null,arguments)},Jc=a._emscripten_bind_Metadata___destroy___0=function(){return a.asm._emscripten_bind_Metadata___destroy___0.apply(null, 73 | arguments)},Kc=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=function(){return a.asm._emscripten_bind_PointAttribute_GetAttributeTransformData_0.apply(null,arguments)},ab=a._emscripten_bind_PointAttribute_PointAttribute_0=function(){return a.asm._emscripten_bind_PointAttribute_PointAttribute_0.apply(null,arguments)},Lc=a._emscripten_bind_PointAttribute___destroy___0=function(){return a.asm._emscripten_bind_PointAttribute___destroy___0.apply(null,arguments)},Mc=a._emscripten_bind_PointAttribute_attribute_type_0= 74 | function(){return a.asm._emscripten_bind_PointAttribute_attribute_type_0.apply(null,arguments)},Nc=a._emscripten_bind_PointAttribute_byte_offset_0=function(){return a.asm._emscripten_bind_PointAttribute_byte_offset_0.apply(null,arguments)},Oc=a._emscripten_bind_PointAttribute_byte_stride_0=function(){return a.asm._emscripten_bind_PointAttribute_byte_stride_0.apply(null,arguments)},Pc=a._emscripten_bind_PointAttribute_data_type_0=function(){return a.asm._emscripten_bind_PointAttribute_data_type_0.apply(null, 75 | arguments)},Qc=a._emscripten_bind_PointAttribute_normalized_0=function(){return a.asm._emscripten_bind_PointAttribute_normalized_0.apply(null,arguments)},Rc=a._emscripten_bind_PointAttribute_num_components_0=function(){return a.asm._emscripten_bind_PointAttribute_num_components_0.apply(null,arguments)},Sc=a._emscripten_bind_PointAttribute_size_0=function(){return a.asm._emscripten_bind_PointAttribute_size_0.apply(null,arguments)},Tc=a._emscripten_bind_PointAttribute_unique_id_0=function(){return a.asm._emscripten_bind_PointAttribute_unique_id_0.apply(null, 76 | arguments)},Xa=a._emscripten_bind_PointCloud_PointCloud_0=function(){return a.asm._emscripten_bind_PointCloud_PointCloud_0.apply(null,arguments)},Uc=a._emscripten_bind_PointCloud___destroy___0=function(){return a.asm._emscripten_bind_PointCloud___destroy___0.apply(null,arguments)},Vc=a._emscripten_bind_PointCloud_num_attributes_0=function(){return a.asm._emscripten_bind_PointCloud_num_attributes_0.apply(null,arguments)},Wc=a._emscripten_bind_PointCloud_num_points_0=function(){return a.asm._emscripten_bind_PointCloud_num_points_0.apply(null, 77 | arguments)},Xc=a._emscripten_bind_Status___destroy___0=function(){return a.asm._emscripten_bind_Status___destroy___0.apply(null,arguments)},Yc=a._emscripten_bind_Status_code_0=function(){return a.asm._emscripten_bind_Status_code_0.apply(null,arguments)},Zc=a._emscripten_bind_Status_error_msg_0=function(){return a.asm._emscripten_bind_Status_error_msg_0.apply(null,arguments)},$c=a._emscripten_bind_Status_ok_0=function(){return a.asm._emscripten_bind_Status_ok_0.apply(null,arguments)},ad=a._emscripten_bind_VoidPtr___destroy___0= 78 | function(){return a.asm._emscripten_bind_VoidPtr___destroy___0.apply(null,arguments)},bd=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=function(){return a.asm._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM.apply(null,arguments)},cd=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=function(){return a.asm._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM.apply(null,arguments)},dd=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM= 79 | function(){return a.asm._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM.apply(null,arguments)},ed=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=function(){return a.asm._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM.apply(null,arguments)},fd=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=function(){return a.asm._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE.apply(null, 80 | arguments)},gd=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=function(){return a.asm._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD.apply(null,arguments)},hd=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=function(){return a.asm._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH.apply(null,arguments)},id=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=function(){return a.asm._emscripten_enum_draco_GeometryAttribute_Type_COLOR.apply(null,arguments)},jd= 81 | a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=function(){return a.asm._emscripten_enum_draco_GeometryAttribute_Type_GENERIC.apply(null,arguments)},kd=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=function(){return a.asm._emscripten_enum_draco_GeometryAttribute_Type_INVALID.apply(null,arguments)},ld=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=function(){return a.asm._emscripten_enum_draco_GeometryAttribute_Type_NORMAL.apply(null,arguments)},md=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION= 82 | function(){return a.asm._emscripten_enum_draco_GeometryAttribute_Type_POSITION.apply(null,arguments)},nd=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=function(){return a.asm._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD.apply(null,arguments)},od=a._emscripten_enum_draco_StatusCode_ERROR=function(){return a.asm._emscripten_enum_draco_StatusCode_ERROR.apply(null,arguments)},pd=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=function(){return a.asm._emscripten_enum_draco_StatusCode_INVALID_PARAMETER.apply(null, 83 | arguments)},qd=a._emscripten_enum_draco_StatusCode_IO_ERROR=function(){return a.asm._emscripten_enum_draco_StatusCode_IO_ERROR.apply(null,arguments)},rd=a._emscripten_enum_draco_StatusCode_OK=function(){return a.asm._emscripten_enum_draco_StatusCode_OK.apply(null,arguments)},sd=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=function(){return a.asm._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION.apply(null,arguments)},td=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=function(){return a.asm._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION.apply(null, 84 | arguments)},nb=a._emscripten_replace_memory=function(){return a.asm._emscripten_replace_memory.apply(null,arguments)};a._free=function(){return a.asm._free.apply(null,arguments)};a._llvm_bswap_i32=function(){return a.asm._llvm_bswap_i32.apply(null,arguments)};var Ka=a._malloc=function(){return a.asm._malloc.apply(null,arguments)};a._memcpy=function(){return a.asm._memcpy.apply(null,arguments)};a._memmove=function(){return a.asm._memmove.apply(null,arguments)};a._memset=function(){return a.asm._memset.apply(null, 85 | arguments)};a._sbrk=function(){return a.asm._sbrk.apply(null,arguments)};a.establishStackSpace=function(){return a.asm.establishStackSpace.apply(null,arguments)};a.getTempRet0=function(){return a.asm.getTempRet0.apply(null,arguments)};a.runPostSets=function(){return a.asm.runPostSets.apply(null,arguments)};var sa=a.setTempRet0=function(){return a.asm.setTempRet0.apply(null,arguments)};a.setThrew=function(){return a.asm.setThrew.apply(null,arguments)};a.stackAlloc=function(){return a.asm.stackAlloc.apply(null, 86 | arguments)};a.stackRestore=function(){return a.asm.stackRestore.apply(null,arguments)};a.stackSave=function(){return a.asm.stackSave.apply(null,arguments)};a.dynCall_ii=function(){return a.asm.dynCall_ii.apply(null,arguments)};a.dynCall_iii=function(){return a.asm.dynCall_iii.apply(null,arguments)};a.dynCall_iiii=function(){return a.asm.dynCall_iiii.apply(null,arguments)};a.dynCall_iiiiiii=function(){return a.asm.dynCall_iiiiiii.apply(null,arguments)};a.dynCall_v=function(){return a.asm.dynCall_v.apply(null, 87 | arguments)};a.dynCall_vi=function(){return a.asm.dynCall_vi.apply(null,arguments)};a.dynCall_vii=function(){return a.asm.dynCall_vii.apply(null,arguments)};a.dynCall_viii=function(){return a.asm.dynCall_viii.apply(null,arguments)};a.dynCall_viiii=function(){return a.asm.dynCall_viiii.apply(null,arguments)};a.dynCall_viiiii=function(){return a.asm.dynCall_viiiii.apply(null,arguments)};a.dynCall_viiiiii=function(){return a.asm.dynCall_viiiiii.apply(null,arguments)};a.asm=Ua;a.then=function(d){if(a.calledRun)d(a); 88 | else{var c=a.onRuntimeInitialized;a.onRuntimeInitialized=function(){c&&c();d(a)}}return a};na.prototype=Error();na.prototype.constructor=na;ra=function c(){a.calledRun||wa();a.calledRun||(ra=c)};a.run=wa;a.exit=function(c,b){if(!b||!a.noExitRuntime||0!==c){if(!a.noExitRuntime&&(oa=!0,ta=void 0,B(ob),a.onExit))a.onExit(c);qa&&process.exit(c);a.quit(c,new na(c))}};a.abort=O;if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0=l.size?(f(0>= 91 | 1;break;case 4:d>>=2;break;case 8:d>>=3}for(var c=0;c 2 | 3 | 4 | 5 | 9 | 10 | 11 | Threejs Webpack Boilerplate 12 | 13 | 14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/static/images/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/8eb71e36b91e598c12d0bea128ab1d8a7755133e/src/static/images/favicons/favicon.ico -------------------------------------------------------------------------------- /src/static/images/flame-texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/8eb71e36b91e598c12d0bea128ab1d8a7755133e/src/static/images/flame-texture.png -------------------------------------------------------------------------------- /src/static/images/grass-texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/8eb71e36b91e598c12d0bea128ab1d8a7755133e/src/static/images/grass-texture.jpg -------------------------------------------------------------------------------- /src/static/images/lightning-texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/8eb71e36b91e598c12d0bea128ab1d8a7755133e/src/static/images/lightning-texture.png -------------------------------------------------------------------------------- /src/static/images/shine-texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/8eb71e36b91e598c12d0bea128ab1d8a7755133e/src/static/images/shine-texture.png -------------------------------------------------------------------------------- /src/static/images/smoke-texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/8eb71e36b91e598c12d0bea128ab1d8a7755133e/src/static/images/smoke-texture.png -------------------------------------------------------------------------------- /src/static/models/dragon/scene.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/8eb71e36b91e598c12d0bea128ab1d8a7755133e/src/static/models/dragon/scene.bin -------------------------------------------------------------------------------- /src/static/models/dragon/scene.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors": [ 3 | { 4 | "bufferView": 1, 5 | "componentType": 5126, 6 | "count": 65532, 7 | "max": [ 8 | 53.115997314453125, 9 | 40.350379943847656, 10 | 24.130050659179688 11 | ], 12 | "min": [ 13 | 2.5279979705810547, 14 | 0.031709998846054077, 15 | 0 16 | ], 17 | "type": "VEC3" 18 | }, 19 | { 20 | "bufferView": 1, 21 | "byteOffset": 786384, 22 | "componentType": 5126, 23 | "count": 65532, 24 | "max": [ 25 | 0.99678313732147217, 26 | 1, 27 | 1 28 | ], 29 | "min": [ 30 | -0.99872809648513794, 31 | -1, 32 | -0.999500572681427 33 | ], 34 | "type": "VEC3" 35 | }, 36 | { 37 | "bufferView": 2, 38 | "componentType": 5126, 39 | "count": 65532, 40 | "max": [ 41 | 1, 42 | 1, 43 | 1, 44 | 1 45 | ], 46 | "min": [ 47 | 1, 48 | 1, 49 | 1, 50 | 1 51 | ], 52 | "type": "VEC4" 53 | }, 54 | { 55 | "bufferView": 0, 56 | "componentType": 5125, 57 | "count": 65532, 58 | "max": [ 59 | 65531 60 | ], 61 | "min": [ 62 | 0 63 | ], 64 | "type": "SCALAR" 65 | }, 66 | { 67 | "bufferView": 1, 68 | "byteOffset": 1572768, 69 | "componentType": 5126, 70 | "count": 65532, 71 | "max": [ 72 | 51.494869232177734, 73 | 43.279277801513672, 74 | 22.318050384521484 75 | ], 76 | "min": [ 77 | 5.3205580711364746, 78 | 0.03371100127696991, 79 | 4.7677140235900879 80 | ], 81 | "type": "VEC3" 82 | }, 83 | { 84 | "bufferView": 1, 85 | "byteOffset": 2359152, 86 | "componentType": 5126, 87 | "count": 65532, 88 | "max": [ 89 | 0.99833756685256958, 90 | 0.9999849796295166, 91 | 0.99989831447601318 92 | ], 93 | "min": [ 94 | -0.99846082925796509, 95 | -0.99985635280609131, 96 | -0.99428945779800415 97 | ], 98 | "type": "VEC3" 99 | }, 100 | { 101 | "bufferView": 2, 102 | "byteOffset": 1048512, 103 | "componentType": 5126, 104 | "count": 65532, 105 | "max": [ 106 | 1, 107 | 1, 108 | 1, 109 | 1 110 | ], 111 | "min": [ 112 | 1, 113 | 1, 114 | 1, 115 | 1 116 | ], 117 | "type": "VEC4" 118 | }, 119 | { 120 | "bufferView": 0, 121 | "byteOffset": 262128, 122 | "componentType": 5125, 123 | "count": 65532, 124 | "max": [ 125 | 65531 126 | ], 127 | "min": [ 128 | 0 129 | ], 130 | "type": "SCALAR" 131 | }, 132 | { 133 | "bufferView": 1, 134 | "byteOffset": 3145536, 135 | "componentType": 5126, 136 | "count": 65534, 137 | "max": [ 138 | 52.685314178466797, 139 | 44.031116485595703, 140 | 20.824050903320312 141 | ], 142 | "min": [ 143 | 5.8613929748535156, 144 | 0.075282998383045197, 145 | 6.759253978729248 146 | ], 147 | "type": "VEC3" 148 | }, 149 | { 150 | "bufferView": 1, 151 | "byteOffset": 3931944, 152 | "componentType": 5126, 153 | "count": 65534, 154 | "max": [ 155 | 0.99548631906509399, 156 | 0.99973899126052856, 157 | 1 158 | ], 159 | "min": [ 160 | -0.99990057945251465, 161 | -0.99996006488800049, 162 | -0.99524128437042236 163 | ], 164 | "type": "VEC3" 165 | }, 166 | { 167 | "bufferView": 2, 168 | "byteOffset": 2097024, 169 | "componentType": 5126, 170 | "count": 65534, 171 | "max": [ 172 | 1, 173 | 1, 174 | 1, 175 | 1 176 | ], 177 | "min": [ 178 | 1, 179 | 1, 180 | 1, 181 | 1 182 | ], 183 | "type": "VEC4" 184 | }, 185 | { 186 | "bufferView": 0, 187 | "byteOffset": 524256, 188 | "componentType": 5125, 189 | "count": 65538, 190 | "max": [ 191 | 65533 192 | ], 193 | "min": [ 194 | 0 195 | ], 196 | "type": "SCALAR" 197 | }, 198 | { 199 | "bufferView": 1, 200 | "byteOffset": 4718352, 201 | "componentType": 5126, 202 | "count": 65532, 203 | "max": [ 204 | 60.035236358642578, 205 | 43.956935882568359, 206 | 15.79355525970459 207 | ], 208 | "min": [ 209 | 5.8348388671875, 210 | 0.0033360000234097242, 211 | 8.5560522079467773 212 | ], 213 | "type": "VEC3" 214 | }, 215 | { 216 | "bufferView": 1, 217 | "byteOffset": 5504736, 218 | "componentType": 5126, 219 | "count": 65532, 220 | "max": [ 221 | 0.99997705221176147, 222 | 0.99998599290847778, 223 | 0.99980276823043823 224 | ], 225 | "min": [ 226 | -0.99991148710250854, 227 | -0.99980729818344116, 228 | -0.9991908073425293 229 | ], 230 | "type": "VEC3" 231 | }, 232 | { 233 | "bufferView": 2, 234 | "byteOffset": 3145568, 235 | "componentType": 5126, 236 | "count": 65532, 237 | "max": [ 238 | 1, 239 | 1, 240 | 1, 241 | 1 242 | ], 243 | "min": [ 244 | 1, 245 | 1, 246 | 1, 247 | 1 248 | ], 249 | "type": "VEC4" 250 | }, 251 | { 252 | "bufferView": 0, 253 | "byteOffset": 786408, 254 | "componentType": 5125, 255 | "count": 65532, 256 | "max": [ 257 | 65531 258 | ], 259 | "min": [ 260 | 0 261 | ], 262 | "type": "SCALAR" 263 | }, 264 | { 265 | "bufferView": 1, 266 | "byteOffset": 6291120, 267 | "componentType": 5126, 268 | "count": 65532, 269 | "max": [ 270 | 62.425941467285156, 271 | 40.603736877441406, 272 | 16.984050750732422 273 | ], 274 | "min": [ 275 | 3.6211569309234619, 276 | 0, 277 | 10.262050628662109 278 | ], 279 | "type": "VEC3" 280 | }, 281 | { 282 | "bufferView": 1, 283 | "byteOffset": 7077504, 284 | "componentType": 5126, 285 | "count": 65532, 286 | "max": [ 287 | 0.99972635507583618, 288 | 0.99999481439590454, 289 | 0.99993818998336792 290 | ], 291 | "min": [ 292 | -1, 293 | -0.9998161792755127, 294 | -0.99998140335083008 295 | ], 296 | "type": "VEC3" 297 | }, 298 | { 299 | "bufferView": 2, 300 | "byteOffset": 4194080, 301 | "componentType": 5126, 302 | "count": 65532, 303 | "max": [ 304 | 1, 305 | 1, 306 | 1, 307 | 1 308 | ], 309 | "min": [ 310 | 1, 311 | 1, 312 | 1, 313 | 1 314 | ], 315 | "type": "VEC4" 316 | }, 317 | { 318 | "bufferView": 0, 319 | "byteOffset": 1048536, 320 | "componentType": 5125, 321 | "count": 65532, 322 | "max": [ 323 | 65531 324 | ], 325 | "min": [ 326 | 0 327 | ], 328 | "type": "SCALAR" 329 | }, 330 | { 331 | "bufferView": 1, 332 | "byteOffset": 7863888, 333 | "componentType": 5126, 334 | "count": 65533, 335 | "max": [ 336 | 62.441135406494141, 337 | 40.606735229492188, 338 | 19.119312286376953 339 | ], 340 | "min": [ 341 | 2.1861670017242432, 342 | 0.013709999620914459, 343 | 11.95405101776123 344 | ], 345 | "type": "VEC3" 346 | }, 347 | { 348 | "bufferView": 1, 349 | "byteOffset": 8650284, 350 | "componentType": 5126, 351 | "count": 65533, 352 | "max": [ 353 | 0.99949878454208374, 354 | 0.99999856948852539, 355 | 0.99996376037597656 356 | ], 357 | "min": [ 358 | -0.99938040971755981, 359 | -0.99998617172241211, 360 | -0.99992895126342773 361 | ], 362 | "type": "VEC3" 363 | }, 364 | { 365 | "bufferView": 2, 366 | "byteOffset": 5242592, 367 | "componentType": 5126, 368 | "count": 65533, 369 | "max": [ 370 | 1, 371 | 1, 372 | 1, 373 | 1 374 | ], 375 | "min": [ 376 | 1, 377 | 1, 378 | 1, 379 | 1 380 | ], 381 | "type": "VEC4" 382 | }, 383 | { 384 | "bufferView": 0, 385 | "byteOffset": 1310664, 386 | "componentType": 5125, 387 | "count": 65535, 388 | "max": [ 389 | 65532 390 | ], 391 | "min": [ 392 | 0 393 | ], 394 | "type": "SCALAR" 395 | }, 396 | { 397 | "bufferView": 1, 398 | "byteOffset": 9436680, 399 | "componentType": 5126, 400 | "count": 65532, 401 | "max": [ 402 | 61.683177947998047, 403 | 40.606735229492188, 404 | 17.094051361083984 405 | ], 406 | "min": [ 407 | 1.1501649618148804, 408 | 0.019709000363945961, 409 | 13.432051658630371 410 | ], 411 | "type": "VEC3" 412 | }, 413 | { 414 | "bufferView": 1, 415 | "byteOffset": 10223064, 416 | "componentType": 5126, 417 | "count": 65532, 418 | "max": [ 419 | 0.99974393844604492, 420 | 0.99999403953552246, 421 | 0.99952065944671631 422 | ], 423 | "min": [ 424 | -0.99907106161117554, 425 | -0.9999995231628418, 426 | -0.99999892711639404 427 | ], 428 | "type": "VEC3" 429 | }, 430 | { 431 | "bufferView": 2, 432 | "byteOffset": 6291120, 433 | "componentType": 5126, 434 | "count": 65532, 435 | "max": [ 436 | 1, 437 | 1, 438 | 1, 439 | 1 440 | ], 441 | "min": [ 442 | 1, 443 | 1, 444 | 1, 445 | 1 446 | ], 447 | "type": "VEC4" 448 | }, 449 | { 450 | "bufferView": 0, 451 | "byteOffset": 1572804, 452 | "componentType": 5125, 453 | "count": 65532, 454 | "max": [ 455 | 65531 456 | ], 457 | "min": [ 458 | 0 459 | ], 460 | "type": "SCALAR" 461 | }, 462 | { 463 | "bufferView": 1, 464 | "byteOffset": 11009448, 465 | "componentType": 5126, 466 | "count": 65532, 467 | "max": [ 468 | 58.662998199462891, 469 | 39.907482147216797, 470 | 22.638050079345703 471 | ], 472 | "min": [ 473 | 0.98497897386550903, 474 | 0.027186000719666481, 475 | 14.686054229736328 476 | ], 477 | "type": "VEC3" 478 | }, 479 | { 480 | "bufferView": 1, 481 | "byteOffset": 11795832, 482 | "componentType": 5126, 483 | "count": 65532, 484 | "max": [ 485 | 0.99997204542160034, 486 | 0.99999642372131348, 487 | 0.99995827674865723 488 | ], 489 | "min": [ 490 | -0.9997667670249939, 491 | -0.99992191791534424, 492 | -0.99992918968200684 493 | ], 494 | "type": "VEC3" 495 | }, 496 | { 497 | "bufferView": 2, 498 | "byteOffset": 7339632, 499 | "componentType": 5126, 500 | "count": 65532, 501 | "max": [ 502 | 1, 503 | 1, 504 | 1, 505 | 1 506 | ], 507 | "min": [ 508 | 1, 509 | 1, 510 | 1, 511 | 1 512 | ], 513 | "type": "VEC4" 514 | }, 515 | { 516 | "bufferView": 0, 517 | "byteOffset": 1834932, 518 | "componentType": 5125, 519 | "count": 65538, 520 | "max": [ 521 | 65531 522 | ], 523 | "min": [ 524 | 0 525 | ], 526 | "type": "SCALAR" 527 | }, 528 | { 529 | "bufferView": 1, 530 | "byteOffset": 12582216, 531 | "componentType": 5126, 532 | "count": 65534, 533 | "max": [ 534 | 58.663089752197266, 535 | 38.970813751220703, 536 | 19.970050811767578 537 | ], 538 | "min": [ 539 | 0, 540 | 0.03570999950170517, 541 | 16.440052032470703 542 | ], 543 | "type": "VEC3" 544 | }, 545 | { 546 | "bufferView": 1, 547 | "byteOffset": 13368624, 548 | "componentType": 5126, 549 | "count": 65534, 550 | "max": [ 551 | 0.99992924928665161, 552 | 0.99996578693389893, 553 | 0.99987584352493286 554 | ], 555 | "min": [ 556 | -0.99985402822494507, 557 | -0.99986374378204346, 558 | -0.99988573789596558 559 | ], 560 | "type": "VEC3" 561 | }, 562 | { 563 | "bufferView": 2, 564 | "byteOffset": 8388144, 565 | "componentType": 5126, 566 | "count": 65534, 567 | "max": [ 568 | 1, 569 | 1, 570 | 1, 571 | 1 572 | ], 573 | "min": [ 574 | 1, 575 | 1, 576 | 1, 577 | 1 578 | ], 579 | "type": "VEC4" 580 | }, 581 | { 582 | "bufferView": 0, 583 | "byteOffset": 2097084, 584 | "componentType": 5125, 585 | "count": 65538, 586 | "max": [ 587 | 65533 588 | ], 589 | "min": [ 590 | 0 591 | ], 592 | "type": "SCALAR" 593 | }, 594 | { 595 | "bufferView": 1, 596 | "byteOffset": 14155032, 597 | "componentType": 5126, 598 | "count": 65532, 599 | "max": [ 600 | 58.414138793945312, 601 | 38.530982971191406, 602 | 22.638050079345703 603 | ], 604 | "min": [ 605 | 0, 606 | 0.07371000200510025, 607 | 18.7049560546875 608 | ], 609 | "type": "VEC3" 610 | }, 611 | { 612 | "bufferView": 1, 613 | "byteOffset": 14941416, 614 | "componentType": 5126, 615 | "count": 65532, 616 | "max": [ 617 | 0.99953740835189819, 618 | 0.99998420476913452, 619 | 0.99952828884124756 620 | ], 621 | "min": [ 622 | -0.99994772672653198, 623 | -0.99996048212051392, 624 | -1 625 | ], 626 | "type": "VEC3" 627 | }, 628 | { 629 | "bufferView": 2, 630 | "byteOffset": 9436688, 631 | "componentType": 5126, 632 | "count": 65532, 633 | "max": [ 634 | 1, 635 | 1, 636 | 1, 637 | 1 638 | ], 639 | "min": [ 640 | 1, 641 | 1, 642 | 1, 643 | 1 644 | ], 645 | "type": "VEC4" 646 | }, 647 | { 648 | "bufferView": 0, 649 | "byteOffset": 2359236, 650 | "componentType": 5125, 651 | "count": 65532, 652 | "max": [ 653 | 65531 654 | ], 655 | "min": [ 656 | 0 657 | ], 658 | "type": "SCALAR" 659 | }, 660 | { 661 | "bufferView": 1, 662 | "byteOffset": 15727800, 663 | "componentType": 5126, 664 | "count": 41571, 665 | "max": [ 666 | 56.782386779785156, 667 | 28.917617797851562, 668 | 27.923421859741211 669 | ], 670 | "min": [ 671 | 3.6035780906677246, 672 | 0.14170999825000763, 673 | 21.42976188659668 674 | ], 675 | "type": "VEC3" 676 | }, 677 | { 678 | "bufferView": 1, 679 | "byteOffset": 16226652, 680 | "componentType": 5126, 681 | "count": 41571, 682 | "max": [ 683 | 0.99997752904891968, 684 | 0.99992978572845459, 685 | 0.99919068813323975 686 | ], 687 | "min": [ 688 | -0.99980759620666504, 689 | -0.99980056285858154, 690 | -0.99994373321533203 691 | ], 692 | "type": "VEC3" 693 | }, 694 | { 695 | "bufferView": 2, 696 | "byteOffset": 10485200, 697 | "componentType": 5126, 698 | "count": 41571, 699 | "max": [ 700 | 1, 701 | 1, 702 | 1, 703 | 1 704 | ], 705 | "min": [ 706 | 1, 707 | 1, 708 | 1, 709 | 1 710 | ], 711 | "type": "VEC4" 712 | }, 713 | { 714 | "bufferView": 0, 715 | "byteOffset": 2621364, 716 | "componentType": 5125, 717 | "count": 41571, 718 | "max": [ 719 | 41570 720 | ], 721 | "min": [ 722 | 0 723 | ], 724 | "type": "SCALAR" 725 | } 726 | ], 727 | "asset": { 728 | "extras": { 729 | "author": "uptown (https://sketchfab.com/uptown)", 730 | "license": "CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)", 731 | "source": "https://sketchfab.com/models/f4b218fb3b0d49e9b3a27367850517b8", 732 | "title": "Dragon" 733 | }, 734 | "generator": "Sketchfab-3.18.2", 735 | "version": "2.0" 736 | }, 737 | "bufferViews": [ 738 | { 739 | "buffer": 0, 740 | "byteLength": 2787648, 741 | "byteOffset": 0, 742 | "name": "floatBufferViews", 743 | "target": 34963 744 | }, 745 | { 746 | "buffer": 0, 747 | "byteLength": 16725504, 748 | "byteOffset": 2787648, 749 | "byteStride": 12, 750 | "name": "floatBufferViews", 751 | "target": 34962 752 | }, 753 | { 754 | "buffer": 0, 755 | "byteLength": 11150336, 756 | "byteOffset": 19513152, 757 | "byteStride": 16, 758 | "name": "floatBufferViews", 759 | "target": 34962 760 | } 761 | ], 762 | "buffers": [ 763 | { 764 | "byteLength": 30663488, 765 | "uri": "scene.bin" 766 | } 767 | ], 768 | "materials": [ 769 | { 770 | "doubleSided": true, 771 | "emissiveFactor": [ 772 | 0, 773 | 0, 774 | 0 775 | ], 776 | "name": "Scene_-_Root", 777 | "pbrMetallicRoughness": { 778 | "baseColorFactor": [ 779 | 0.5, 780 | 0.5, 781 | 0.5, 782 | 1 783 | ], 784 | "metallicFactor": 0, 785 | "roughnessFactor": 0.59999999999999998 786 | } 787 | } 788 | ], 789 | "meshes": [ 790 | { 791 | "primitives": [ 792 | { 793 | "attributes": { 794 | "COLOR_0": 2, 795 | "NORMAL": 1, 796 | "POSITION": 0 797 | }, 798 | "indices": 3, 799 | "material": 0, 800 | "mode": 4 801 | } 802 | ] 803 | }, 804 | { 805 | "primitives": [ 806 | { 807 | "attributes": { 808 | "COLOR_0": 6, 809 | "NORMAL": 5, 810 | "POSITION": 4 811 | }, 812 | "indices": 7, 813 | "material": 0, 814 | "mode": 4 815 | } 816 | ] 817 | }, 818 | { 819 | "primitives": [ 820 | { 821 | "attributes": { 822 | "COLOR_0": 10, 823 | "NORMAL": 9, 824 | "POSITION": 8 825 | }, 826 | "indices": 11, 827 | "material": 0, 828 | "mode": 4 829 | } 830 | ] 831 | }, 832 | { 833 | "primitives": [ 834 | { 835 | "attributes": { 836 | "COLOR_0": 14, 837 | "NORMAL": 13, 838 | "POSITION": 12 839 | }, 840 | "indices": 15, 841 | "material": 0, 842 | "mode": 4 843 | } 844 | ] 845 | }, 846 | { 847 | "primitives": [ 848 | { 849 | "attributes": { 850 | "COLOR_0": 18, 851 | "NORMAL": 17, 852 | "POSITION": 16 853 | }, 854 | "indices": 19, 855 | "material": 0, 856 | "mode": 4 857 | } 858 | ] 859 | }, 860 | { 861 | "primitives": [ 862 | { 863 | "attributes": { 864 | "COLOR_0": 22, 865 | "NORMAL": 21, 866 | "POSITION": 20 867 | }, 868 | "indices": 23, 869 | "material": 0, 870 | "mode": 4 871 | } 872 | ] 873 | }, 874 | { 875 | "primitives": [ 876 | { 877 | "attributes": { 878 | "COLOR_0": 26, 879 | "NORMAL": 25, 880 | "POSITION": 24 881 | }, 882 | "indices": 27, 883 | "material": 0, 884 | "mode": 4 885 | } 886 | ] 887 | }, 888 | { 889 | "primitives": [ 890 | { 891 | "attributes": { 892 | "COLOR_0": 30, 893 | "NORMAL": 29, 894 | "POSITION": 28 895 | }, 896 | "indices": 31, 897 | "material": 0, 898 | "mode": 4 899 | } 900 | ] 901 | }, 902 | { 903 | "primitives": [ 904 | { 905 | "attributes": { 906 | "COLOR_0": 34, 907 | "NORMAL": 33, 908 | "POSITION": 32 909 | }, 910 | "indices": 35, 911 | "material": 0, 912 | "mode": 4 913 | } 914 | ] 915 | }, 916 | { 917 | "primitives": [ 918 | { 919 | "attributes": { 920 | "COLOR_0": 38, 921 | "NORMAL": 37, 922 | "POSITION": 36 923 | }, 924 | "indices": 39, 925 | "material": 0, 926 | "mode": 4 927 | } 928 | ] 929 | }, 930 | { 931 | "primitives": [ 932 | { 933 | "attributes": { 934 | "COLOR_0": 42, 935 | "NORMAL": 41, 936 | "POSITION": 40 937 | }, 938 | "indices": 43, 939 | "material": 0, 940 | "mode": 4 941 | } 942 | ] 943 | } 944 | ], 945 | "nodes": [ 946 | { 947 | "children": [ 948 | 1 949 | ], 950 | "name": "RootNode (gltf orientation matrix)", 951 | "rotation": [ 952 | -0.70710678118654746, 953 | -0, 954 | -0, 955 | 0.70710678118654757 956 | ] 957 | }, 958 | { 959 | "children": [ 960 | 2 961 | ], 962 | "matrix": [ 963 | 1, 964 | 0, 965 | 0, 966 | 0, 967 | 0, 968 | 0, 969 | 1, 970 | 0, 971 | 0, 972 | -1, 973 | 0, 974 | 0, 975 | 0, 976 | 11.171805381774902, 977 | 14.953897476196289, 978 | 1 979 | ], 980 | "name": "RootNode (model correction matrix)" 981 | }, 982 | { 983 | "children": [ 984 | 3 985 | ], 986 | "matrix": [ 987 | 1, 988 | 0, 989 | 0, 990 | 0, 991 | 0, 992 | 1, 993 | 0, 994 | 0, 995 | 0, 996 | 0, 997 | 1, 998 | 0, 999 | -31.215309143066406, 1000 | -22.012807846069336, 1001 | 0.0019680000841617584, 1002 | 1 1003 | ], 1004 | "name": "1d969623d787416bb4b5123cdde5ce0b.dxf.cleaner" 1005 | }, 1006 | { 1007 | "children": [ 1008 | 4 1009 | ], 1010 | "matrix": [ 1011 | 1, 1012 | 0, 1013 | 0, 1014 | 0, 1015 | 0, 1016 | 1, 1017 | 0, 1018 | 0, 1019 | 0, 1020 | 0, 1021 | 1, 1022 | 0, 1023 | 1.4306640494510248e-07, 1024 | -1.5393066377100695e-07, 1025 | -8.4161758291678712e-11, 1026 | 1 1027 | ], 1028 | "name": "Layers" 1029 | }, 1030 | { 1031 | "children": [ 1032 | 5, 1033 | 6, 1034 | 7, 1035 | 8, 1036 | 9, 1037 | 10, 1038 | 11, 1039 | 12, 1040 | 13, 1041 | 14, 1042 | 15 1043 | ], 1044 | "name": "0" 1045 | }, 1046 | { 1047 | "mesh": 0, 1048 | "name": "" 1049 | }, 1050 | { 1051 | "mesh": 1, 1052 | "name": "" 1053 | }, 1054 | { 1055 | "mesh": 2, 1056 | "name": "" 1057 | }, 1058 | { 1059 | "mesh": 3, 1060 | "name": "" 1061 | }, 1062 | { 1063 | "mesh": 4, 1064 | "name": "" 1065 | }, 1066 | { 1067 | "mesh": 5, 1068 | "name": "" 1069 | }, 1070 | { 1071 | "mesh": 6, 1072 | "name": "" 1073 | }, 1074 | { 1075 | "mesh": 7, 1076 | "name": "" 1077 | }, 1078 | { 1079 | "mesh": 8, 1080 | "name": "" 1081 | }, 1082 | { 1083 | "mesh": 9, 1084 | "name": "" 1085 | }, 1086 | { 1087 | "mesh": 10, 1088 | "name": "" 1089 | } 1090 | ], 1091 | "scene": 0, 1092 | "scenes": [ 1093 | { 1094 | "name": "OSG_Scene", 1095 | "nodes": [ 1096 | 0 1097 | ] 1098 | } 1099 | ] 1100 | } 1101 | 1102 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require("path"); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 5 | const PreloadWebpackPlugin = require("preload-webpack-plugin"); 6 | const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin"); 7 | const CopyPlugin = require("copy-webpack-plugin"); 8 | const entry = path.resolve(__dirname, "./src/js/index.js"); 9 | const nodePath = path.resolve(__dirname, "./node_modules"); 10 | 11 | module.exports = { 12 | stats: { 13 | chunks: true, 14 | colors: true, 15 | env: true 16 | }, 17 | performance: { 18 | hints: false 19 | }, 20 | entry: { 21 | main: entry 22 | }, 23 | output: { 24 | filename: "js/[name].bundle.js", 25 | path: path.resolve(__dirname, "dist") 26 | }, 27 | module: { 28 | rules: [ 29 | // Shim SPE-Package 30 | { 31 | test: require.resolve("shader-particle-engine"), 32 | use: [ 33 | 'imports-loader?imports[]=namespace|three|THREE', 34 | 'exports-loader?exports=default|SPE' 35 | ] 36 | }, 37 | // Targets all .js files 38 | { 39 | test: /\.m?js$/i, 40 | exclude: nodePath, 41 | use: [ 42 | // Transplies from ES6 to ES5. 43 | { 44 | loader: "babel-loader", 45 | options: { 46 | cacheDirectory: true, 47 | presets: ["@babel/preset-env"], 48 | cacheCompression: true 49 | } 50 | }, 51 | // Lint javascript before transpiling 52 | { 53 | loader: "eslint-loader", 54 | options: { 55 | cache: true 56 | } 57 | } 58 | ] 59 | }, 60 | // Loads all CSS, SASS AND SCSS files 61 | { 62 | test: /\.(sa|sc|c)ss$/i, 63 | use: [ 64 | { 65 | loader: MiniCssExtractPlugin.loader, 66 | options: { 67 | // Path all assets AFTER build process 68 | publicPath: "../", 69 | hmr: true 70 | } 71 | }, 72 | // Translates CSS into CommonJS 73 | { 74 | loader: "css-loader", 75 | options: { 76 | sourceMap: true 77 | } 78 | }, 79 | // Adds vendor prefixes with Autoprefixer 80 | "postcss-loader", 81 | { 82 | // Compiles SASS to CSS 83 | loader: "sass-loader", 84 | options: { 85 | sourceMap: true 86 | } 87 | } 88 | ] 89 | } 90 | ] 91 | }, 92 | plugins: [ 93 | new HtmlWebpackPlugin({ 94 | title: "Threejs ES6 Simple Boilerplate", 95 | filename: "index.html", 96 | template: "./src/static/html/index.html", 97 | favicon: "./src/static/images/favicons/favicon.ico", 98 | inject: "head" 99 | }), 100 | //Adds rel="preload" to fonts; 101 | new PreloadWebpackPlugin({ 102 | rel: "preload", 103 | as(entry) { 104 | if (/\.(woff|woff2|ttf|otf)$/.test(entry)) return "font"; 105 | }, 106 | fileWhitelist: [/\.(woff|woff2|ttf|otf)$/], 107 | //Includes all assets; even fonts loaded by file-loader 108 | include: "allAssets" 109 | }), 110 | //Adds defer to js scripts to speed load times. 111 | new ScriptExtHtmlWebpackPlugin({ 112 | defaultAttribute: "defer" 113 | }), 114 | new CopyPlugin({ 115 | patterns: [ 116 | { from: path.resolve(__dirname, "./src/js/vendor/draco"), to: "js/vendor/draco" } 117 | ] 118 | }) 119 | ], 120 | optimization: { 121 | runtimeChunk: "single", 122 | moduleIds: "hashed", 123 | splitChunks: { 124 | cacheGroups: { 125 | // Extracts all .css files into a single css file 126 | styles: { 127 | name: "styles", 128 | test: /\.css$/, 129 | chunks: "all", 130 | enforce: true 131 | } 132 | } 133 | } 134 | } 135 | }; 136 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const merge = require("webpack-merge"); 3 | const common = require("./webpack.common.js"); 4 | const path = require("path"); 5 | const webpack = require("webpack"); 6 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 7 | 8 | module.exports = merge(common, { 9 | mode: "development", 10 | devtool: "inline-source-map", 11 | output: { 12 | filename: "js/[name].bundle.js", 13 | path: path.resolve(__dirname, "dist") 14 | }, 15 | module: { 16 | rules: [ 17 | // Loads all image files; no minfication 18 | { 19 | test: /\.(png|svg|jpe?g|gif|ico)$/i, 20 | use: { 21 | loader: "file-loader", 22 | options: { 23 | outputPath: "images/", 24 | name: "[name].[ext]", 25 | esModule: false 26 | } 27 | } 28 | }, 29 | // Loads all audio files 30 | { 31 | test: /\.(ogg|wma|mp3|wav|mpe?g)$/i, 32 | use: { 33 | loader: "file-loader", 34 | options: { 35 | outputPath: "audio/", 36 | // name: "[name].[contenthash].[ext]" 37 | name: "[name].[ext]", 38 | esModule: false 39 | } 40 | } 41 | }, 42 | // Loads all font files 43 | { 44 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 45 | use: { 46 | loader: "file-loader", 47 | options: { 48 | outputPath: "fonts/", 49 | name: "[name].[ext]", 50 | esModule: false 51 | } 52 | } 53 | }, 54 | // Loads all 3D model files; add more based on your needs 55 | { 56 | test: /\.(obj|gltf|drc|mtl|glb)$/i, 57 | use: { 58 | loader: "file-loader", 59 | options: { 60 | outputPath: "models/", 61 | name: "[name].[ext]", 62 | esModule: false 63 | } 64 | } 65 | }, 66 | //Load all .html files 67 | { 68 | test: /\.(html)$/, 69 | use: { 70 | loader: "html-loader", 71 | options: { 72 | root: path.resolve(__dirname, "dist") 73 | } 74 | } 75 | } 76 | ] 77 | }, 78 | devServer: { 79 | contentBase: path.join(__dirname, "src/static"), 80 | compress: true, 81 | hot: true, 82 | port: 9000 83 | }, 84 | plugins: [ 85 | new MiniCssExtractPlugin({ 86 | filename: "css/style.css", 87 | chunkFilename: "css/style.[id].css" 88 | }) 89 | ], 90 | optimization: { 91 | splitChunks: { 92 | cacheGroups: { 93 | vendor: { 94 | test: /[\\/]node_modules[\\/]/, 95 | name: "vendor", 96 | chunks: "all", 97 | reuseExistingChunk: true 98 | } 99 | } 100 | } 101 | } 102 | }); 103 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require("webpack-merge"); 2 | const common = require("./webpack.common.js"); 3 | const TerserJSPlugin = require("terser-webpack-plugin"); 4 | const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); 5 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 6 | const path = require("path"); 7 | const CompressionPlugin = require("compression-webpack-plugin"); 8 | const glob = require("glob"); 9 | const PurgecssPlugin = require("purgecss-webpack-plugin"); 10 | const ImageminPlugin = require("imagemin-webpack-plugin").default; 11 | const imageminMozjpeg = require("imagemin-mozjpeg"); 12 | 13 | module.exports = merge(common, { 14 | mode: "production", 15 | devtool: "cheap-module-eval-source-map", 16 | output: { 17 | // Contenthash substitution used for cache bursting 18 | filename: "js/[name].[contenthash].bundle.js", 19 | path: path.resolve(__dirname, "dist") 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.(png|svg|jpe?g|gif|ico)$/i, 25 | use: [ 26 | { 27 | loader: "file-loader", 28 | options: { 29 | outputPath: "images/", 30 | name: "[name].[contenthash].[ext]", 31 | esModule: false 32 | } 33 | } 34 | ] 35 | }, 36 | // Loads all audio files; 37 | { 38 | test: /\.(ogg|wma|mp3|wav|mpe?g)$/i, 39 | use: { 40 | loader: "file-loader", 41 | options: { 42 | outputPath: "audio/", 43 | name: "[name].[contenthash].[ext]", 44 | esModule: false 45 | } 46 | } 47 | }, 48 | // Loads all font files 49 | { 50 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 51 | use: { 52 | loader: "file-loader", 53 | options: { 54 | outputPath: "fonts/", 55 | name: "[name].[contenthash].[ext]", 56 | esModule: false 57 | } 58 | } 59 | }, 60 | // Loads all 3D model files; add more based on your needs 61 | { 62 | test: /\.(obj|gltf|drc|mtl|glb)$/i, 63 | use: { 64 | loader: "file-loader", 65 | options: { 66 | outputPath: "models/", 67 | name: "[name].[contenthash].[ext]", 68 | esModule: false 69 | } 70 | } 71 | }, 72 | // loads all html files 73 | { 74 | test: /\.(html)$/, 75 | use: { 76 | loader: "html-loader", 77 | options: { 78 | minimize: true, 79 | root: path.resolve(__dirname, "dist") 80 | } 81 | } 82 | } 83 | ] 84 | }, 85 | plugins: [ 86 | new MiniCssExtractPlugin({ 87 | filename: "css/style.[contenthash].css", 88 | chunkFilename: "css/style.[contenthash].css" 89 | }), 90 | new ImageminPlugin({ 91 | optipng: { 92 | optimizationLevel: 6 93 | }, 94 | plugins: [ 95 | imageminMozjpeg({ 96 | quality: 100, 97 | progressive: true 98 | }) 99 | ] 100 | }), 101 | new CompressionPlugin({ 102 | test: /\.(html|css|js)(\?.*)?$/i 103 | }), 104 | new PurgecssPlugin({ 105 | paths: glob.sync("src/**/*", { nodir: true }) 106 | }) 107 | ], 108 | optimization: { 109 | minimizer: [ 110 | // Minify JS; by default applies to all .js files; 111 | new TerserJSPlugin({ 112 | cache: true, 113 | parallel: true, 114 | sourceMap: true 115 | }), 116 | // Minify CSS; default applies to all .css files 117 | new OptimizeCSSAssetsPlugin({}) 118 | ], 119 | splitChunks: { 120 | chunks: "all", 121 | maxInitialRequests: Infinity, 122 | minSize: 0, 123 | cacheGroups: { 124 | vendor: { 125 | test: /[\\/]node_modules[\\/]/, 126 | reuseExistingChunk: true, 127 | name(module) { 128 | const packageName = module.context.match( 129 | /[\\/]node_modules[\\/](.*?)([\\/]|$)/ 130 | )[1]; 131 | return `vendor/npm.${packageName.replace("@", "")}`; 132 | } 133 | } 134 | } 135 | } 136 | } 137 | }); 138 | --------------------------------------------------------------------------------